Security hardening and release readiness
ober
e2447741f19b74978b9d304945f05ac3f7604bfe
new file mode 100644 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: CI + +on: + push: + branches: [main, master] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + build-test-audit: + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + + - name: Install system dependencies + run: | + set -eu + sudo apt-get update + sudo apt-get install -y build-essential git curl ca-certificates ripgrep pkg-config libncurses-dev uuid-dev + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install cargo-audit + run: cargo install cargo-audit --locked + + - name: Build and test + run: make test + + - name: Security audit + run: make audit new file mode 100644 --- /dev/null +++ b/.github/workflows/security-baseline.yml @@ -0,0 +1,35 @@ +name: Security Baseline + +on: + push: + branches: [main, master] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + baseline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Required release files + run: | + set -eu + test -f LICENSE + test -f SECURITY.md + test -f .gitignore + find . -maxdepth 1 -iname "README*" -type f | grep -q . + + - name: High-confidence secret scan + run: | + set -eu + pattern="(BEGIN (RSA|OPENSSH|EC|DSA|PRIVATE) KEY|ghp_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|sk-(ant-api03|proj|svcacct)-[A-Za-z0-9_-]{30,}|AKIA[0-9A-Z]{16})" + matches="$(git grep -n -I -E "$pattern" -- . ":!*.png" ":!*.jpg" ":!*.jpeg" ":!*.gif" ":!*.so" ":!*.dylib" ":!*.o" ":!*.a" ":!*.boot" ":!*.tar.gz" || true)" + if [ -n "$matches" ]; then + echo "$matches" + echo "High-confidence secret pattern found." + exit 1 + fi --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ jcode.json /support/sqlite-bundled/target/ /jcode_tui_shim.dylib /jcode_tui_shim.so +/dist/ # Android build outputs /android/android.jar @@ -24,7 +25,8 @@ jcode.json # Runtime data (checkpoints, security cache, logs) /.jcode/ -/.jerboa/ +/.jerboa/* +!/.jerboa/security.json /jcode.log /jcode.trace /.claude/ new file mode 100644 --- /dev/null +++ b/.jerboa/security.json @@ -0,0 +1,41 @@ +{ + "repo": "jerboa-code", + "classification": "C-private-until-target-evidence", + "license": "Apache-2.0", + "securityGates": { + "requiredFiles": [ + "LICENSE", + "SECURITY.md", + "docs/threat-model.md", + "docs/credential-handling.md", + "docs/ffi-boundary.md", + "docs/release-evidence.md" + ], + "localVerification": [ + "make test", + "make audit", + "make release-evidence" + ], + "targetEvidence": { + "runtimeProof": "marker-validated-fail-closed", + "fuzzSanitizerProof": "marker-validated-fail-closed", + "cleanHostProof": "marker-validated-fail-closed" + } + }, + "sensitiveSurfaces": [ + "filesystem writes", + "shell and git subprocesses", + "external provider CLIs", + "MCP and LSP subprocesses", + "remote serve and relay endpoints", + "debug REPL socket", + "native Rust and C FFI" + ], + "releaseBlockers": [ + "hosted CI evidence", + "clean-host/native-clean WPO reproducibility policy", + "release-host fuzz and sanitizer evidence for sandbox/MCP/LSP/remote surfaces", + "external CLI/MCP/provider policy review", + "external security review" + ] +} new file mode 100644 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # jerbuild is the self-contained build tool: it bundles Chez Scheme + the # jerboa stdlib under ~/.cache/jerbuild/<sha>/, so the host dev loop and the # native `binary` need NO jerboa source checkout and NO separately-built Chez. -JERBOA_VERSION ?= v0.2.0 +JERBOA_VERSION ?= v0.2.3 JERBOA_TOOL_DIR ?= $(CURDIR)/.jerboa/bin JERBUILD ?= $(shell if [ -x ./jerbuild ]; then echo ./jerbuild; \ elif [ -x "$(JERBOA_TOOL_DIR)/jerbuild" ]; then echo "$(JERBOA_TOOL_DIR)/jerbuild"; \ @@ -19,6 +19,10 @@ JSQLITE_LIBDIR ?= $(JSQLITE_DIR)/src LIBDIRS = --libdirs ./lib:$(JSQLITE_LIBDIR):vendor/jerboa-websearch/src:$(JH)/lib JEXEC = $(JERBUILD) exec $(LIBDIRS) +SBOM_DIR ?= dist/sbom +REPRO_DIR ?= dist/reproducibility +TARGET_EVIDENCE_DIR ?= dist/target-evidence +RELEASE_EVIDENCE_DIR ?= dist/release-evidence # Library paths for FFI shared objects (macOS: dylib, Linux: so). The native # Rust lib now lives in ./lib (dropped by the native-rs target), matching the @@ -49,7 +53,7 @@ else JCODE_OS_LIBS := -lm -ldl -lpthread -luuid -lncurses -lstdc++ endif -.PHONY: all help ensure-jerboa-tools build gen run test test-providers local-eval clean repl binary install tui-shim run-tui native-rs linux linux-check linux-amd64 linux-arm64 jcode-linux-amd64 jcode-linux-arm64 test-linux test-linux-amd64 freebsd freebsd-amd64 jcode-freebsd-amd64 purge-stale android android-clean vendor-deps vendor-clean +.PHONY: all help ensure-jerboa-tools build gen run test audit security-audit sbom target-evidence reproducibility-report release-evidence test-providers local-eval clean repl binary install tui-shim run-tui native-rs linux linux-check linux-amd64 linux-arm64 jcode-linux-amd64 jcode-linux-arm64 test-linux test-linux-amd64 freebsd freebsd-amd64 jcode-freebsd-amd64 purge-stale android android-clean vendor-deps vendor-clean all: help @@ -58,6 +62,11 @@ help: @echo " ensure-jerboa-tools Ensure project-local jerboa/jerbuild are available" @echo " build Compile src/ → lib/ and pre-compile imports (jerbuild)" @echo " test Run test suite (test/run.ss)" + @echo " audit Run release security audit checks" + @echo " sbom Write SBOM/provenance evidence to $(SBOM_DIR)" + @echo " target-evidence Record target proof status for sandbox/MCP/LSP/remote/fuzz/clean-host gates" + @echo " reproducibility-report Compare two clean binary builds" + @echo " release-evidence Write test/audit/SBOM/reproducibility bundle" @echo " test-providers Live 'say hello' smoke test per configured provider" @echo " local-eval Run live local-model verified eval (PROVIDER=mlx2 MODEL=/path/model)" @echo " run Start interactive agent REPL" @@ -227,6 +236,57 @@ test: build DYLD_LIBRARY_PATH=$(LDPATH) LD_LIBRARY_PATH=$(LDPATH) \ $(JEXEC) test/run.ss +audit: security-audit + +security-audit: + sh tools/security-audit.sh + +sbom: binary + JERBUILD="$(JERBUILD)" JSQLITE_DIR="$(JSQLITE_DIR)" JSQLITE_LIBDIR="$(JSQLITE_LIBDIR)" \ + scripts/sbom.sh "$(SBOM_DIR)" + +target-evidence: + JCODE_TARGET_RUNTIME_PROOF_FILE="$(JCODE_TARGET_RUNTIME_PROOF_FILE)" \ + JCODE_TARGET_FUZZ_PROOF_FILE="$(JCODE_TARGET_FUZZ_PROOF_FILE)" \ + JCODE_TARGET_CLEAN_HOST_PROOF_FILE="$(JCODE_TARGET_CLEAN_HOST_PROOF_FILE)" \ + JCODE_REQUIRE_TARGET_RUNTIME_PROOF="$(JCODE_REQUIRE_TARGET_RUNTIME_PROOF)" \ + JCODE_REQUIRE_TARGET_FUZZ_PROOF="$(JCODE_REQUIRE_TARGET_FUZZ_PROOF)" \ + JCODE_REQUIRE_TARGET_CLEAN_HOST_PROOF="$(JCODE_REQUIRE_TARGET_CLEAN_HOST_PROOF)" \ + scripts/target-evidence.sh "$(TARGET_EVIDENCE_DIR)" + +reproducibility-report: + JERBUILD="$(JERBUILD)" JSQLITE_DIR="$(JSQLITE_DIR)" JSQLITE_LIBDIR="$(JSQLITE_LIBDIR)" \ + MAKE="$(MAKE)" scripts/reproducibility-report.sh "$(REPRO_DIR)" + +release-evidence: + rm -rf "$(RELEASE_EVIDENCE_DIR)" + mkdir -p "$(RELEASE_EVIDENCE_DIR)" + $(MAKE) test > "$(RELEASE_EVIDENCE_DIR)/test.log" 2>&1 + $(MAKE) audit > "$(RELEASE_EVIDENCE_DIR)/audit.log" 2>&1 + $(MAKE) reproducibility-report > "$(RELEASE_EVIDENCE_DIR)/reproducibility-report.log" 2>&1 + $(MAKE) sbom > "$(RELEASE_EVIDENCE_DIR)/sbom.log" 2>&1 + $(MAKE) target-evidence > "$(RELEASE_EVIDENCE_DIR)/target-evidence.log" 2>&1 + rm -rf "$(RELEASE_EVIDENCE_DIR)/sbom" "$(RELEASE_EVIDENCE_DIR)/reproducibility" "$(RELEASE_EVIDENCE_DIR)/target-evidence" + cp -R "$(SBOM_DIR)" "$(RELEASE_EVIDENCE_DIR)/sbom" + cp -R "$(REPRO_DIR)" "$(RELEASE_EVIDENCE_DIR)/reproducibility" + cp -R "$(TARGET_EVIDENCE_DIR)" "$(RELEASE_EVIDENCE_DIR)/target-evidence" + git rev-parse HEAD > "$(RELEASE_EVIDENCE_DIR)/git-commit.txt" 2>/dev/null || true + git status --short > "$(RELEASE_EVIDENCE_DIR)/git-status.txt" 2>/dev/null || true + git diff --stat > "$(RELEASE_EVIDENCE_DIR)/diff-stat.txt" 2>/dev/null || true + uname -a > "$(RELEASE_EVIDENCE_DIR)/build-environment.txt" + "$(JERBUILD)" --version >> "$(RELEASE_EVIDENCE_DIR)/build-environment.txt" + "$(JERBUILD)" --jerboa-home >> "$(RELEASE_EVIDENCE_DIR)/build-environment.txt" + (cc --version || cc -v) > "$(RELEASE_EVIDENCE_DIR)/cc-version.txt" 2>&1 || true + (rustc --version && cargo --version) > "$(RELEASE_EVIDENCE_DIR)/rust-version.txt" 2>&1 || true + ./jcode --version > "$(RELEASE_EVIDENCE_DIR)/binary-version.txt" 2>&1 + shasum -a 256 jcode > "$(RELEASE_EVIDENCE_DIR)/binary-sha256.txt" + find Makefile .jerbuild .jerboa/security.json main.ss main-binary.ss src support test docs tools .github \ + -type f \( -name 'Makefile' -o -name '*.ss' -o -name '*.sls' -o -name '*.c' -o -name '*.h' -o -name '*.list' -o -name '*.sh' -o -name '*.json' -o -name '*.md' -o -name '*.yml' -o -name '*.yaml' \) \ + -print 2>/dev/null | LC_ALL=C sort | xargs shasum -a 256 > "$(RELEASE_EVIDENCE_DIR)/release-inputs.sha256" + grep -q '^sbom_status=present$$' "$(RELEASE_EVIDENCE_DIR)/sbom/manifest.txt" + grep -q '^status=match$$' "$(RELEASE_EVIDENCE_DIR)/reproducibility/result.txt" + grep -Eq '^(status=local-status-recorded|status=target-proof-recorded-review-required)$$' "$(RELEASE_EVIDENCE_DIR)/target-evidence/status.txt" + # Live smoke test — one round-trip per provider with a configured key. # Providers without a key are SKIPped, not failed. Excluded from `test` # because it makes real network calls. --- a/README.md +++ b/README.md @@ -75,13 +75,18 @@ make binary # produce the standalone ./jcode | **[TUI](docs/tui.md)** | Layout, keybindings, themes, and the rendering features. | | **[Forge & ATLAS reliability](docs/FORGE.md)** | Guardrails, workflows, verify-gate, best-of-k, the proxy, and the eval harness. | | **[Remote & Android](docs/remote.md)** | `serve` / `relay` / `connect` and the thin-client architecture. | +| **[Threat model](docs/threat-model.md)** | Assets, trust boundaries, controls, and residual risks for production use. | +| **[Credential handling](docs/credential-handling.md)** | API key sources, storage, logging, tracing, and release checks. | +| **[FFI boundary](docs/ffi-boundary.md)** | Native components, ownership rules, generated artifacts, and audit procedure. | +| **[Release evidence](docs/release-evidence.md)** | SBOM, reproducibility, binary smoke, and release-bundle contents. | See **[docs/](docs/)** for the full index. ## Build targets -`make build` · `test` · `run` · `run-tui` · `binary` · `install` · -`linux` · `linux-amd64` · `linux-arm64` · `freebsd` · `android`. +`make build` · `test` · `audit` · `sbom` · `reproducibility-report` · +`release-evidence` · `run` · `run-tui` · `binary` · `install` · `linux` · +`linux-amd64` · `linux-arm64` · `freebsd` · `android`. Run `make help` for the complete list. ## Layout new file mode 100644 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,69 @@ +# Security Policy + +`jerboa-code` is a local AI coding agent with deliberate access to project +files, subprocesses, provider APIs, MCP servers, and optional remote-control +ports. Treat it as security-sensitive software even when running on a personal +workstation. + +Production claims require the release gates tracked in `~/Release-plan.md` and +`~/mine/jerboa-production-readiness.md` to be complete for this repository. + +## Supported Status + +No public production-support commitment exists yet. Security-sensitive releases +must be cut from a clean checkout after: + +- `git status --short` shows only intentional release changes. +- `make test` passes. +- `make audit` passes. +- `make sbom` and `make reproducibility-report` record reviewed release + evidence. +- `make release-evidence` passes and archives the test, audit, SBOM, + reproducibility, target-evidence, binary-smoke, and toolchain outputs. +- The Jerboa high-severity security scanner reports no findings. +- Dependency and native-code audit gaps are documented. +- File I/O, shell execution, network access, FFI ownership, remote tokens, and + credential storage are documented. + +## Security Boundaries + +- `jcode` is not a sandbox boundary by itself. The operating-system sandbox, + Jerboa permissions, write scopes, and user approval flow are the enforcement + layers. +- The `bash`, `git`, `apply_patch`, LSP, MCP, and external-LLM integrations + execute local programs. Treat configured commands and MCP servers as trusted + code with the user's account privileges. +- Provider prompts, tool arguments, tool results, and trace logs can contain + sensitive project content. Normal logs redact known secret shapes and no + longer record full prompt or shell command bodies, but explicit trace output + is still a sensitive diagnostic artifact. +- Remote `serve`, `relay`, and debug REPL endpoints require token handling and + bind-address review before use off-host. + +## Release Gates + +- Required files: `LICENSE`, `SECURITY.md`, `.gitignore`, `README.md`, + `docs/threat-model.md`, `docs/credential-handling.md`, + `docs/ffi-boundary.md`, CI workflows, and this audit policy. +- Local verification: `make test && make audit && make release-evidence`. +- Secret scanning: high-confidence working-tree scan and history review before + first public release. +- SBOM/provenance: release evidence records the Jerboa toolchain, generated + binary inputs, vendored native Rust crate, termbox2, websearch, jsqlite, and + native shim artifacts. +- Reproducibility: release evidence compares two clean project binary builds + and requires a no-network `./jcode --version` smoke to pass. +- Target proof: sandbox/MCP/LSP/remote runtime review, release-host + fuzz/sanitizer evidence, and clean-host/native-clean WPO reproducibility + proof are marker-validated and fail closed when required proof is missing or + incomplete. +- Native code: review the Rust `jerboa-native-rs` dependency set, C shims, FFI + lifetimes, allocation/freeing rules, and cross-build generated artifacts. +- Daemon-like features: remote server, relay, and debug REPL must retain token + auth, restrictive file permissions, and documented bind-address behavior. + +## Reporting + +Before public release, report issues privately to the repository owner. After +public release, replace this section with a dedicated advisory contact, +supported versions, and disclosure window. --- a/build-jcode-cross.ss +++ b/build-jcode-cross.ss @@ -19,7 +19,21 @@ ;;; bundled into one .so via compile-whole-program, loaded at runtime via ;;; Sscheme_program — no jcode.boot bundling required. -(import (chezscheme)) +(import (scheme) + (only (chezscheme) + compile-imported-libraries + compile-program + compile-whole-program + file-options + generate-wpo-files + get-bytevector-all + library-directories + load + native-transcoder + open-file-input-port + open-file-output-port + open-process-ports + system)) ;; ── Params ────────────────────────────────────────────────────────────────── (define jerboa-home @@ -60,6 +74,14 @@ ;; or jerboa-native sqlite feature is linked here. (define cargo-features "tls,crypto") +(define (write-text-file! path proc) + (call-with-port + (open-file-output-port path + (file-options no-fail) + (buffer-mode block) + (native-transcoder)) + proc)) + (define jerboa-native-a (or (getenv "JERBOA_NATIVE_A") (format "~a/jerboa-native-rs/target/~a/release/libjerboa_native.a" @@ -154,12 +176,27 @@ (close-port from-stderr) (if (or (eof-object? line) (zero? (string-length line))) #f line))))) +(define (string-has-nul? s) + (let ([n (string-length s)]) + (let loop ([i 0]) + (cond + [(>= i n) #f] + [(char=? (string-ref s i) #\nul) #t] + [else (loop (+ i 1))])))) + +(define (check-untainted-shell-command cmd) + (unless (and (string? cmd) + (> (string-length cmd) 0) + (not (string-has-nul? cmd))) + (error 'build-jcode-cross "invalid shell command" cmd)) + cmd) + (define rustup-rustc-path (capture-line "rustup which rustc 2>/dev/null")) (define rustup-bin-dir (and rustup-rustc-path (path-dirname rustup-rustc-path))) (define (try-cargo-build cmd) (printf " $ ~a~n" cmd) - (zero? (system cmd))) + (zero? (system (check-untainted-shell-command cmd)))) (define (rebuild-native-lib!) (let* ([nrs-dir (format "~a/jerboa-native-rs" jerboa-home)] @@ -183,9 +220,8 @@ rust-target rust-target cross-cc))) (unless (file-exists? jerboa-native-a) (error 'build-jcode-cross "cargo succeeded but .a missing" jerboa-native-a)) - (call-with-output-file native-features-sentinel - (lambda (out) (display cargo-features out) (newline out)) - 'truncate))) + (write-text-file! native-features-sentinel + (lambda (out) (display cargo-features out) (newline out))))) (cond [(not (file-exists? jerboa-native-a)) @@ -217,11 +253,11 @@ (define entry-wpo (format "~a/program.wpo" obj-dir)) (define (prepare-obj-dir!) - (system (format "rm -rf '~a'" obj-dir)) - (system (format "mkdir -p '~a'" obj-dir))) + (system (check-untainted-shell-command (format "rm -rf '~a'" obj-dir))) + (system (check-untainted-shell-command (format "mkdir -p '~a'" obj-dir)))) (define (cleanup!) - (system (format "rm -rf '~a'" obj-dir))) + (system (check-untainted-shell-command (format "rm -rf '~a'" obj-dir)))) (prepare-obj-dir!) @@ -506,7 +542,7 @@ cross-csv-dir cross-csv-dir cross-csv-dir jerboa-native-a)) (printf " ~a~n" link-cmd) -(let ([rc (system link-cmd)]) +(let ([rc (system (check-untainted-shell-command link-cmd))]) (unless (zero? rc) (cleanup!) (error 'build-jcode-cross "cross-link failed" rc))) @@ -515,5 +551,5 @@ (cleanup!) (printf "~n=== Build complete: ~a ===~n" output) -(system (format "ls -lh ~a" output)) -(system (format "file ~a" output)) +(system (check-untainted-shell-command (format "ls -lh ~a" output))) +(system (check-untainted-shell-command (format "file ~a" output))) --- a/build-jcode-freebsd-cross.ss +++ b/build-jcode-freebsd-cross.ss @@ -18,7 +18,21 @@ ;;; cannot satisfy. Dynamic linking is the standard FreeBSD distribution ;;; model anyway. -(import (chezscheme)) +(import (scheme) + (only (chezscheme) + compile-imported-libraries + compile-program + compile-whole-program + file-options + generate-wpo-files + get-bytevector-all + library-directories + load + native-transcoder + open-file-input-port + open-file-output-port + open-process-ports + system)) ;; ── Params ────────────────────────────────────────────────────────────────── (define jerboa-home @@ -39,6 +53,14 @@ ;; or jerboa-native sqlite feature is linked here. (define cargo-features "tls,crypto") +(define (write-text-file! path proc) + (call-with-port + (open-file-output-port path + (file-options no-fail) + (buffer-mode block) + (native-transcoder)) + proc)) + (define jerboa-native-a (or (getenv "JERBOA_NATIVE_A") (format "~a/jerboa-native-rs/target/x86_64-unknown-freebsd/release/libjerboa_native.a" @@ -133,12 +155,27 @@ (close-port from-stderr) (if (or (eof-object? line) (zero? (string-length line))) #f line))))) +(define (string-has-nul? s) + (let ([n (string-length s)]) + (let loop ([i 0]) + (cond + [(>= i n) #f] + [(char=? (string-ref s i) #\nul) #t] + [else (loop (+ i 1))])))) + +(define (check-untainted-shell-command cmd) + (unless (and (string? cmd) + (> (string-length cmd) 0) + (not (string-has-nul? cmd))) + (error 'build-jcode-freebsd-cross "invalid shell command" cmd)) + cmd) + (define rustup-rustc-path (capture-line "rustup which rustc 2>/dev/null")) (define rustup-bin-dir (and rustup-rustc-path (path-dirname rustup-rustc-path))) (define (try-cargo-build cmd) (printf " $ ~a~n" cmd) - (zero? (system cmd))) + (zero? (system (check-untainted-shell-command cmd)))) (define freebsd-cc-env (format @@ -165,9 +202,8 @@ "/opt/homebrew/opt/llvm/bin/llvm-ar from the LLVM Homebrew formula."))) (unless (file-exists? jerboa-native-a) (error 'build-jcode-freebsd-cross "cargo succeeded but .a missing" jerboa-native-a)) - (call-with-output-file native-features-sentinel - (lambda (out) (display cargo-features out) (newline out)) - 'truncate))) + (write-text-file! native-features-sentinel + (lambda (out) (display cargo-features out) (newline out))))) (cond [(not (file-exists? jerboa-native-a)) @@ -192,11 +228,11 @@ (define entry-wpo (format "~a/program.wpo" obj-dir)) (define (prepare-obj-dir!) - (system (format "rm -rf '~a'" obj-dir)) - (system (format "mkdir -p '~a'" obj-dir))) + (system (check-untainted-shell-command (format "rm -rf '~a'" obj-dir))) + (system (check-untainted-shell-command (format "mkdir -p '~a'" obj-dir)))) (define (cleanup!) - (system (format "rm -rf '~a'" obj-dir))) + (system (check-untainted-shell-command (format "rm -rf '~a'" obj-dir)))) (prepare-obj-dir!) @@ -489,7 +525,7 @@ cross-csv-dir cross-csv-dir cross-csv-dir jerboa-native-a)) (printf " ~a~n" link-cmd) -(let ([rc (system link-cmd)]) +(let ([rc (system (check-untainted-shell-command link-cmd))]) (unless (zero? rc) (cleanup!) (error 'build-jcode-freebsd-cross "cross-link failed" rc))) @@ -498,5 +534,5 @@ (cleanup!) (printf "~n=== Build complete: ~a ===~n" output) -(system (format "ls -lh ~a" output)) -(system (format "file ~a" output)) +(system (check-untainted-shell-command (format "ls -lh ~a" output))) +(system (check-untainted-shell-command (format "file ~a" output))) --- a/build-jcode-musl.ss +++ b/build-jcode-musl.ss @@ -13,11 +13,27 @@ ;;; The resulting jcode-musl binary has zero runtime dependencies. (import - (except (chezscheme) void box box? unbox set-box! - andmap ormap iota last-pair find - 1+ 1- fx/ fx1+ fx1- - error error? raise with-exception-handler identifier? - hash-table? make-hash-table) + (scheme) + (only (chezscheme) + commonization-level + compile-imported-libraries + compile-program + cp0-effort-limit + cp0-outer-unroll-limit + cp0-score-limit + debug-level + enable-arithmetic-left-associative + enable-unsafe-application + enable-unsafe-variable-reference + file-options + generate-inspector-information + library-directories + make-boot-file + native-transcoder + open-file-output-port + open-process-ports + optimize-level + system) (jerboa build) (jerboa build musl)) @@ -45,6 +61,38 @@ ;; previous setup. (musl-chez-prefix-set! jcode-musl-chez-prefix) +(define (string-has-nul? s) + (let ([n (string-length s)]) + (let loop ([i 0]) + (cond + [(>= i n) #f] + [(char=? (string-ref s i) #\nul) #t] + [else (loop (+ i 1))])))) + +(define (check-untainted-shell-command cmd) + (unless (and (string? cmd) + (> (string-length cmd) 0) + (not (string-has-nul? cmd))) + (error 'build-jcode-musl "invalid shell command" cmd)) + cmd) + +(define (check-untainted-file-path path) + (unless (and (string? path) + (> (string-length path) 0) + (not (string=? path ".")) + (not (string=? path "..")) + (not (string-has-nul? path))) + (error 'build-jcode-musl "invalid file path" path)) + path) + +(define (write-text-file! path proc) + (call-with-port + (open-file-output-port (check-untainted-file-path path) + (file-options no-fail) + (buffer-mode block) + (native-transcoder)) + proc)) + (unless (file-directory? jcode-musl-chez-prefix) (let* ([home (or (getenv "HOME") "/root")] [chez-src (format "~a/mine/ChezScheme" home)] @@ -65,7 +113,9 @@ (exit 1)] [else (printf "This takes ~~5-10 minutes; subsequent runs reuse it.~n~n") - (let ([rc (system (format "bash '~a' '~a' '~a'" bootstrap chez-src jcode-musl-chez-prefix))]) + (let ([rc (system + (check-untainted-shell-command + (format "bash '~a' '~a' '~a'" bootstrap chez-src jcode-musl-chez-prefix)))]) (unless (= rc 0) (printf "~nFATAL: musl Chez bootstrap failed (rc=~a)~n" rc) (printf "Run manually to inspect:~n bash ~a ~a ~a~n" bootstrap chez-src jcode-musl-chez-prefix) @@ -117,11 +167,11 @@ (define program-so (format "~a/program.so" obj-dir)) (define (prepare-build-dir!) - (system (format "rm -rf '~a'" build-dir)) - (system (format "mkdir -p '~a'" obj-dir))) + (system (check-untainted-shell-command (format "rm -rf '~a'" build-dir))) + (system (check-untainted-shell-command (format "mkdir -p '~a'" obj-dir)))) (define (cleanup-build-dir!) - (system (format "rm -rf '~a'" build-dir))) + (system (check-untainted-shell-command (format "rm -rf '~a'" build-dir)))) ;; ========== Auto-discovery helpers ========== ;; @@ -364,7 +414,7 @@ ;; Generate static_boot.c: embeds petite.boot + scheme.boot + jcode.boot (define static-boot-c (format "~a/static_boot.c" build-dir)) -(call-with-output-file static-boot-c +(write-text-file! static-boot-c (lambda (out) (display "#include \"scheme.h\"\n\n" out) (display (file->c-array petite-boot-path "petite_boot") out) @@ -437,7 +487,7 @@ (define (try-cargo-build cmd) (printf " $ ~a~n" cmd) - (zero? (system cmd))) + (zero? (system (check-untainted-shell-command cmd)))) (define (rebuild-native-lib!) (let* ([nrs-dir (format "~a/jerboa-native-rs" jerboa-dir-base)] @@ -462,11 +512,10 @@ "references __memcpy_chk and libstdc++ symbols musl-gcc cannot link.") native-lib-path)) ;; Record the feature set this .a was built with. - (call-with-output-file native-features-sentinel + (write-text-file! native-features-sentinel (lambda (out) (display jcode-cargo-features out) - (newline out)) - 'truncate))) + (newline out))))) (cond [(not (file-exists? native-lib-path)) @@ -484,7 +533,7 @@ ;; Generate jcode_main_musl.c (define program-c (format "~a/jcode_main_musl.c" build-dir)) -(call-with-output-file program-c +(write-text-file! program-c (lambda (out) (display "#define _GNU_SOURCE\n" out) (display "#include <stdlib.h>\n" out) @@ -573,7 +622,7 @@ (display " close(fd);\n\n" out) (display " /* Tell jerboa stdlib libraries (std/net/*, std/os/*, std/db/*)\n" out) (display " * we are statically linked. Without JERBOA_STATIC=1 their\n" out) - (display " * library visit-time code calls (load-shared-object #f) which\n" out) + (display " * library visit-time code tries to load process-global FFI symbols,\n" out) (display " * raises in a static binary. Variable must be set BEFORE\n" out) (display " * Sscheme_init since libraries are visited during heap build. */\n" out) (display " setenv(\"JERBOA_STATIC\", \"1\", 1);\n" out) @@ -594,9 +643,23 @@ (define (run-cmd cmd) (printf " ~a~n" cmd) - (unless (= 0 (system cmd)) + (unless (= 0 (system (check-untainted-shell-command cmd))) (error 'build-jcode-musl "Command failed" cmd))) +(define (capture-command-output cmd) + (call-with-values + (lambda () + (open-process-ports + (check-untainted-shell-command cmd) + (buffer-mode block) + (native-transcoder))) + (lambda (to-stdin from-stdout from-stderr pid) + (let ([out (get-string-all from-stdout)]) + (close-port to-stdin) + (close-port from-stdout) + (close-port from-stderr) + out)))) + ;; Compile static_boot.c (run-cmd (format "~a -c -O2 ~a -I'~a' -o '~a/static_boot.o' '~a'" gcc harden-cflags scheme-h-dir @@ -618,7 +681,7 @@ gcc harden-cflags build-dir landlock-src)) (begin (printf " Note: landlock-shim.c not found — creating stub~n") - (system (format "echo '' | ~a -c -x c -o '~a/landlock-shim.o' -" gcc build-dir))))) + (run-cmd (format "echo '' | ~a -c -x c -o '~a/landlock-shim.o' -" gcc build-dir))))) ;; ========== Step 7: Link static binary ========== @@ -645,7 +708,7 @@ (printf "~n[harden] Stripping symbols...~n") (let ([pre-size (file-length (open-file-input-port "jcode-musl"))]) (run-cmd "strip --strip-all jcode-musl") - (when (= 0 (system "objcopy --strip-section-headers jcode-musl 2>/dev/null")) + (when (= 0 (system (check-untainted-shell-command "objcopy --strip-section-headers jcode-musl 2>/dev/null"))) (printf " Section headers removed~n")) (let ([post-size (file-length (open-file-input-port "jcode-musl"))]) (printf " Stripped: ~a → ~a bytes (~a% reduction)~n" @@ -655,16 +718,15 @@ ;; Compute SHA-256 integrity hash (when (file-exists? "jcode-musl") (printf "[harden] Computing integrity hash...~n") - (system "sha256sum jcode-musl | cut -d' ' -f1 | tr -d '\\n' > /tmp/_jcode_hash.txt") - (let ([hash-hex (call-with-input-file "/tmp/_jcode_hash.txt" get-string-all)]) - (system "rm -f /tmp/_jcode_hash.txt") + (let ([hash-hex (substring (capture-command-output "sha256sum jcode-musl") 0 64)]) (printf " SHA-256: ~a~n" hash-hex) (when (= (string-length hash-hex) 64) (let ([bv (make-bytevector 32)]) - (do ([i 0 (+ i 1)]) - ((= i 32)) - (bytevector-u8-set! bv i - (string->number (substring hash-hex (* i 2) (+ (* i 2) 2)) 16))) + (let loop ([i 0]) + (when (< i 32) + (bytevector-u8-set! bv i + (string->number (substring hash-hex (* i 2) (+ (* i 2) 2)) 16)) + (loop (+ i 1)))) (let ([port (open-file-output-port "jcode-musl.sha256" (file-options no-fail))]) (put-bytevector port bv) (close-port port)) @@ -675,13 +737,14 @@ (printf "~n[cleanup] Removing isolated build directory...~n") (cleanup-build-dir!) (for-each (lambda (f) - (when (file-exists? f) (delete-file f))) + (when (file-exists? f) + (delete-file (check-untainted-file-path f)))) '("jcode.boot")) ;; Summary (printf "~n========================================~n") (printf "Static binary created: jcode-musl~n~n") -(system "ls -lh jcode-musl") +(system (check-untainted-shell-command "ls -lh jcode-musl")) (printf "~n") -(system "file jcode-musl") +(system (check-untainted-shell-command "file jcode-musl")) (printf "~nTest: ./jcode-musl --help~n") --- a/docs/README.md +++ b/docs/README.md @@ -45,6 +45,15 @@ elevator pitch. - **[Remote & Android](remote.md)** — `jcode serve`, the `relay` rendezvous, and `connect`; how the Android thin client talks to a server running in Termux. +## Security + +- **[Threat model](threat-model.md)** — assets, trust boundaries, controls, and + residual risks for production-readiness review. +- **[Credential handling](credential-handling.md)** — key sources, storage,