From 4c02e811006101fa2ecc07f085cedbb33cc54e87 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 23:48:36 +0000 Subject: [PATCH 1/4] ci(workflow-linter): look for SPDX in the leading comment block The "Check SPDX headers" step only read line 1 of each workflow. Dependabot's actions-lock bumps now prepend "# This workflow is managed by gh actions-lock." above the existing header, so every workflow's SPDX line sits on line 2 or later and lint-workflows fails on main ("missing SPDX header", one error per file). Moving the SPDX line back to the top would last only until the next Dependabot bump. The check now accepts an SPDX-License-Identifier anywhere in the leading block of comment and blank lines, before the first YAML line. A file whose only SPDX line comes after YAML content, or that has none, still fails. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0132Cdz3iwk2QGKv2sUhhsye --- .github/workflows/workflow-linter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/workflow-linter.yml b/.github/workflows/workflow-linter.yml index eac10a8..49031c1 100644 --- a/.github/workflows/workflow-linter.yml +++ b/.github/workflows/workflow-linter.yml @@ -27,7 +27,7 @@ jobs: errors=0 for f in .github/workflows/*.yml .github/workflows/*.yaml; do [ -f "$f" ] || continue - if ! head -1 "$f" | grep -q "SPDX-License-Identifier"; then + if ! awk '/^[[:space:]]*(#|$)/ { if (/SPDX-License-Identifier/) found = 1; next } { exit } END { exit !found }' "$f"; then echo "ERROR: $f missing SPDX header" errors=$((errors + 1)) fi From 122982c8044b7d441d4966aa562c5ba2e11639e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 00:06:31 +0000 Subject: [PATCH 2/4] fix(k9): satisfy the K9 validator in three contracts The Dogfood Gate's "Validate K9 contracts" job (k9-ecosystem/validate-action) fails on main with four errors: - config/build.k9.ncl, config/rust-fmt.k9.ncl: "Missing K9 format marker". The K9! preamble sat on line 2, under the SPDX comment; the validator requires it as the first non-empty line, which is how the contractiles templates lay it out. K9! now comes first, SPDX second. - config/rust-fmt.k9.ncl: "Pedigree block missing 'name' field". Both config contracts now carry a pedigree name. - setup-dev-env.k9.ncl: "Hunt-level K9 file must include a 'signature' or 'signature_required' field". It is a Hunt component (it runs installers and shell commands), so its pedigree now declares signature_required = true, as contractiles/self-validating/template-hunt.k9.ncl does. The validator now reports 10 files scanned, 0 errors. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0132Cdz3iwk2QGKv2sUhhsye (cherry picked from commit a94f3eaa487b65ce9d810e5b4e0ea4df1eaa817c) --- config/build.k9.ncl | 3 ++- config/rust-fmt.k9.ncl | 3 ++- setup-dev-env.k9.ncl | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/config/build.k9.ncl b/config/build.k9.ncl index 2c396ce..ed65584 100644 --- a/config/build.k9.ncl +++ b/config/build.k9.ncl @@ -1,10 +1,11 @@ -# SPDX-License-Identifier: MPL-2.0 K9! +# SPDX-License-Identifier: MPL-2.0 leash = 'Yard # Validation only, no I/O pedigree = { schema_version = "1.0.0", component_type = "rust-build-config", + name = "bunsenite-build-config", tool = "bunsenite", validation_level = "strict", description = "Self-validating Cargo build configuration for Bunsenite" diff --git a/config/rust-fmt.k9.ncl b/config/rust-fmt.k9.ncl index e0035dc..8bd2033 100644 --- a/config/rust-fmt.k9.ncl +++ b/config/rust-fmt.k9.ncl @@ -1,10 +1,11 @@ -# SPDX-License-Identifier: MPL-2.0 K9! +# SPDX-License-Identifier: MPL-2.0 leash = 'Yard # Validation only, no I/O pedigree = { schema_version = "1.0.0", component_type = "rust-formatter-config", + name = "bunsenite-rust-fmt-config", tool = "bunsenite", validation_level = "strict", description = "Self-validating Rust formatter configuration for Bunsenite" diff --git a/setup-dev-env.k9.ncl b/setup-dev-env.k9.ncl index 3c9d087..730f0d1 100644 --- a/setup-dev-env.k9.ncl +++ b/setup-dev-env.k9.ncl @@ -11,6 +11,7 @@ leash = 'Hunt pedigree = { schema_version = "1.0.0", component_type = "dev-env-setup", + signature_required = true, author = "Jonathan D.A. Jewell ", description = "Automated development environment setup for Bunsenite", created = "2026-01-30", From fbe5a56db26cc2ad9c83fe36489322aaae0536ab Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 00:13:53 +0000 Subject: [PATCH 3/4] ci(cflite): add the Dockerfile ClusterFuzzLite needs and fix the fuzz build ClusterFuzzLite fails in build_fuzzers on every PR, for three reasons: - It runs `docker build -f .clusterfuzzlite/Dockerfile`, but the directory only had a Containerfile. Add a Dockerfile that mirrors it (same pinned base-builder-rust digest). The Containerfile stays for podman-first local builds. - build.sh copied targets from ./target/..., but cargo-fuzz writes them to ./fuzz/target/x86_64-unknown-linux-gnu/release/. The copy failed with "cannot stat". - Once built, fuzz_parser reported a leak on its first input (a single newline). The leak comes from nickel-lang-parser, which allocates AST nodes in a bumpalo arena that never runs destructors, so strings in those nodes always leak. Add fuzz_parser.options turning off leak detection for libFuzzer and ASan, and have build.sh copy each target's .options file to $OUT. ASan still reports memory-safety errors. Verified locally with the same base image: - The compile finishes and $OUT contains fuzz_parser. - A 90-second fuzz run (615 executions) produced no crashes. - With both leak switches off, the run exits 0 with no artifacts. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0132Cdz3iwk2QGKv2sUhhsye (cherry picked from commit 3f5120de8656a825580197e775bcdc1870428c3f) --- .clusterfuzzlite/Dockerfile | 13 +++++++++++++ .clusterfuzzlite/build.sh | 5 ++++- .clusterfuzzlite/fuzz_parser.options | 15 +++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 .clusterfuzzlite/Dockerfile create mode 100644 .clusterfuzzlite/fuzz_parser.options diff --git a/.clusterfuzzlite/Dockerfile b/.clusterfuzzlite/Dockerfile new file mode 100644 index 0000000..274187a --- /dev/null +++ b/.clusterfuzzlite/Dockerfile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: MPL-2.0 +# ClusterFuzzLite build image for bunsenite. +# +# ClusterFuzzLite invokes `docker build -f .clusterfuzzlite/Dockerfile`, +# so this file must be named `Dockerfile`. It mirrors the OCI-neutral +# Containerfile in this directory (kept for podman-first local builds per +# the authority stack); keep the two in sync. +FROM gcr.io/oss-fuzz-base/base-builder-rust@sha256:73c1d5648db54100639339d411a5d192cbc8bf413ee91e843a07cf6f0e319dc7 + +COPY . $SRC/bunsenite +WORKDIR $SRC/bunsenite + +COPY .clusterfuzzlite/build.sh $SRC/ diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh index bfdc807..8a410da 100644 --- a/.clusterfuzzlite/build.sh +++ b/.clusterfuzzlite/build.sh @@ -9,5 +9,8 @@ cargo +nightly fuzz build # Copy fuzz targets to $OUT for target in $(cargo +nightly fuzz list); do - cp ./target/x86_64-unknown-linux-gnu/release/$target $OUT/ + cp ./fuzz/target/x86_64-unknown-linux-gnu/release/$target $OUT/ + if [ -f .clusterfuzzlite/$target.options ]; then + cp .clusterfuzzlite/$target.options $OUT/ + fi done diff --git a/.clusterfuzzlite/fuzz_parser.options b/.clusterfuzzlite/fuzz_parser.options new file mode 100644 index 0000000..46556d0 --- /dev/null +++ b/.clusterfuzzlite/fuzz_parser.options @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: MPL-2.0 +# Runtime options for the fuzz_parser target. build.sh copies this file to +# $OUT next to the binary, where the ClusterFuzzLite runner picks it up. +# +# Leak detection is off because nickel-lang-parser allocates AST nodes in a +# bumpalo arena, which never runs destructors. Strings held by those nodes +# therefore leak by design, on every input, including a single newline. +# Without this, every run reports that upstream leak as a crash and no real +# finding can surface. Memory-safety errors (overflows, use-after-free) are +# still reported by AddressSanitizer. +[libfuzzer] +detect_leaks = 0 + +[asan] +detect_leaks = 0 From 0d5dbbbab802aefa07ead405d544b2547bec1ef9 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 00:21:43 +0000 Subject: [PATCH 4/4] ci(cflite): copy only what the fuzz build reads, and use [[ in build.sh SonarCloud failed this PR on a D security rating for new code: - Dockerfile:10 "Copying recursively might inadvertently add sensitive data to the container." `COPY . $SRC/bunsenite` sent the whole checkout, .git included, into the build image. - build.sh:13 "Use '[[' instead of '['". The image now copies only what the fuzz build reads: - Cargo.toml and Cargo.lock - src/ - benches/ (declared as [[bench]] targets) - crates/ (the patched nickel-lang-vector) - fuzz/ - .clusterfuzzlite/ (build.sh reads the per-target .options from there) The Containerfile gets the same change, to stay in sync. Nothing in these paths uses include_str!/include_bytes! on files outside them, and `cargo metadata` resolves both manifests from that subset. The "runs as root" warning stays as it is: the OSS-Fuzz base-builder image needs root. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0132Cdz3iwk2QGKv2sUhhsye (cherry picked from commit bb33a7e116d5c23ac2e939322541780e1db62568) --- .clusterfuzzlite/Containerfile | 11 ++++++++++- .clusterfuzzlite/Dockerfile | 11 ++++++++++- .clusterfuzzlite/build.sh | 2 +- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/.clusterfuzzlite/Containerfile b/.clusterfuzzlite/Containerfile index ff3d423..2aeae2f 100644 --- a/.clusterfuzzlite/Containerfile +++ b/.clusterfuzzlite/Containerfile @@ -2,7 +2,16 @@ # ClusterFuzzLite build environment for bunsenite FROM gcr.io/oss-fuzz-base/base-builder-rust@sha256:73c1d5648db54100639339d411a5d192cbc8bf413ee91e843a07cf6f0e319dc7 -COPY . $SRC/bunsenite +# Copy only what the fuzz build reads, not the whole checkout: the crate +# manifest and lockfile, its sources and declared benches, the patched +# nickel-lang-vector crate, the fuzz crate, and this directory (build.sh +# reads per-target .options files from it). +COPY Cargo.toml Cargo.lock $SRC/bunsenite/ +COPY src $SRC/bunsenite/src +COPY benches $SRC/bunsenite/benches +COPY crates $SRC/bunsenite/crates +COPY fuzz $SRC/bunsenite/fuzz +COPY .clusterfuzzlite $SRC/bunsenite/.clusterfuzzlite WORKDIR $SRC/bunsenite COPY .clusterfuzzlite/build.sh $SRC/ diff --git a/.clusterfuzzlite/Dockerfile b/.clusterfuzzlite/Dockerfile index 274187a..4fafcd9 100644 --- a/.clusterfuzzlite/Dockerfile +++ b/.clusterfuzzlite/Dockerfile @@ -7,7 +7,16 @@ # the authority stack); keep the two in sync. FROM gcr.io/oss-fuzz-base/base-builder-rust@sha256:73c1d5648db54100639339d411a5d192cbc8bf413ee91e843a07cf6f0e319dc7 -COPY . $SRC/bunsenite +# Copy only what the fuzz build reads, not the whole checkout: the crate +# manifest and lockfile, its sources and declared benches, the patched +# nickel-lang-vector crate, the fuzz crate, and this directory (build.sh +# reads per-target .options files from it). +COPY Cargo.toml Cargo.lock $SRC/bunsenite/ +COPY src $SRC/bunsenite/src +COPY benches $SRC/bunsenite/benches +COPY crates $SRC/bunsenite/crates +COPY fuzz $SRC/bunsenite/fuzz +COPY .clusterfuzzlite $SRC/bunsenite/.clusterfuzzlite WORKDIR $SRC/bunsenite COPY .clusterfuzzlite/build.sh $SRC/ diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh index 8a410da..aeb61da 100644 --- a/.clusterfuzzlite/build.sh +++ b/.clusterfuzzlite/build.sh @@ -10,7 +10,7 @@ cargo +nightly fuzz build # Copy fuzz targets to $OUT for target in $(cargo +nightly fuzz list); do cp ./fuzz/target/x86_64-unknown-linux-gnu/release/$target $OUT/ - if [ -f .clusterfuzzlite/$target.options ]; then + if [[ -f .clusterfuzzlite/$target.options ]]; then cp .clusterfuzzlite/$target.options $OUT/ fi done