Security hardening and release readiness
ober
40789ee8880435640675d3451379f4cc75d3f225
--- a/.build.yml +++ b/.build.yml @@ -8,6 +8,7 @@ packages: - libx11-dev - file - git + - ripgrep - curl - unzip - zip @@ -32,7 +33,7 @@ tasks: "$J" pkg verify --reproduce - test-gitsafe: | cd jerboa-gitsafe - JERBUILD="$HOME/jerboa/dist/jerbuild" make test - - build-gitsafe: | + JERBUILD="$HOME/jerboa/dist/jerbuild" make verify + - evidence-gitsafe: | cd jerboa-gitsafe - JERBUILD="$HOME/jerboa/dist/jerbuild" make binary + JERBUILD="$HOME/jerboa/dist/jerbuild" make release-evidence 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 + +env: + JERBOA_VERSION: v0.2.3 + JERBUILD: ${{ github.workspace }}/.jerboa/bin/jerbuild + +jobs: + verify: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install system tools + run: | + set -eu + sudo apt-get update + sudo apt-get install -y --no-install-recommends build-essential curl ca-certificates ripgrep + + - name: Install Jerboa toolchain + run: sh support/ensure-jerboa.sh "$JERBOA_VERSION" .jerboa/bin + + - name: Verify + run: make verify + + - name: Release evidence + run: make release-evidence 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" -- . ":!README.md" ":!PLAN.md" ":!test/**" ":!tests/**" ":!*.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 @@ -12,6 +12,11 @@ petite_boot.h scheme_boot.h gitsafe_program.h .build/ +dist/ +gitsafe.boot +gitsafe-all.so +gitsafe-main.c +gitsafe-main.o *.so *.wpo .jerboa/ --- a/.jerboa/security.json +++ b/.jerboa/security.json @@ -1,11 +1,11 @@ { "version": 1, "repo": "jerboa-gitsafe", - "extends": ["jerboa:cli", "jerboa:parser", "jerboa:crypto"], + "extends": ["jerboa:cli", "jerboa:parser", "jerboa:security-tool"], "paths": { - "production": ["*.ss", "*.sls", "lib/**/*.ss", "lib/**/*.sls", "src/**/*.ss", "src/**/*.sls", "bin/**", "Makefile"], + "production": ["gitsafe/**/*.ss", "build*.ss", "build-common.ss", "Makefile", ".jerbuild"], "tests": ["test/**", "tests/**", "**/*-test.ss", "fixtures/**"], - "generated": ["build/**", "dist/**", "target/**"], + "generated": ["build/**", "dist/**", "target/**", ".build/**", "gitsafe-bin", "gitsafe-linux-*", "gitsafe-freebsd-*", "*.so", "*.wpo", "*.o", "*.boot", "*_boot.h", "gitsafe_program.h"], "vendor": ["vendor/**", "third_party/**"], "docs": ["README.md", "docs/**", "*.md"] }, @@ -14,9 +14,45 @@ "imports": { "directChezscheme": "deny" }, "ffi": { "allowed": false }, "process": { "shellInterpolation": "deny" }, - "network": { "requireTimeouts": false }, + "network": { "allowed": false, "requireTimeouts": false }, "eval": { "stringEval": "deny", "bareRead": "deny", "allowReadEval": false }, - "secrets": { "scannerRepo": true, "allowFixtureSecretsOnlyInTests": true } + "secrets": { + "scannerRepo": true, + "allowFixtureSecretsOnlyInTests": true, + "fixturePaths": ["test/fixtures/**"], + "defaultPublicSeverity": "medium" + }, + "release": { + "requireEvidence": true, + "rejectGeneratedArtifactsOutsideDist": true, + "requireHistorySecretReviewBeforePublicRelease": true + } }, + "targetEvidence": { + "script": "scripts/target-evidence.sh", + "optionalProofFileEnv": "JSAFE_TARGET_PROOF_FILE", + "requiredProofEnv": "JSAFE_REQUIRE_TARGET_PROOF", + "maxProofBytes": 65536, + "targetProofStatus": "target_gitsafe_proof_status", + "productionStatus": "production_gitsafe_status", + "sensitiveArtifactPolicy": "no-real-secrets-fixtures-private-paths-or-host-data", + "requiredMarkers": [ + "release_host_sbom_reproducibility_status=reviewed", + "pre_publication_history_secret_scan_status=reviewed", + "detector_policy_review_status=reviewed", + "fixture_policy_review_status=reviewed", + "false_positive_suppression_review_status=reviewed", + "provider_token_coverage_status=reviewed", + "hook_install_policy_review_status=reviewed", + "sensitive_artifact_policy=no-real-secrets-fixtures-private-paths-or-host-data" + ] + }, + "releaseGates": [ + "make security", + "make test", + "make binary", + "make target-evidence", + "make release-evidence" + ], "suppressions": [] } 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 @@ -2,7 +2,7 @@ # only `jerbuild` + a C compiler — no jerboa source checkout and no separately # built Chez. If no jerbuild is found, `make binary` downloads the Jerboa # release toolchain for this os/arch into .jerboa/bin (support/ensure-jerboa.sh). -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"; \ @@ -16,14 +16,18 @@ LIBDIRS = --libdirs $(CURDIR):$(JH)/lib JEXEC = $(JERBUILD) exec $(LIBDIRS) BIN := gitsafe-bin BIN_DIR := $(HOME)/.local/bin +EVIDENCE_DIR ?= dist/release-evidence +SBOM_DIR ?= dist/sbom +REPRO_DIR ?= dist/reproducibility +TARGET_EVIDENCE_DIR ?= dist/target-evidence TEMPLATE_DIR := $(HOME)/.git-templates HOOK_DIR := $(TEMPLATE_DIR)/hooks -JERBOA_HOME ?= $(HOME)/mine/jerboa +JERBOA_HOME ?= ../jerboa SCHEME ?= $(JERBOA_HOME)/.chez/bin/scheme FREEBSD_AMD64_CC ?= $(JERBOA_HOME)/support/cross-cc-freebsd-amd64 XC_LIBDIRS = $(CURDIR):$(JERBOA_HOME)/lib:$(JH)/lib -.PHONY: all build binary run test install clean help ensure-jerboa-tools linux linux-amd64 linux-arm64 freebsd freebsd-amd64 +.PHONY: all build binary run test import-check security audit verify sbom reproducibility-report target-evidence release-evidence install clean help ensure-jerboa-tools linux linux-amd64 linux-arm64 freebsd freebsd-amd64 .DEFAULT_GOAL := help all: binary @@ -60,17 +64,76 @@ run: binary ./$(BIN) $(ARGS) test: ensure-jerboa-tools - $(JEXEC) test/test-gitsafe.ss + JERBOA_RUNTIME_HOME="$(JH)" $(JEXEC) test/test-gitsafe.ss + +import-check: ensure-jerboa-tools + JERBOA_RUNTIME_HOME="$(JH)" $(JEXEC) support/import-check.ss + +security: + ./scripts/security-check.sh + $(MAKE) import-check + +audit: security + +target-evidence: scripts/target-evidence.sh + @REPO_ROOT=. TARGET_EVIDENCE_DIR="$(TARGET_EVIDENCE_DIR)" sh scripts/target-evidence.sh + +verify: security test binary target-evidence + +sbom: ensure-jerboa-tools + REPO_ROOT="$(CURDIR)" SBOM_DIR="$(SBOM_DIR)" JERBUILD="$(JERBUILD)" sh scripts/sbom.sh + +reproducibility-report: ensure-jerboa-tools + REPO_ROOT="$(CURDIR)" REPRO_DIR="$(REPRO_DIR)" MAKE="$(MAKE)" sh scripts/reproducibility-report.sh + +release-evidence: + rm -rf $(EVIDENCE_DIR) + mkdir -p $(EVIDENCE_DIR) + git rev-parse HEAD > $(EVIDENCE_DIR)/git-commit.txt + git status --short > $(EVIDENCE_DIR)/git-status.txt + { uname -srm; printf 'jerbuild: '; "$(JERBUILD)" --version; if "$(JERBUILD)" --jerboa-home >/dev/null 2>&1; then echo 'jerboa_home_status=present'; else echo 'jerboa_home_status=missing'; fi; } > $(EVIDENCE_DIR)/build-environment.txt + $(MAKE) security > $(EVIDENCE_DIR)/security-check.txt 2>&1 + $(MAKE) test > $(EVIDENCE_DIR)/test.txt 2>&1 + $(MAKE) binary > $(EVIDENCE_DIR)/build.txt 2>&1 + $(MAKE) sbom > $(EVIDENCE_DIR)/sbom-log.txt 2>&1 + $(MAKE) reproducibility-report > $(EVIDENCE_DIR)/reproducibility-log.txt 2>&1 + $(MAKE) target-evidence > $(EVIDENCE_DIR)/target-evidence.log 2>&1 + ./$(BIN) --version > $(EVIDENCE_DIR)/binary-smoke.txt + shasum -a 256 $(BIN) > $(EVIDENCE_DIR)/binary-sha256.txt + find . -type f \ + ! -path './.git/*' ! -path './dist/*' ! -path './.jerboa/bin/*' \ + ! -path './.build/*' ! -name '$(BIN)' ! -name 'gitsafe-linux-*' \ + ! -name 'gitsafe-freebsd-*' ! -name '*.so' ! -name '*.wpo' \ + ! -name '*.o' ! -name '*.boot' ! -name '*.jpkg' \ + ! -name 'gitsafe_program.h' ! -name 'petite_boot.h' ! -name 'scheme_boot.h' \ + -print | sort | xargs shasum -a 256 > $(EVIDENCE_DIR)/source-sha256.txt + shasum -a 256 Makefile .jerbuild SECURITY.md README.md LICENSE .jerboa/security.json docs/*.md scripts/*.sh support/import-check.ss > $(EVIDENCE_DIR)/release-inputs-sha256.txt + rm -rf $(EVIDENCE_DIR)/sbom $(EVIDENCE_DIR)/reproducibility $(EVIDENCE_DIR)/target-evidence + cp -R $(SBOM_DIR) $(EVIDENCE_DIR)/sbom + cp -R $(REPRO_DIR) $(EVIDENCE_DIR)/reproducibility + cp -R $(TARGET_EVIDENCE_DIR) $(EVIDENCE_DIR)/target-evidence + grep -q '^target_evidence_status=present$$' "$(EVIDENCE_DIR)/target-evidence/status.txt" + test "$$(grep '^status=' "$(EVIDENCE_DIR)/reproducibility/report.txt" | cut -d= -f2)" = "match" + sh scripts/sanitize-evidence.sh "$(EVIDENCE_DIR)" + @echo "release evidence written to $(EVIDENCE_DIR)" install: binary - mkdir -p $(BIN_DIR) - install -m 0755 $(BIN) $(BIN_DIR)/gitsafe - mkdir -p $(HOOK_DIR) - printf '#!/bin/sh\nexec gitsafe pre-commit\n' > $(HOOK_DIR)/pre-commit - chmod +x $(HOOK_DIR)/pre-commit - printf '#!/bin/sh\nwhile read local_ref local_sha remote_ref remote_sha; do\n gitsafe pre-push --local-ref "$$local_sha" --remote-ref "$$remote_sha" || exit $$?\ndone\n' > $(HOOK_DIR)/pre-push - chmod +x $(HOOK_DIR)/pre-push - git config --global init.templateDir $(TEMPLATE_DIR) + mkdir -p "$(BIN_DIR)" + install -m 0755 "$(BIN)" "$(BIN_DIR)/gitsafe" + mkdir -p "$(HOOK_DIR)" + @for hook in pre-commit pre-push; do \ + path="$(HOOK_DIR)/$$hook"; \ + if [ -f "$$path" ] && ! grep -q 'Installed by gitsafe' "$$path"; then \ + echo "ERROR: $$path exists and was not installed by gitsafe; refusing to overwrite." >&2; \ + echo "Back it up first, or use per-repo installation with: gitsafe install" >&2; \ + exit 1; \ + fi; \ + done + printf '#!/bin/sh\n# Installed by gitsafe\nexec gitsafe pre-commit\n' > "$(HOOK_DIR)/pre-commit" + chmod +x "$(HOOK_DIR)/pre-commit" + printf '#!/bin/sh\n# Installed by gitsafe\nwhile read local_ref local_sha remote_ref remote_sha; do\n gitsafe pre-push --local-ref "$$local_sha" --remote-ref "$$remote_sha" || exit $$?\ndone\n' > "$(HOOK_DIR)/pre-push" + chmod +x "$(HOOK_DIR)/pre-push" + git config --global init.templateDir "$(TEMPLATE_DIR)" @echo "Installed gitsafe to $(BIN_DIR)/gitsafe + global git hooks." linux: linux-amd64 @@ -136,5 +199,8 @@ help: @echo " make freebsd-amd64 Cross-build ./gitsafe-freebsd-amd64 (FreeBSD dynamic)" @echo " make run ARGS='...' Build + run ./gitsafe-bin" @echo " make test Run the test suite" + @echo " make security Run local security/import checks" + @echo " make verify Run security, tests, and binary build" + @echo " make release-evidence Capture release-gate evidence under $(EVIDENCE_DIR)" @echo " make install Build + install to ~/.local/bin + global git hooks" @echo " make clean Remove build artifacts" --- a/PLAN.md +++ b/PLAN.md @@ -566,7 +566,7 @@ ANSI colors: ### Makefile ```makefile -JERBOA_HOME ?= $(HOME)/mine/jerboa +JERBOA_HOME ?= ../jerboa SCHEME ?= scheme .PHONY: run test binary install-binary install clean --- a/README.md +++ b/README.md @@ -2,6 +2,17 @@ A fast, zero-dependency git hook that blocks commits and pushes containing leaked secrets, API keys, tokens, and credentials. Compiles to a single static binary — no runtime dependencies on the target machine. +## Security And Release Status + +This repo is preview security tooling until the release checklist is complete. Local release gates: + +```bash +make verify +make release-evidence +``` + +Security notes are in `SECURITY.md`, `docs/threat-model.md`, `docs/security-hardening.md`, `docs/corpus-policy.md`, and `docs/release-evidence.md`. + ## Quick Start ```bash @@ -23,6 +34,10 @@ This builds the binary (fetching the Jerboa toolchain on first run if needed) an - Creates pre-commit and pre-push hooks in `~/.git-templates/hooks/` - Sets `git config --global init.templateDir ~/.git-templates` +If a global template hook already exists and does not contain the +`Installed by gitsafe` marker, `make install` refuses to overwrite it. Back up or +merge that hook manually, or use per-repo `gitsafe install`. + Make sure `~/.local/bin` is on your `PATH`: ```bash @@ -37,10 +52,16 @@ make binary # Native build → ./gitsafe-bin make linux-amd64 # Linux x86_64 musl static → ./gitsafe-linux-amd64 make linux-arm64 # Linux arm64 musl static → ./gitsafe-linux-arm64 make freebsd-amd64 # FreeBSD amd64 dynamic → ./gitsafe-freebsd-amd64 +make verify # Security checks, tests, and binary build +make release-evidence # Capture release evidence under dist/release-evidence/ ``` To pin a toolchain explicitly: `make binary JERBUILD=/path/to/jerbuild` or `JERBOA_VERSION=<tag>`. Cross targets use `JERBOA_HOME` for Chez cross prefixes and still run `ensure-jerboa-tools`, so a missing `jerbuild` is bootstrapped the same way as native builds. +Production support also requires a reviewed `JSAFE_TARGET_PROOF_FILE` with the +markers documented in `docs/release-evidence.md`. `JSAFE_REQUIRE_TARGET_PROOF=1` +fails closed when that target proof is missing, incomplete, or contains secret +material. ## Setting Up Existing Repos @@ -85,6 +106,9 @@ The global git template is set during `make install`: ~/.git-templates/hooks/pre-push → exec gitsafe pre-push ``` +Both generated hooks include an `Installed by gitsafe` marker so future gitsafe +installs can update them without overwriting unrelated hook content. + You can verify this is active: ```bash new file mode 100644 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,56 @@ +# Security Policy + +`jerboa-gitsafe` is security tooling. It scans untrusted repository content, staged git blobs, diffs, `.gitsafe.json`, `.gitsafeignore`, and optional baseline reports for secret-like material. + +## Supported Status + +No public production-support commitment exists yet. Treat releases as preview +until the release evidence in `docs/release-evidence.md`, a history secret scan, +target release-host SBOM/reproducibility evidence, and external review are +complete. + +Before any security-sensitive release: + +- `make verify` must pass from a clean checkout. +- `make target-evidence` must record local blocked status or a reviewed + marker-complete target proof. +- `make release-evidence` must write a complete `dist/release-evidence/` bundle. +- `jerboa_security_scan` must be clean for the production source set. +- The fake-token fixture policy in `docs/corpus-policy.md` must be followed. + +## Security Scope + +Expected behavior: + +- Findings are redacted before text, JSON, or SARIF output. +- Git is invoked through argv process APIs. +- Hook installation touches only `.git/hooks` for the current repository. +- Test fixtures may contain synthetic token shapes only under `test/fixtures/`. +- Target proof files must not contain real secrets, token examples, raw finding + output, private checkout paths, hostnames, or customer/user data. + +Not guaranteed: + +- `gitsafe` does not revoke or validate provider credentials. +- `gitsafe` does not replace a history scan before first public release. +- `gitsafe` does not guarantee zero false positives or zero false negatives. + +## Target Production Proof + +`JSAFE_TARGET_PROOF_FILE` may point at a reviewed proof file for a production +target environment. `JSAFE_REQUIRE_TARGET_PROOF=1` fails closed when that proof +is missing or marker-incomplete. Proof files containing high-confidence secret +material are rejected. The proof file must contain these exact markers: + +- `release_host_sbom_reproducibility_status=reviewed` +- `pre_publication_history_secret_scan_status=reviewed` +- `detector_policy_review_status=reviewed` +- `fixture_policy_review_status=reviewed` +- `false_positive_suppression_review_status=reviewed` +- `provider_token_coverage_status=reviewed` +- `hook_install_policy_review_status=reviewed` +- `sensitive_artifact_policy=no-real-secrets-fixtures-private-paths-or-host-data` + +## 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-binary.ss +++ b/build-binary.ss @@ -6,7 +6,40 @@ ;; ;; Produces: ./gitsafe-bin (single ELF binary with embedded boot files + program) -(import (chezscheme)) +(import (scheme) + (only (std security taint) check-untainted! safe-delete-file safe-system)) + +;; --- Checked shell/file helpers --- + +(define (shell-quote s) + (let ([out (open-output-string)]) + (put-char out #\') + (let loop ([i 0]) + (when (< i (string-length s)) + (let ([ch (string-ref s i)]) + (if (char=? ch #\') + (display "'\\''" out) + (put-char out ch))) + (loop (+ i 1)))) + (put-char out #\') + (get-output-string out))) + +(define (run-shell command) + (check-untainted! command 'run-shell) + (safe-system command)) + +(define (call-with-replace-output-file path proc) + (check-untainted! path 'call-with-replace-output-file) + (call-with-port (open-file-output-port path + (file-options no-fail) + (buffer-mode block) + (native-transcoder)) + proc)) + +(define (delete-if-exists! path) + (check-untainted! path 'delete-if-exists!) + (when (file-exists? path) + (safe-delete-file path))) ;; --- Helper: generate C header from binary file --- (define (file->c-header input-path output-path array-name size-name) @@ -14,7 +47,7 @@ [data (get-bytevector-all port)] [size (bytevector-length data)]) (close-port port) - (call-with-output-file output-path + (call-with-replace-output-file output-path (lambda (out) (fprintf out "/* Auto-generated — do not edit */\n") (fprintf out "static const unsigned char ~a[] = {\n" array-name) @@ -27,7 +60,6 @@ (loop (+ i 1)))) (fprintf out "\n};\n") (fprintf out "static const unsigned int ~a = ~a;\n" size-name size)) - 'replace) (printf " ~a: ~a bytes\n" output-path size))) ;; --- Detect OS --- @@ -183,7 +215,7 @@ ;; --- Step 4: Generate C main, compile, and link --- (printf "[4/5] Compiling and linking...\n") -(call-with-output-file "gitsafe-main.c" +(call-with-replace-output-file "gitsafe-main.c" (lambda (out) (fprintf out "/* Auto-generated — do not edit */\n") (fprintf out "#define _GNU_SOURCE\n") @@ -219,8 +251,7 @@ (fprintf out " unlink(prog_path);\n") (fprintf out " Sscheme_deinit();\n") (fprintf out " return status;\n") - (fprintf out "}\n")) - 'replace) + (fprintf out "}\n"))) (define link-libs (cond @@ -230,15 +261,21 @@ [else "-lkernel -llz4 -lz -lm -ldl -lpthread -luuid -lncurses"])) (let ([cc (or (getenv "CC") "cc")]) - (let ([rc (system (format "~a -c -I~a -o gitsafe-main.o gitsafe-main.c" cc chez-dir))]) + (let ([rc (run-shell + (format "~a -c -I~a -o gitsafe-main.o gitsafe-main.c" + (shell-quote cc) + (shell-quote chez-dir)))]) (unless (= rc 0) (printf "Error: C compilation failed\n") (exit 1))) - (let ([rc (system (format "~a -o gitsafe-bin gitsafe-main.o -L~a ~a" - cc chez-dir link-libs))]) + (let ([rc (run-shell + (format "~a -o gitsafe-bin gitsafe-main.o -L~a ~a" + (shell-quote cc) + (shell-quote chez-dir) + link-libs))]) (unless (= rc 0) (printf "Error: linking failed\n") (exit 1)))) ;; --- Step 5: Cleanup --- (printf "[5/5] Cleaning up intermediate files...\n") -(for-each (lambda (f) (when (file-exists? f) (delete-file f))) +(for-each delete-if-exists! '("gitsafe-main.c" "gitsafe-main.o" "gitsafe_program.h" "gitsafe_petite_boot.h" "gitsafe_scheme_boot.h" "gitsafe_boot.h" @@ -248,7 +285,7 @@ (for-each (lambda (m) (for-each (lambda (ext) (let ([f (format "~a~a" m ext)]) - (when (file-exists? f) (delete-file f)))) + (delete-if-exists! f))) '(".so" ".wpo"))) gitsafe-modules) --- a/build-common.ss +++ b/build-common.ss @@ -13,13 +13,28 @@ ;; Steps 4 (generate C main) and 5 (compile + link) are platform-specific ;; and live in the including script. +;; --- Checked file helpers --- + +(define (call-with-replace-output-file path proc) + (check-untainted! path 'call-with-replace-output-file) + (call-with-port (open-file-output-port path + (file-options no-fail) + (buffer-mode block) + (native-transcoder)) + proc)) + +(define (delete-if-exists! path) + (check-untainted! path 'delete-if-exists!) + (when (file-exists? path) + (safe-delete-file path))) + ;; --- C byte-array header generation --- (define (file->c-header input-path output-path array-name size-name) (let* ([port (open-file-input-port input-path)] [data (get-bytevector-all port)] [size (bytevector-length data)]) (close-port port) - (call-with-output-file output-path + (call-with-replace-output-file output-path (lambda (out) (fprintf out "/* Auto-generated — do not edit */\n") (fprintf out "static const unsigned char ~a[] = {\n" array-name) @@ -32,7 +47,6 @@ (loop (+ i 1)))) (fprintf out "\n};\n") (fprintf out "static const unsigned int ~a = ~a;\n" size-name size)) - 'replace) (printf " ~a: ~a bytes\n" output-path size))) ;; --- Find the csv<version>/<machine-type> dir inside a Chez lib directory --- @@ -155,7 +169,7 @@ ;; platform: "macos" or "musl" — determines the C source/object file names (define (do-cleanup! platform) (printf "[6/6] Cleaning up intermediate files...\n") - (for-each (lambda (f) (when (file-exists? f) (delete-file f))) + (for-each delete-if-exists! (list (format "gitsafe-main-~a.c" platform) (format "gitsafe-main-~a.o" platform) "gitsafe_program.h" "gitsafe_petite_boot.h" @@ -165,6 +179,6 @@ (for-each (lambda (m) (for-each (lambda (ext) (let ([f (format "~a~a" m ext)]) - (when (file-exists? f) (delete-file f)))) + (delete-if-exists! f))) '(".so" ".wpo"))) gitsafe-modules)) --- a/build-gitsafe-cross.ss +++ b/build-gitsafe-cross.ss @@ -5,7 +5,25 @@ ;;; amd64 (default) -> ta6le + x86_64-linux-musl-gcc -> gitsafe-linux-amd64 ;;; arm64 -> tarm64le + aarch64-linux-musl-gcc -> gitsafe-linux-arm64 -(import (chezscheme)) +(import (scheme) + (only (std security taint) check-untainted! safe-system)) + +(define (shell-quote s) + (let ([out (open-output-string)]) + (put-char out #\') + (let loop ([i 0]) + (when (< i (string-length s)) + (let ([ch (string-ref s i)]) + (if (char=? ch #\') + (display "'\\''" out) + (put-char out ch))) + (loop (+ i 1)))) + (put-char out #\') + (get-output-string out))) + +(define (run-shell command) + (check-untainted! command 'run-shell) + (safe-system command)) (define (require-env name) (or (getenv name) @@ -69,11 +87,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))) + (run-shell (format "rm -rf ~a" (shell-quote obj-dir))) + (run-shell (format "mkdir -p ~a" (shell-quote obj-dir)))) (define (cleanup!) - (system (format "rm -rf '~a'" obj-dir))) + (run-shell (format "rm -rf ~a" (shell-quote obj-dir)))) (prepare-obj-dir!) @@ -233,11 +251,15 @@ "-Wl,--allow-multiple-definition " "-Wl,--defsym=_dl_find_object=0 " "-lm -ldl -lpthread") - cross-cc cross-csv-dir output - main-c-path - cross-csv-dir cross-csv-dir cross-csv-dir)) + (shell-quote cross-cc) + (shell-quote cross-csv-dir) + (shell-quote output) + (shell-quote main-c-path) + (shell-quote cross-csv-dir) + (shell-quote cross-csv-dir) + (shell-quote cross-csv-dir))) (printf " ~a~n" link-cmd) -(let ([rc (system link-cmd)]) +(let ([rc (run-shell link-cmd)]) (unless (zero? rc) (cleanup!) (error 'build-gitsafe-cross "cross-link failed" rc))) @@ -245,5 +267,5 @@ (cleanup!) (printf "~n=== Build complete: ~a ===~n" output) -(system (format "ls -lh ~a" output)) -(system (format "file ~a" output)) +(run-shell (format "ls -lh ~a" (shell-quote output))) +(run-shell (format "file ~a" (shell-quote output))) --- a/build-gitsafe-freebsd-cross.ss +++ b/build-gitsafe-freebsd-cross.ss @@ -1,7 +1,25 @@ #!chezscheme ;;; Cross-compile gitsafe from this host to FreeBSD amd64. -(import (chezscheme)) +(import (scheme) + (only (std security taint) check-untainted! safe-system)) + +(define (shell-quote s) + (let ([out (open-output-string)]) + (put-char out #\') + (let loop ([i 0]) + (when (< i (string-length s)) + (let ([ch (string-ref s i)]) + (if (char=? ch #\') + (display "'\\''" out) + (put-char out ch))) + (loop (+ i 1)))) + (put-char out #\') + (get-output-string out))) + +(define (run-shell command) + (check-untainted! command 'run-shell) + (safe-system command)) (define (require-env name) (or (getenv name) @@ -54,11 +72,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))) + (run-shell (format "rm -rf ~a" (shell-quote obj-dir))) + (run-shell (format "mkdir -p ~a" (shell-quote obj-dir)))) (define (cleanup!) - (system (format "rm -rf '~a'" obj-dir))) + (run-shell (format "rm -rf ~a" (shell-quote obj-dir)))) (prepare-obj-dir!) @@ -202,11 +220,15 @@ "~a " "~a/libkernel.a ~a/libz.a ~a/liblz4.a " "-lm -lpthread -lutil") - cross-cc cross-csv-dir output - main-c-path - cross-csv-dir cross-csv-dir cross-csv-dir)) + (shell-quote cross-cc) + (shell-quote cross-csv-dir) + (shell-quote output) + (shell-quote main-c-path) + (shell-quote cross-csv-dir) + (shell-quote cross-csv-dir) + (shell-quote cross-csv-dir))) (printf " ~a~n" link-cmd) -(let ([rc (system link-cmd)]) +(let ([rc (run-shell link-cmd)]) (unless (zero? rc) (cleanup!) (error 'build-gitsafe-freebsd-cross "cross-link failed" rc))) @@ -214,5 +236,5 @@ (cleanup!) (printf "~n=== Build complete: ~a ===~n" output) -(system (format "ls -lh ~a" output)) -(system (format "file ~a" output)) +(run-shell (format "ls -lh ~a" (shell-quote output))) +(run-shell (format "file ~a" (shell-quote output))) new file mode 100644 --- /dev/null +++ b/docs/corpus-policy.md @@ -0,0 +1,24 @@ +# gitsafe Corpus And False-Positive Policy + +`gitsafe` is a scanner repository, so tests must include fake token shapes. These fixtures are allowed only under `test/fixtures/` and must be safe examples that cannot authenticate to a real service. + +## Allowed Fixture Data + +- Provider example tokens documented as invalid. +- Synthetic strings generated for parser or detector tests. +- Placeholder private-key headers without valid private-key material. +- False-positive examples that document why entropy-only signals are suppressed. + +## Not Allowed + +- Real tokens, keys, session cookies, account IDs tied to real users, production hostnames, or customer/user data. +- Valid private keys, even if encrypted. +- Live webhook secrets or OAuth credentials. + +## Release Rule + +The release gate excludes `test/fixtures/` from high-confidence secret matching but still scans production source, docs, scripts, and CI definitions. Any new fixture file must be reviewed as synthetic before publication. + +Target release proof must not include fixture strings or raw finding output. It +should record reviewed marker statuses only, and the target proof script rejects +high-confidence secret material. new file mode 100644 --- /dev/null +++ b/docs/release-evidence.md @@ -0,0 +1,75 @@ +# gitsafe Release Evidence + +Run: + +```sh +JERBUILD=/path/to/jerbuild make release-evidence +make target-evidence +``` + +The target writes evidence to `dist/release-evidence/`: + +- `git-commit.txt` and `git-status.txt` +- `build-environment.txt` +- `security-check.txt` +- `test.txt` +- `build.txt` +- `sbom-log.txt` +- `reproducibility-log.txt` +- `binary-smoke.txt` +- `binary-sha256.txt` +- `source-sha256.txt` +- `release-inputs-sha256.txt` +- `sbom/`, a dependency/toolchain manifest with source and release-input hashes +- `reproducibility/report.txt`, comparing two clean local `gitsafe-bin` builds + after a throwaway toolchain prewarm, plus the source manifest before/after the + measured comparison +- `target-evidence/`, a local blocked-status record or a reviewed target proof + copied with a SHA-256 sidecar. + +The security evidence must prove that global hook installation refuses to +overwrite unmanaged template hooks and that generated global hooks include the +`Installed by gitsafe` ownership marker. + +A public release requires this evidence from a clean checkout, plus a history