fix: harden runtime and release boundaries
ober
dbf27d00935d3434ecb0836cc8f054e12e4d8ee6
--- a/.github/workflows/build-macos-libs.yml +++ b/.github/workflows/build-macos-libs.yml @@ -5,6 +5,8 @@ on: push: paths: - 'jerboa-native-rs/**' + - 'support/container-dependencies.lock' + - 'support/container-inputs.sh' - '.github/workflows/build-macos-libs.yml' env: @@ -21,10 +23,12 @@ jobs: arch: x86_64 runs-on: ${{ matrix.runner }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@8fae6aadcd3e1a6f3fcb446ce7f9438c9d1349f1 # 1.94.1 + with: + toolchain: 1.94.1 - name: Build libjerboa_native.a run: | @@ -32,8 +36,8 @@ jobs: cargo build --release ls -lh target/release/libjerboa_native.a - - name: Clone jerboa-shell (for rust-coreutils) - run: git clone --depth 1 https://git.sr.ht/~lisp/jerboa-shell /tmp/jerboa-shell + - name: Materialize pinned jerboa-shell (for rust-coreutils) + run: support/container-inputs.sh fetch-git jerboa-shell - name: Build libjsh_coreutils.a run: | --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,14 +12,21 @@ jobs: build-and-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + + - name: Verify immutable container inputs + id: container-lock + run: | + support/container-inputs.sh check + echo "base_image=$(support/container-inputs.sh field ubuntu-24.04 3)" >> "$GITHUB_OUTPUT" - name: Build base image (loaded locally) - uses: docker/build-push-action@v5 + uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0 with: context: . + build-args: JERBOA_BASE_IMAGE=${{ steps.container-lock.outputs.base_image }} platforms: linux/amd64 tags: ${{ env.IMAGE }}:ci load: true @@ -87,20 +94,27 @@ jobs: if: github.ref == 'refs/heads/master' && github.event_name == 'push' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + + - name: Verify immutable container inputs + id: container-lock + run: | + support/container-inputs.sh check + echo "base_image=$(support/container-inputs.sh field ubuntu-24.04 3)" >> "$GITHUB_OUTPUT" - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push (latest + sha) - uses: docker/build-push-action@v5 + uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0 with: context: . + build-args: JERBOA_BASE_IMAGE=${{ steps.container-lock.outputs.base_image }} platforms: linux/amd64 tags: | ${{ env.IMAGE }}:latest --- a/.github/workflows/security-baseline.yml +++ b/.github/workflows/security-baseline.yml @@ -13,7 +13,7 @@ jobs: baseline: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Required release files run: | --- a/.gitignore +++ b/.gitignore @@ -89,3 +89,4 @@ tests/vm/*.log tests/vm/*.pid .claude/ *.jpkg +.jerboa-catalog.lock --- a/.jerboa/security.json +++ b/.jerboa/security.json @@ -16,6 +16,7 @@ "lib/**/*.sls", "support/**/*.{ss,c,h,sh}", "src/**/*.{c,h}", + "jerboa-native-rs/src/**/*.rs", "jerbuild.ss", "Makefile" ], @@ -29,6 +30,7 @@ "lib-cross/**", ".chez/**", ".chez-cross-*/**", + "jerboa-native-rs/target/**", "jerboa-bin*" ], "vendor": [ --- a/Dockerfile +++ b/Dockerfile @@ -22,16 +22,36 @@ # docker build --platform linux/amd64 -t jerboa21/jerboa . # docker push jerboa21/jerboa -FROM ubuntu:24.04 +ARG JERBOA_BASE_IMAGE +FROM ${JERBOA_BASE_IMAGE} + +ARG JERBOA_BASE_IMAGE + +# The base reference is deliberately supplied by the consumer-controlled lock +# rather than defaulting to a mutable tag. Direct builds without an immutable +# reference fail while resolving FROM; a substituted tag is rejected here. +COPY support/container-inputs.sh support/container-dependencies.lock /tmp/jerboa-container/ +ENV JERBOA_CONTAINER_LOCK=/tmp/jerboa-container/container-dependencies.lock +RUN /tmp/jerboa-container/container-inputs.sh check-base "$JERBOA_BASE_IMAGE" && \ + /tmp/jerboa-container/container-inputs.sh check +LABEL org.opencontainers.image.base.name="$JERBOA_BASE_IMAGE" ARG DEBIAN_FRONTEND=noninteractive # ── System dependencies for static builds ──────────────────────────────────── -RUN apt-get update && apt-get install -y --no-install-recommends \ +RUN snapshot=$(/tmp/jerboa-container/container-inputs.sh field ubuntu-noble 3) && \ + rm -f /etc/apt/sources.list /etc/apt/sources.list.d/ubuntu.sources && \ + { \ + echo "deb [check-valid-until=no] $snapshot noble main restricted universe multiverse"; \ + echo "deb [check-valid-until=no] $snapshot noble-updates main restricted universe multiverse"; \ + echo "deb [check-valid-until=no] $snapshot noble-security main restricted universe multiverse"; \ + } > /etc/apt/sources.list.d/jerboa-snapshot.list && \ + apt-get update && apt-get install -y --no-install-recommends \ build-essential \ musl-tools \ musl-dev \ git \ + openssh-client \ ca-certificates \ curl \ libncurses-dev \ @@ -51,11 +71,15 @@ RUN printf '#!/bin/sh\nexec /usr/bin/g++ -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 " ln -sf /usr/local/bin/x86_64-linux-musl-g++ /usr/local/bin/musl-g++ # ── Rust toolchain ──────────────────────────────────────────────────────────── -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ - sh -s -- -y --default-toolchain stable --profile minimal && \ +RUN /tmp/jerboa-container/container-inputs.sh fetch rustup-installer /tmp/rustup-init && \ + rust_version=$(/tmp/jerboa-container/container-inputs.sh field rust 4) && \ + audit_version=$(/tmp/jerboa-container/container-inputs.sh field cargo-audit 4) && \ + chmod 0755 /tmp/rustup-init && \ + /tmp/rustup-init -y --default-toolchain "$rust_version" --profile minimal && \ + rm -f /tmp/rustup-init && \ . /root/.cargo/env && \ - rustup target add x86_64-unknown-linux-musl && \ - cargo install cargo-audit --locked + rustup target add --toolchain "$rust_version" x86_64-unknown-linux-musl && \ + cargo install cargo-audit --version "=$audit_version" --locked ENV PATH="/root/.cargo/bin:${PATH}" ENV RUSTUP_HOME="/root/.rustup" @@ -112,21 +136,16 @@ RUN cd /build/mine/jerboa/jerboa-native-rs && \ sed -i 's/^mod duckdb_native;/#[cfg(feature = "duckdb")]\nmod duckdb_native;/' src/lib.rs && \ CARGO_HOME=/build/.cargo \ RUSTFLAGS="--remap-path-prefix /build/.cargo/registry/src=crate --remap-path-prefix /build/mine=src" \ - cargo build --release --target x86_64-unknown-linux-musl --no-default-features && \ + cargo build --locked --release --target x86_64-unknown-linux-musl --no-default-features && \ strip -S target/x86_64-unknown-linux-musl/release/libjerboa_native.a -# ── Clone all common dependency repos ──────────────────────────────────────── -RUN git clone --depth 1 https://git.sr.ht/~lisp/jerboa-ssh && \ - git clone --depth 1 https://git.sr.ht/~lisp/jerboa-sqlite && \ - git clone --depth 1 https://git.sr.ht/~lisp/jerboa-crypto && \ - git clone --depth 1 https://git.sr.ht/~lisp/jerboa-ssl && \ - git clone --depth 1 https://git.sr.ht/~lisp/jerboa-https && \ - git clone --depth 1 https://git.sr.ht/~lisp/jerboa-awk && \ - git clone --depth 1 https://git.sr.ht/~lisp/jerboa-sed && \ - git clone --depth 1 https://git.sr.ht/~lisp/jerboa-aws && \ - git clone --depth 1 https://git.sr.ht/~lisp/jerboa-fuse && \ - git clone --depth 1 https://git.sr.ht/~lisp/jerboa-scintilla && \ - git clone --depth 1 https://git.sr.ht/~lisp/jerboa-pcre2 +# ── Materialize pinned common dependency repos ─────────────────────────────── +RUN for name in \ + jerboa-ssh jerboa-sqlite jerboa-crypto jerboa-ssl jerboa-https \ + jerboa-awk jerboa-sed jerboa-aws jerboa-fuse jerboa-scintilla \ + jerboa-pcre2; do \ + /tmp/jerboa-container/container-inputs.sh fetch-git "$name"; \ + done # ── TUI dependencies: libvterm, libpcre2, ncurses (static) ───────────────── RUN apt-get update && apt-get install -y --no-install-recommends \ @@ -142,16 +161,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # 2. Clone scintilla-termbox backend into scintilla/termbox/ # 3. Clone termbox_next into scintilla/termbox/termbox_next/ # 4. Build all three static archives -RUN mkdir -p /build/sci-vendor && cd /build/sci-vendor && \ - SCINTILLA_VER=558 && LEXILLA_VER=544 && \ - curl -sLO https://scintilla.org/scintilla${SCINTILLA_VER}.tgz && \ - tar xf scintilla${SCINTILLA_VER}.tgz && rm scintilla${SCINTILLA_VER}.tgz && \ - curl -sLO https://scintilla.org/lexilla${LEXILLA_VER}.tgz && \ - tar xf lexilla${LEXILLA_VER}.tgz && rm lexilla${LEXILLA_VER}.tgz && \ - git clone --depth 1 https://github.com/masahino/scintilla-termbox.git \ - /build/sci-vendor/scintilla/termbox && \ - cd /build/sci-vendor/scintilla/termbox && \ - git clone --depth 1 https://github.com/masahino/termbox_next.git && \ +RUN mkdir -p /build/sci-vendor && \ + /tmp/jerboa-container/container-inputs.sh extract-tgz scintilla && \ + /tmp/jerboa-container/container-inputs.sh extract-tgz lexilla && \ + /tmp/jerboa-container/container-inputs.sh fetch-git scintilla-termbox && \ + /tmp/jerboa-container/container-inputs.sh fetch-git termbox-next && \ cd /build/sci-vendor/scintilla/termbox/termbox_next && make -j$(nproc) && \ cd /build/sci-vendor/scintilla/termbox && make -j$(nproc) && \ cd /build/sci-vendor/lexilla/src && make -j$(nproc) && \ @@ -169,6 +183,21 @@ ENV JERBOA_SCINTILLA_DIR=/build/mine/jerboa-scintilla/src ENV JERBOA_PCRE2_DIR=/build/mine/jerboa-pcre2 ENV SCI_VENDOR_DIR=/build/sci-vendor +# Retain the resolved non-Cargo graph in the image itself. The checked lock +# captures remote identity; dpkg/toolchain records capture what the snapshot +# resolver and compilers actually selected. +RUN mkdir -p /usr/local/share/jerboa/build-graph && \ + cp /tmp/jerboa-container/container-dependencies.lock \ + /usr/local/share/jerboa/build-graph/container-dependencies.lock && \ + dpkg-query -W -f='${binary:Package}\t${Version}\n' \ + | LC_ALL=C sort > /usr/local/share/jerboa/build-graph/debian-packages.tsv && \ + { \ + rustc --version; \ + cargo --version; \ + scheme --version; \ + musl-gcc --version | head -1; \ + } > /usr/local/share/jerboa/build-graph/toolchains.txt 2>&1 + # ── Smoke test ─────────────────────────────────────────────────────────────── RUN scheme --version && \ musl-gcc --version | head -1 && \ --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ JERBOA_HOME := $(CURDIR) +export JERBOA_HOME PROJECT_VERSION ?= $(shell if [ -r VERSION ]; then v=$$(tr -d '[:space:]' < VERSION); if [ -n "$$v" ]; then printf '%s\n' "$$v"; else printf '0.2.0\n'; fi; else printf '0.2.0\n'; fi) SOURCE_DATE_EPOCH ?= $(shell git log -1 --format=%ct) export SOURCE_DATE_EPOCH @@ -77,7 +78,8 @@ TYPED_WRAPPER_DIR ?= build/typed/jerboa jmcp-freebsd-arm64 jmcp-linux-amd64 jmcp-linux-arm64 \ jmcp-macos-amd64 test-all test-nrepl-auth test-phase2 \ test-phase3 test-phase5a test-phase5b test-phase5c test-phase5d \ - test-typed-fuzz typed-project-build + test-typed-fuzz typed-project-build test-web-large-response \ + test-secure-link-argv test-jerboa-build-argv help: @echo "Usage: make <target>" @@ -311,10 +313,13 @@ jerbuild-smoke: jerbuild # dist/jmcp # MCP server (mcp/server.ss) # dist/jlsp # LSP server (lsp/main-binary.ss) # dist/jerbuild <src> <lib> # transpile; dist/jerbuild binary E.ss OUT -.PHONY: jerboa jerboa-multicall jerboa-smoke +.PHONY: jerboa jerboa-multicall jerboa-smoke native-bundle jerboa: chez build mcp-check lsp-gen jerboa-multicall -jerboa-multicall: chez +native-bundle: + cd $(RUST_NATIVE_DIR) && CARGO_TARGET_DIR=target/bundle-native cargo build --locked --release --features full + +jerboa-multicall: chez native-bundle $(SCHEME) --libdirs $(LIBDIRS) --script support/build-jerboa-multicall.ss .PHONY: system-check @@ -544,6 +549,10 @@ signing-evidence: JERBOA_RELEASE_DIR="$(RELEASE_DIR)" JERBOA_SIGNING_EVIDENCE_DIR="$(SIGNING_EVIDENCE_DIR)" support/sign-release-artifacts.sh evidence release-upload: + @JERBOA_RELEASE_DIR="$(RELEASE_DIR)" \ + JERBOA_REQUIRE_RELEASE_SIGNATURES=1 \ + JERBOA_REQUIRE_RELEASE_SIGNATURE_VERIFICATION=1 \ + support/sign-release-artifacts.sh check @support/sourcehut-release-upload.sh "$(RELEASE_VERSION)" "$(RELEASE_REPO)" \ "$(RELEASE_DIR)"/jerboa-"$(RELEASE_VERSION)"-*.tar.gz \ "$(RELEASE_DIR)"/jerboa-"$(RELEASE_VERSION)"-*.tar.gz.sha256 \ @@ -556,10 +565,14 @@ release-upload: "$(RELEASE_DIR)"/release-manifest.sha256 \ "$(RELEASE_DIR)"/release-manifest.sha256.asc \ "$(RELEASE_DIR)"/release-manifest.sha256.sig \ - "$(RELEASE_DIR)"/release-manifest.sha256.minisig + "$(RELEASE_DIR)"/release-manifest.sha256.minisig \ + "$(RELEASE_DIR)"/install.sh \ + "$(RELEASE_DIR)"/install.sh.asc \ + "$(RELEASE_DIR)"/install.sh.sig \ + "$(RELEASE_DIR)"/install.sh.minisig sbom: - JERBOA_SBOM_DIR="$(SBOM_DIR)" support/sbom.sh + JERBOA_SBOM_DIR="$(SBOM_DIR)" SCHEME="$(SCHEME)" support/sbom.sh reproducibility-report: JERBOA_REPRO_DIR="$(REPRO_DIR)" support/reproducibility-report.sh @@ -587,7 +600,13 @@ release-evidence: $(MAKE) reproducibility-report > "$(EVIDENCE_DIR)/reproducibility-report.txt" 2>&1 JERBOA_RELEASE_DIR="$(RELEASE_DIR)" JERBOA_SIGNING_EVIDENCE_DIR="$(EVIDENCE_DIR)/signing" support/sign-release-artifacts.sh evidence > "$(EVIDENCE_DIR)/signing-evidence.txt" 2>&1 $(MAKE) sbom > "$(EVIDENCE_DIR)/sbom.txt" 2>&1 - shasum -a 256 Makefile SECURITY.md README.md docs/release-security.md docs/release-artifacts.md support/sign-release-artifacts.sh support/sbom.sh support/sanitize-evidence.sh support/fasl-cache-equivalence.ss support/reproducibility-report.sh tools/security-audit.sh > "$(EVIDENCE_DIR)/release-inputs-sha256.txt" + shasum -a 256 Dockerfile Makefile SECURITY.md README.md rust-toolchain.toml \ + docs/release-security.md docs/release-artifacts.md \ + support/container-dependencies.lock support/container-inputs.sh \ + support/install.sh support/ensure-jerboa.sh support/sign-release-artifacts.sh \ + support/sbom.sh support/sanitize-evidence.sh support/fasl-cache-equivalence.ss \ + support/reproducibility-report.sh tools/security-audit.sh \ + > "$(EVIDENCE_DIR)/release-inputs-sha256.txt" rm -rf "$(EVIDENCE_DIR)/sbom" "$(EVIDENCE_DIR)/reproducibility" cp -R "$(SBOM_DIR)" "$(EVIDENCE_DIR)/sbom" cp -R "$(REPRO_DIR)" "$(EVIDENCE_DIR)/reproducibility" @@ -810,6 +829,7 @@ mcp-check: @$(MAKE) data-check data-check: + @$(SCHEME) --libdirs $(LIBDIRS) --script support/data-check.ss --self-test @$(SCHEME) --libdirs $(LIBDIRS) --script support/data-check.ss data mcp: chez mcp-check @@ -821,8 +841,8 @@ mcp-run: @JERBOA_HOME=$(JERBOA_HOME) JERBOA_MCP_SCHEME_PATH=$(SCHEME) \ $(SCHEME) --libdirs $(LIBDIRS) --script $(MCP_ENTRY) -mcp-test: chez mcp-check - @JERBOA_MCP_MODE=full JERBOA_HOME=$(JERBOA_HOME) JERBOA_MCP_SCHEME_PATH=$(SCHEME) \ +mcp-test: chez mcp-check native + @$(NATIVE_TEST_ENV) JERBOA_MCP_MODE=full JERBOA_MCP_SCHEME_PATH=$(SCHEME) \ $(SCHEME) --libdirs $(LIBDIRS) --script $(MCP_TEST) mcp-test-binary: jmcp mcp-check @@ -1050,14 +1070,22 @@ test: native @set -e; \ for t in $(TEST_RUN_FILES); do \ echo "== $$t"; \ - $(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script "$$t"; \ + case "$$t" in \ + tests/test-sandbox.ss|tests/test-sandbox-native-timeout.ss|tests/test-sandbox-thread-failclosed.ss) run_mode=--program ;; \ + *) run_mode=--script ;; \ + esac; \ + $(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) "$$run_mode" "$$t"; \ done test-known-flaky: native @status=0; \ for t in $(TEST_QUARANTINE_FILES); do \ echo "== $$t"; \ - if $(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script "$$t"; then :; else status=1; fi; \ + case "$$t" in \ + tests/test-cage.ss|tests/test-functional.ss) run_mode=--program ;; \ + *) run_mode=--script ;; \ + esac; \ + if $(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) "$$run_mode" "$$t"; then :; else status=1; fi; \ done; \ exit $$status @@ -1178,6 +1206,8 @@ typed-wrapper-smoke: @$(MAKE) --no-print-directory typed-build TYPED_RUST_SOURCES=tests/fixtures/typed/rust-basic.ss TYPED_WRAPPER_DIR=build/typed/jerboa-smoke @lib="$$(find $(TYPED_RUST_DIR)/target/debug -maxdepth 1 \( -name 'libjerboa_typed_generated.dylib' -o -name 'libjerboa_typed_generated.so' \) -print | head -n 1)"; \ test -n "$$lib" || { echo "ERROR: generated typed Rust dynamic library not found" >&2; exit 1; }; \ + lib="$$(cd "$$(dirname "$$lib")" && pwd -P)/$$(basename "$$lib")"; \ + JERBOA_TYPED_RUST_DEV_NATIVE=1 \ JERBOA_TYPED_RUST_LIB="$$lib" \ TYPED_WRAPPER_FILE="build/typed/jerboa-smoke/sample_typed_rust_basic.ss" \ $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-typed-wrapper-e2e.ss @@ -1186,9 +1216,12 @@ typed-split-tree-smoke: @$(MAKE) --no-print-directory typed-build TYPED_RUST_SOURCES=tests/fixtures/typed/valid-split-tree.ss TYPED_WRAPPER_DIR=build/typed/split-tree-smoke @lib="$$(find $(TYPED_RUST_DIR)/target/debug -maxdepth 1 \( -name 'libjerboa_typed_generated.dylib' -o -name 'libjerboa_typed_generated.so' \) -print | head -n 1)"; \ test -n "$$lib" || { echo "ERROR: generated typed Rust dynamic library not found" >&2; exit 1; }; \ + lib="$$(cd "$$(dirname "$$lib")" && pwd -P)/$$(basename "$$lib")"; \ + JERBOA_TYPED_RUST_DEV_NATIVE=1 \ JERBOA_TYPED_RUST_LIB="$$lib" \ TYPED_WRAPPER_FILE="build/typed/split-tree-smoke/sample_typed_split_tree.ss" \ $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-typed-split-tree-e2e.ss && \ + JERBOA_TYPED_RUST_DEV_NATIVE=1 \ JERBOA_TYPED_RUST_LIB="$$lib" \ $(SCHEME) --libdirs $(LIBDIRS) --script support/typed-run.ss \ build/typed/split-tree-smoke/sample_typed_split_tree.ss \ @@ -1430,7 +1463,10 @@ test-phase4b: @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-row2.ss @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-effects-new.ss @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-taint.ss - @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-sandbox.ss + @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-sandbox-script-failclosed.ss + @$(SCHEME) --libdirs $(LIBDIRS) --program tests/test-sandbox.ss + @$(SCHEME) --libdirs $(LIBDIRS) --program tests/test-sandbox-thread-failclosed.ss + @$(SCHEME) --libdirs $(LIBDIRS) --program tests/test-sandbox-native-timeout.ss test-phase4c: @echo "--- Phase 4c: Systems and Performance tests ---" @@ -1506,9 +1542,9 @@ test-phase5e: @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-json-schema.ss @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-query-compile.ss -test-phase6: +test-phase6: native @echo "--- Phase 6: Making Real Programs Easier to Build ---" - @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-phase6.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-phase6.ss test-phase7: @echo "--- Phase 7: Gerbil Porting Features ---" @@ -1523,34 +1559,59 @@ test-repl: @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-repl-enhanced.ss @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-repl-server.ss -test-functional: +test-functional: native @echo "--- Functional Tests (real I/O, fork, Landlock, signals) ---" @if [ "$(HOST_UNAME_S)" = "Linux" ]; then \ gcc -shared -fPIC -O2 -o support/libjerboa-landlock.so support/landlock-shim.c; \ else \ echo " landlock shim: skipped on $(HOST_UNAME_S)"; \ fi - @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-functional.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --program tests/test-functional.ss -test-security: +test-security: native @echo "--- Security tests ---" + @sh tests/test-supply-chain-policy.sh + @SCHEME=$(SCHEME) sh tests/test-web-large-response.sh + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-secure-link-argv.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-jerboa-build-argv.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-fasl-safety.ss @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-security-profile.ss - @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-crypto-random.ss - @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-crypto-compare.ss - @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-crypto-digest.ss - @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-crypto-native.ss - @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-security-capability.ss - @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-restrict-hardened.ss - @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-process-exec.ss - @JERBOA_DB_HOST=evil.com JERBOA_DB_PORT=5433 JERBOA_SECRET=leaked $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-config-env.ss - @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-audit.ss - @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-sanitize.ss - @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-phase3-security.ss - @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-phase3-remaining.ss - @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-phase4-safety.ss - @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-phase5-os.ss - @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-phase6-supply.ss - @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-security2-parsers.ss + @SCHEME=$(SCHEME) sh tests/test-native-loader-policy.sh + @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-tls-deadline.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-ffi-buffer-capacity.ss + @if [ "$(HOST_UNAME_S)" = "Linux" ]; then \ + $(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/fixtures/netio-buffer-capacity.ss; \ + else \ + echo "netio-buffer-capacity: SKIP ($(HOST_UNAME_S), epoll-only)"; \ + fi + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-native-loader-imports.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-signal-import.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-crypto-random.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-crypto-compare.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-crypto-digest.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-crypto-native.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-security-capability.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-restrict-hardened.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-process-exec.ss + @JERBOA_DB_HOST=evil.com JERBOA_DB_PORT=5433 JERBOA_SECRET=leaked $(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-config-env.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-audit.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-sanitize.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-phase3-security.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-phase3-remaining.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-phase4-safety.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-phase5-os.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-phase6-supply.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-security2-parsers.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-thread-httpd-bind.ss + +test-web-large-response: + @SCHEME=$(SCHEME) sh tests/test-web-large-response.sh + +test-secure-link-argv: native + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-secure-link-argv.ss + +test-jerboa-build-argv: native + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-jerboa-build-argv.ss test-security-profile: @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-security-profile.ss @@ -1592,14 +1653,17 @@ else NATIVE_LIB_EXT = so NATIVE_LD_VAR = LD_LIBRARY_PATH endif -NATIVE_TEST_ENV = JERBOA_HOME=$(JERBOA_HOME) $(NATIVE_LD_VAR)=$(JERBOA_HOME)/lib:$${$(NATIVE_LD_VAR):-} +# Tests use the same explicit, canonical development override enforced by +# (std native). Do not also inject DYLD_LIBRARY_PATH/LD_LIBRARY_PATH: the +# system loader policy intentionally rejects ambient loader search state. +NATIVE_TEST_ENV = JERBOA_HOME=$(JERBOA_HOME) JERBOA_DEV_NATIVE=1 JERBOA_NATIVE_LIB=$(abspath $(RUST_NATIVE_LIB)) RUST_NATIVE_LIB = $(RUST_NATIVE_DIR)/target/release/libjerboa_native.$(NATIVE_LIB_EXT) $(RUST_NATIVE_LIB): $(RUST_NATIVE_DIR)/src/*.rs $(RUST_NATIVE_DIR)/Cargo.toml - cd $(RUST_NATIVE_DIR) && cargo build --release --features full + cd $(RUST_NATIVE_DIR) && cargo build --locked --release --features full native: - cd $(RUST_NATIVE_DIR) && cargo build --release --features full + cd $(RUST_NATIVE_DIR) && cargo build --locked --release --features full cp $(RUST_NATIVE_LIB) lib/ ifeq ($(UNAME_S),Darwin) @# Re-sign ad-hoc so dyld accepts the freshly-copied dylib. @@ -1617,7 +1681,11 @@ test-native: native @echo "--- Rust native library tests (weeks 1-4) ---" @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-native-rust.ss @echo "--- Rust native library tests (weeks 5-6) ---" - @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-native-rust-week5-6.ss + @if [ "$(UNAME_S)" = "Linux" ]; then \ + $(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-native-rust-week5-6.ss; \ + else \ + echo "SKIP: native weeks 5-6 suite requires Linux (host: $(UNAME_S))"; \ + fi audit-native: @set -eu; \ @@ -1712,7 +1780,11 @@ docker: docker-build docker-build: @echo "=== Building $(DOCKER_IMAGE) base image ===" - docker build --platform linux/amd64 -t $(DOCKER_IMAGE) . + @support/container-inputs.sh check + @base=$$(support/container-inputs.sh field ubuntu-24.04 3); \ + docker build --platform linux/amd64 \ + --build-arg JERBOA_BASE_IMAGE="$$base" \ + -t $(DOCKER_IMAGE) . @echo "" @docker images $(DOCKER_IMAGE) --format "Image: {{.Repository}}:{{.Tag}} Size: {{.Size}}" --- a/benchmarks/bench-ac-alloc.ss +++ b/benchmarks/bench-ac-alloc.ss @@ -12,12 +12,6 @@ ;;; - Compare ac-search-fold (no list cons) vs ac-search (list cons) ;;; - Report bytes allocated per byte scanned -(or (guard (_ [#t #f]) (load-shared-object "libc.so.7") #t) - (guard (_ [#t #f]) (load-shared-object "libc.so.6") #t) - (guard (_ [#t #f]) (load-shared-object "libc.so") #t) - (guard (_ [#t #f]) (load-shared-object "libSystem.dylib") #t) - (guard (_ [#t #f]) (load-shared-object "libSystem.B.dylib") #t)) - ;; CRITICAL: optimize-level 3 is required for the AC inner loop to compile ;; to a tight zero-allocation loop. At the default level, the same code ;; allocates ~256 bytes per byte scanned (a 1000x penalty on throughput). @@ -25,7 +19,13 @@ (compile-imported-libraries #t) (import (scheme) - (std text aho-corasick) + (only (std native-loader) native-loader-ensure-libc-symbol!)) + +;; Make libc available through the fixed-path loader policy before importing +;; the measured libraries; never delegate this benchmark to a bare-name search. +(native-loader-ensure-libc-symbol! 'benchmarks/bench-ac-alloc "getrusage") + +(import (std text aho-corasick) (std runtime gc)) ;; ---- Test data ---- --- a/benchmarks/bench-gc-tuning.ss +++ b/benchmarks/bench-gc-tuning.ss @@ -13,15 +13,15 @@ ;;; ;;; Reports wall + GC count + collector CPU for each (trip, radix) tuple. -;; ---- libc preamble ---- -(or (guard (_ [#t #f]) (load-shared-object "libc.so.7") #t) - (guard (_ [#t #f]) (load-shared-object "libc.so.6") #t) - (guard (_ [#t #f]) (load-shared-object "libc.so") #t) - (guard (_ [#t #f]) (load-shared-object "libSystem.dylib") #t) - (guard (_ [#t #f]) (load-shared-object "libSystem.B.dylib") #t)) - (import (scheme) - (std runtime gc)) + (only (std native-loader) native-loader-ensure-libc-symbol!)) + +;; Make libc available in this script's consuming context without a bare-name +;; dynamic-loader search. Import workload libraries only after this step. +(native-loader-ensure-libc-symbol! + 'benchmarks/bench-gc-tuning "getrusage") + +(import (std runtime gc)) ;; ---- Synthetic scanner workload ---- ;; --- a/benchmarks/bench-mmap.ss +++ b/benchmarks/bench-mmap.ss @@ -16,15 +16,15 @@ ;;; Wrap with `/usr/bin/time -l` (macOS) or `/usr/bin/time -v` (Linux) ;;; for peak RSS. We also print Chez's gc-stats deltas inline. -;; ---- libc preamble (mmap/munmap symbols, see tests/test-mmap.ss) ---- -(or (guard (_ [#t #f]) (load-shared-object "libc.so.7") #t) - (guard (_ [#t #f]) (load-shared-object "libc.so.6") #t) - (guard (_ [#t #f]) (load-shared-object "libc.so") #t) - (guard (_ [#t #f]) (load-shared-object "libSystem.dylib") #t) - (guard (_ [#t #f]) (load-shared-object "libSystem.B.dylib") #t)) - (import (scheme) - (std mmap) + (only (std native-loader) native-loader-ensure-libc-symbol!)) + +;; (std mmap) resolves its libc FFI entries while it initializes. Load only +;; from the fixed system-library policy, in this script's consuming context, +;; before importing that library. +(native-loader-ensure-libc-symbol! 'benchmarks/bench-mmap "mmap") + +(import (std mmap) (std runtime gc)) ;; ---- Corpus walk ---- @@ -34,14 +34,14 @@ ;; (std os file-info) which would pull more libraries into the bench. (define (safe-port-length path) - (guard (_ [#t #f]) + (guard (_ [#t #f]) ; jerboa-security: suppress catch-all-guard-masks-errors -- corpus probing intentionally maps directories, special files, and unreadable entries to the documented #f skip sentinel (call-with-port (open-file-input-port path) (lambda (p) (let ([n (file-length p)]) (and (integer? n) (> n 0) n)))))) (define (list-files dir) - (let ([entries (guard (_ [#t '()]) (directory-list dir))]) + (let ([entries (directory-list dir)]) (let loop ([es entries] [acc '()] [sizes '()]) (cond [(null? es) (values acc sizes)] --- a/data/anti-patterns.sexp +++ b/data/anti-patterns.sexp @@ -1,5 +1,655 @@ ((("advice" . + "Pin a reviewed digest in repository-controlled source or verify a signature against a separately pinned trusted key; then validate the archive before extraction.") + ("avoid" + . + "Do not treat a checksum downloaded from the same origin and release path as independent authenticity evidence.") + ("id" . "cohosted-archive-checksum-authentication") + ("kinds" "security" "script") + ("pattern" + . + "(sum_url|checksum_url).*(archive|url)|download.*\\\\.sha256") + ("severity" . "high") + ("tags" "supply-chain" "bootstrap" "sha256" "download" + "provenance" "archive") + ("title" + . + "Do not authenticate downloads with a co-hosted checksum") + ("tools" "jerboa_security_scan" "sha256sum" "git diff")) + (("advice" + . + "Build the locked native crate first, derive the complete FFI symbol manifest from that archive, clear the configured object directory, and perform one Jerbuild link. Re-run bit-for-bit reproducibility evidence.") + ("avoid" + . + "Do not perform a preliminary Jerbuild link before generating an FFI manifest when the native archive can be built directly.") + ("id" . "preliminary-jerbuild-link-for-ffi-manifest") + ("kinds" "script" "ffi" "security") + ("pattern" + . + "jerbuild build[\\\\s\\\\S]*gen-ffi-symbols[\\\\s\\\\S]*jerbuild build") + ("severity" . "medium") + ("tags" "jerbuild" "ffi-symbols" "rust" "reproducibility" + "wpo" "build") + ("title" + . + "Do not link twice just to discover native FFI symbols") + ("tools" + "cargo build --locked" + "nm" + "jerbuild build" + "reproducibility-report")) + (("advice" + . + "Use let* or explicit nested calls so old/current/admin material is consumed before new/reset material in the documented order; add a two-line same-fd regression test.") + ("avoid" + . + "Do not depend on the unspecified evaluation order of plain let initializers when consuming multiple newline-framed secrets from one descriptor.") + ("id" . "parallel-let-secret-fd-reads") + ("kinds" "security" "module" "ffi") + ("pattern" + . + "\\\\(let \\\\(\\\\[[^]]*read-secret-from-fd[^]]*\\\\][\\\\s\\\\S]*\\\\[[^]]*read-secret-from-fd") + ("severity" . "high") + ("tags" "scheme" "secret-input" "file-descriptor" "framing" + "let-star" "evaluation-order") + ("title" + . + "Do not frame sequential fd secrets with plain let") + ("tools" + "jerboa_compile_check" + "jerboa_security_scan" + "fd framing regression")) + (("advice" + . + "Carry the exact policy-approved numeric address into TCP connect while retaining the original hostname only for Host, TLS SNI, and certificate verification; repeat this independently for every redirect hop.") + ("avoid" + . + "Do not approve a hostname's resolved address and then pass the hostname to a connection API that resolves it again.") + ("id" . "dns-policy-double-resolution") + ("kinds" "security" "module" "ffi") + ("pattern" + . + "validate.*(host|url).*(http-fetch|connect).*host") + ("severity" . "high") + ("tags" "dns-rebinding" "ssrf" "http" "tls" + "connect-address" "sni") + ("title" + . + "Do not validate DNS and then reconnect by hostname") + ("tools" + "jerboa_security_scan" + "tests/test-net-policy.ss" + "live pinned-connect smoke")) + (("advice" + . + "Select any explicitly permitted development fallback once at trusted startup. In production/default mode propagate every sandbox availability and input failure as a bounded error without reparsing.") + ("avoid" + . + "Do not catch input-dependent sandbox limits, traps, fuel exhaustion, or output overflow and reparse the same hostile bytes in-process.") + ("id" . "sandbox-rejection-host-fallback") + ("kinds" "security" "module" "parser") + ("pattern" . "(catch|with-catch).*parse.*sandbox.*parse") + ("severity" . "high") + ("tags" "wasm" "sandbox" "parser" "fallback" "fail-closed" + "hostile-input") + ("title" + . + "Do not turn sandbox rejection into host-parser fallback") + ("tools" + "jerboa_security_scan" + "sandbox fuzz regression" + "fail-closed subprocess test")) + (("advice" + . + "Use a bounded instance pool or one mutex covering the complete reset/write/call/read transaction. Stress with interleaved unique canaries to detect leakage as well as crashes.") + ("avoid" + . + "Do not let concurrent threads reset, write, execute, or read one cached WASM instance independently.") + ("id" . "shared-mutable-wasm-transaction") + ("kinds" "security" "module" "ffi") + ("pattern" + . + "(%instance|wasm.*instance).*(reset|memory-write|call|memory-read)") + ("severity" . "high") + ("tags" "wasm" "concurrency" "mutex" "linear-memory" "race" + "isolation") + ("title" + . + "Do not share a mutable WASM instance without transaction locking") + ("tools" + "jerboa_security_scan" + "parallel canary stress" + "thread soak")) + (("advice" + . + "Enforce Content-Length, decoded chunk/EOF byte ceilings, header limits, and one absolute deadline in the socket read path. Keep raw byte, parser output, and presentation character caps separate.") + ("avoid" + . + "Do not buffer and parse an entire response before applying a character/output cap.") + ("id" . "post-download-output-only-limit") + ("kinds" "security" "module") + ("pattern" + . + "(http-body|read.*body).*parse.*(truncate|max_chars)") + ("severity" . "high") + ("tags" "http" "resource-exhaustion" "body-limit" + "content-length" "chunked" "deadline") + ("title" + . + "Do not apply output limits after an unbounded download") + ("tools" + "jerboa_security_scan" + "hostile framing server" + "timeout regression")) + (("advice" + . + "Resolve the tool to one repository-controlled, version-marked path; authenticate its archive with a consumer-owned digest; verify the executable after installation; refresh native-library links from that exact installation; and run two consecutive post-warm reproducibility measurements.") + ("avoid" + . + "Do not declare an exact tool version while silently preferring a sibling checkout, PATH binary, unversioned cache entry, or stale native-library symlink. The selected executable and linked native graph can then differ across machines or consecutive builds.") + ("id" . "declared-toolchain-pin-with-ambient-fallback") + ("kinds" "build" "release" "supply-chain") + ("pattern" + . + "(\\.\\./[^ ]*/bin/jerbuild|command -v[[:space:]]+jerbuild|which[[:space:]]+jerbuild|ln[[:space:]]+-s[f]?[^\n]*(cache|native))") + ("severity" . "high") + ("tags" "reproducibility" "toolchain" "jerbuild" + "supply-chain" "ambient-state" "native-ffi") + ("title" + . + "Do not claim a pinned toolchain while resolving ambient binaries") + ("tools" + "jerboa_security_scan" + "reproducibility-report" + "sha256sum" + "git diff")) + (("advice" + . + "For pre-commit review evidence, enumerate git ls-files --cached --others --exclude-standard, hash that exact set, and separately record dirty status. For a release, require a clean committed snapshot and regenerate evidence there.") + ("avoid" + . + "Do not generate SBOM or reproducibility evidence from git ls-files alone when the reviewed fixes include untracked working-tree files.") + ("id" . "tracked-only-release-evidence") + ("kinds" "security" "script" "docs") + ("pattern" . "git ls-files(?!.*--others)") + ("severity" . "high") + ("tags" "release-evidence" "git" "untracked" "sbom" + "reproducibility") + ("title" + . + "Do not hash only tracked files from a dirty review tree") + ("tools" + "git status --short" + "git ls-files --cached --others --exclude-standard" + "git diff --check")) + (("advice" + . + "Keep the credential in the current process and send the request with an in-process HTTP client, or use a protected descriptor/config mechanism whose contents are not present in argv. Keep URLs, logs, and error output redacted.") + ("avoid" + . + "Do not call curl with a secret-expanded Authorization header and assume the credential is no longer exposed merely because it left the URL.") + ("id" . "credential-header-in-child-argv") + ("kinds" "security" "script") + ("pattern" + . + "curl.*(--header|-H).*(Authorization|Bearer|API[_-]?KEY)") + ("severity" . "high") + ("tags" "credentials" "curl" "argv" "authorization" + "process-list") + ("title" + . + "Do not move URL credentials into a child process argument") + ("tools" + "jerboa_security_scan" + "process argv regression" + "secret log scan")) + (("advice" + . + "Make build/install/publication targets fail closed, keep only dependency-free archive/security regressions in hosted CI, and record live runtime and target evidence as blocked-retired. Revival must be an explicit reviewed change.") + ("avoid" + . + "Do not treat archive documentation as a security boundary while run, binary, install, cross-build, or publish targets remain operational.") + ("id" . "retired-but-runnable-archive") + ("kinds" "security" "script" "docs") + ("pattern" + . + "(D/archive|retired).*(make run|binary|install|publish)") + ("severity" . "high") + ("tags" "archive" "retired" "build" "publish" "fail-closed") + ("title" + . + "Do not label a project retired while leaving publication paths live") + ("tools" + "make security" + "archive regression test" + "release evidence audit")) + (("advice" + . + "Link the repository-pinned real native static archive (for Jerbuild, enable `(static-native #t)` or pass the exact archive), derive/register its exported FFI symbols, and remove overlapping placeholder stubs to avoid duplicate symbols. Keep stubs only for truly optional unreachable APIs, behind an explicit build-mode guard. Run a feature smoke as well as `--version` and the static symbol audit.") + ("avoid" + . + "Do not make a standalone release binary link by registering placeholder C functions that merely return failure for native APIs the runtime actually imports. The build and shallow smoke may pass while every affected runtime feature fails.") + ("id" . "fail-only-native-stubs-in-release-binary") + ("kinds" "ffi" "build" "release") + ("pattern" + . + "static-native|libjerboa_native|stub|Sforeign_symbol") + ("severity" . "high") + ("tags" "ffi" "static-link" "jerbuild" "release" + "native-archive" "stubs") + ("title" + . + "Do not satisfy release FFI linkage with fail-only native stubs") + ("tools" "nm" "jerbuild check" "jerbuild build" + "static symbol audit" "runtime feature smoke")) + (("advice" + . + "Have the authenticating updater stage the artifact privately, verify it before parsing, and atomically publish owner-only artifact plus metadata that binds a cryptographic digest, version, signer, build time, and policy level. On every load, recheck type/owner/mode/no-follow ancestry, recompute the digest, enforce rollback/future/freshness policy, and fail closed on any mismatch.") + ("avoid" + . + "Do not auto-load a cached feed, ruleset, model, or database merely because a sidecar says a trusted updater verified it. A stale, swapped, symlinked, or attacker-written artifact can inherit that trust label.") + ("id" . "cache-verifier-sidecar-without-artifact-binding") + ("kinds" "security") + ("pattern" + . + "verified[_-]?by|verification[_-]?status|trusted[_-]?cache") + ("severity" . "high") + ("tags" "updater" "cache" "provenance" "digest" "rollback" + "sidecar") + ("title" + . + "Do not trust updater provenance metadata unless it binds the exact artifact") + ("tools" + "jerboa_security_scan" + "sha256sum" + "symlink-race regression" + "rollback/future/tamper tests")) + (("advice" + . + "Use a launcher that validates policy maxima, establishes every required kernel/supervisor limit before exporting the marker, verifies the effective limits, and exits without the marker when any host limit is unsupported. Treat the marker as internal attestation and still enforce engine-local match/depth/output budgets.") + ("avoid" + . + "Do not let a caller-controlled environment variable alone authorize backtracking regex, native parsers, or other high-risk work. The marker can be set without the CPU, memory, output, descriptor, and wall limits its name implies.") + ("id" . "unverified-isolation-environment-marker") + ("kinds" "security" "script") + ("pattern" + . + "(ISOLATED|SANDBOXED|BOUNDED)_WORKER\\s*=\\s*1") + ("severity" . "high") + ("tags" "sandbox" "resource-limits" "regex" "worker" + "environment" "fail-closed") + ("title" + . + "Do not enable unsafe parsing from an unverified isolation marker") + ("tools" + "jerboa_security_scan" + "ulimit/prlimit regression" + "unsupported-host fail-closed test" + "cat /proc/self/limits")) + (("advice" + . + "Use fixed workers and bounded admission, then record an absolute accept/handshake/frame deadline and check it after every chunk. Enforce global and per-source active/rate caps, and test with a byte-drip client whose interval stays below the socket timeout but whose total frame time exceeds the absolute deadline.") + ("avoid" + . + "Relying only on a receive/read timeout for a length-prefixed or authenticated frame. A peer can send one byte before each timeout and reset the per-syscall timer indefinitely, occupying a connection or worker.") + ("id" . "per-syscall-timeout-slowloris") + ("kinds" "security" "network-server" "denial-of-service") + ("pattern" + . + "SO_RCVTIMEO|set-socket-timeout|socket-timeout") + ("severity" . "high") + ("tags" "slowloris" "socket-timeout" "absolute-deadline" + "framing" "worker-pool") + ("title" + . + "Per-syscall socket timeouts do not bound whole-frame time") + ("tools" + "jerboa_security_scan" + "adversarial byte-drip integration test")) + (("advice" + . + "Delete only an explicit generated-output directory or manifest after validating that the path is the expected non-symlink build root. If the quarantined/archive project has no supported build, make clean a no-op. Regress it in a disposable fixture containing source, artifacts, symlinks, and an outside sentinel, and compare source hashes before and after.") + ("avoid" + . + "Do not make a clean target recursively select source-code extensions or pipe repository-wide find output into rm. In a dirty or non-versioned workspace this can irrecoverably destroy the only source corpus.") + ("id" . "clean-target-source-glob-destruction") + ("kinds" "all" "script" "security" "test") + ("pattern" + . + "clean\\s*:([\\s\\S]*?)(find[^\\n]*(?:[.]ss|[.]scm|[.]sls|[.]py|[.]js)[^\\n]*(?:rm|delete)|rm[^\\n]*(?:[*][.]ss|[*][.]scm|[*][.]sls))") + ("severity" . "high") + ("tags" "makefile" "clean" "source-files" + "destructive-delete" "quarantine" "non-versioned") + ("title" + . + "Do not implement clean by recursively deleting source extensions") + ("tools" + "make -n" + "disposable fixture regression" + "shasum" + "git diff --check")) + (("advice" + . + "Carry the validated numeric bind address through every facade layer, make loopback the backend default, fail closed on unsupported addresses, query the kernel-confirmed endpoint after bind, log that endpoint, and test default refusal from a non-loopback interface plus explicit wildcard opt-in.") + ("avoid" + . + "Do not parse or document a loopback bind setting and then call a lower-layer server constructor without forwarding it; the backend may default to a wildcard listener and expose development routes remotely.") + ("id" . "configured-bind-dropped-by-facade") + ("kinds" "security" "module" "test") + ("pattern" + . + "(?s)(bind|address).*(127\\.0\\.0\\.1|loopback).*httpd-start\\s+[^\\n)]*port\\s+[^\\n)]*handler") + ("severity" . "high") + ("tags" "httpd" "bind" "loopback" "facade" "listener" + "exposure") + ("title" + . + "Do not advertise a bind address that the listener facade drops") + ("tools"