diff --git a/.buildkite/scripts/fallow.sh b/.buildkite/scripts/fallow.sh index 34ae582..eb5692f 100755 --- a/.buildkite/scripts/fallow.sh +++ b/.buildkite/scripts/fallow.sh @@ -11,6 +11,7 @@ fi base_branch="${BUILDKITE_PULL_REQUEST_BASE_BRANCH:-${TABELLIO_BASE_BRANCH:-main}}" git fetch --no-tags origin "+refs/heads/${base_branch}:refs/remotes/origin/${base_branch}" +. .buildkite/scripts/security-tools.sh npm install --global fallow@2.89.0 c8@10.1.3 bash .buildkite/scripts/provenance-coverage.sh diff --git a/.buildkite/scripts/product-validation.sh b/.buildkite/scripts/product-validation.sh index f8e4cf1..242f8a0 100755 --- a/.buildkite/scripts/product-validation.sh +++ b/.buildkite/scripts/product-validation.sh @@ -15,6 +15,7 @@ if [[ "$pull_request" == "false" && "$build_context" != "preflight" && "$default fi . .buildkite/scripts/verify-git-toolchain.sh +. .buildkite/scripts/security-tools.sh candidate="${BUILDKITE_COMMIT:-HEAD}" base_branch="${BUILDKITE_PULL_REQUEST_BASE_BRANCH:-${TABELLIO_BASE_BRANCH:-main}}" diff --git a/.buildkite/scripts/security-tools.sh b/.buildkite/scripts/security-tools.sh new file mode 100644 index 0000000..f958a01 --- /dev/null +++ b/.buildkite/scripts/security-tools.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Source this script to retain PATH in Buildkite. GitHub receives the same paths +# through its runner environment files. No candidate package scripts are run. +if [[ -n "${TABELLIO_SECURITY_TOOLS_DIR:-}" ]]; then + security_tools_root="$TABELLIO_SECURITY_TOOLS_DIR" +else + security_tools_root="$(mktemp -d "${TMPDIR:-/tmp}/tabellio-security-tools.XXXXXX")" +fi +mkdir -p "$security_tools_root" +case "$(uname -s)-$(uname -m)" in + Darwin-arm64) + security_archive_name="gitleaks_8.30.1_darwin_arm64.tar.gz" + security_archive_digest="b40ab0ae55c505963e365f271a8d3846efbc170aa17f2607f13df610a9aeb6a5" + ;; + Linux-x86_64) + security_archive_name="gitleaks_8.30.1_linux_x64.tar.gz" + security_archive_digest="551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb" + ;; + *) printf '%s\n' 'Unsupported security tool platform.' >&2; exit 1 ;; +esac +security_archive_path="$security_tools_root/$security_archive_name" +if [[ ! -f "$security_archive_path" ]]; then + curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --tlsv1.2 \ + --connect-timeout 15 --max-time 120 \ + "https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/$security_archive_name" \ + --output "$security_archive_path" +fi +node --input-type=module - "$security_archive_path" "$security_archive_digest" <<'NODE' +import { createHash } from 'node:crypto'; +import { readFileSync } from 'node:fs'; +const actual = createHash('sha256').update(readFileSync(process.argv[2])).digest('hex'); +if (actual !== process.argv[3]) throw new Error('Security scanner archive integrity mismatch.'); +NODE +tar -xzf "$security_archive_path" -C "$security_tools_root" gitleaks +npm install --prefix "$security_tools_root" --no-save --no-package-lock --ignore-scripts \ + --registry https://registry.npmjs.org @ast-grep/cli@0.45.1 +export PATH="$security_tools_root:$security_tools_root/node_modules/.bin:$PATH" +export TABELLIO_GITLEAKS="$security_tools_root/gitleaks" +export TABELLIO_REQUIRE_SECURITY_SCANNERS=1 +test "$(gitleaks version)" = "8.30.1" +test "$(ast-grep --version)" = "ast-grep 0.45.1" +if [[ -n "${GITHUB_PATH:-}" ]]; then + printf '%s\n' "$security_tools_root" "$security_tools_root/node_modules/.bin" >> "$GITHUB_PATH" + printf 'TABELLIO_GITLEAKS=%s\nTABELLIO_REQUIRE_SECURITY_SCANNERS=1\n' "$TABELLIO_GITLEAKS" >> "$GITHUB_ENV" +fi diff --git a/.buildkite/scripts/tests.sh b/.buildkite/scripts/tests.sh index 0bddf21..990be14 100644 --- a/.buildkite/scripts/tests.sh +++ b/.buildkite/scripts/tests.sh @@ -2,6 +2,7 @@ set -euo pipefail . .buildkite/scripts/verify-git-toolchain.sh +. .buildkite/scripts/security-tools.sh npm run check node scripts/write-tabellio-evidence-envelope.mjs --out tabellio-pr-evidence.json node scripts/check-tabellio-evidence-envelope.mjs --evidence tabellio-pr-evidence.json diff --git a/.github/workflows/product-validation.yml b/.github/workflows/product-validation.yml index d7aace2..480bb6c 100644 --- a/.github/workflows/product-validation.yml +++ b/.github/workflows/product-validation.yml @@ -28,6 +28,8 @@ jobs: test -x "$postgres_bin/initdb" test -x "$postgres_bin/pg_ctl" echo "$postgres_bin" >> "$GITHUB_PATH" + - name: Install pinned security scanners + run: bash .buildkite/scripts/security-tools.sh - name: Resolve squash-merge checkpoint revision id: checkpoint if: github.event_name == 'push' diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index c4f672d..2963462 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -32,6 +32,8 @@ jobs: with: node-version: 20 package-manager-cache: false + - name: Install pinned security scanners + run: bash .buildkite/scripts/security-tools.sh - name: Run repository checks run: npm run check @@ -49,6 +51,8 @@ jobs: with: node-version: 20 package-manager-cache: false + - name: Install pinned security scanners + run: bash .buildkite/scripts/security-tools.sh - name: Install pinned Fallow run: npm install --global fallow@2.89.0 c8@10.1.3 - name: Measure provenance function and statement coverage diff --git a/.tabellio/validators.json b/.tabellio/validators.json index 77f1062..dca21c9 100644 --- a/.tabellio/validators.json +++ b/.tabellio/validators.json @@ -26,10 +26,10 @@ "summary": "Agent run and review lifecycle examples" }, "security": { - "commands": [["node", "scripts/check-tabellio-external-actions.mjs", "--evidence", "examples/tabellio-evidence/minimal-evidence.json"]], + "commands": [["node", "scripts/check-tabellio-external-actions.mjs", "--evidence", "examples/tabellio-evidence/minimal-evidence.json"], ["node", "scripts/check-provenance-security.mjs"]], "metrics": [{"name": "external_action_policy_pass", "unit": "boolean", "passValue": 1, "failValue": 0}], "cost": {"telemetry": "available", "usd": 0, "modelCalls": 0, "toolCalls": 0}, - "summary": "External-action and side-effect policy checks" + "summary": "External-action policy and real candidate-bound security scanner regressions" }, "analytics-static": { "commands": [["node", "--test", "tests/analytics-core.test.mjs"]], diff --git a/README.md b/README.md index ba806c1..3d47cad 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,45 @@ Git and PostgreSQL operations are real; Plane, Entire, GitHub, Buildkite, and security observations in this sample are explicitly synthetic. This is not a live-provider or security-scanner certification. +The demo receipt includes a failure matrix with expected and actual verdicts, +safe reasons, and lineage digests where available. It exercises missing, stale, +conflicting, tampered, secret, failed-validation, outage, and moved-base cases. +Source replay runs twice in a newly created local database, verifies the original +digest, and checks that source snapshots and Git refs remain unchanged. Cleanup +and local cost/time are recorded even when the demo fails. + +To replace the sample security observation with real bounded checks, install +Gitleaks 8.30.1 and ast-grep 0.45.1 on `PATH`, then run: + +```bash +node scripts/demo-provenance.mjs --verify-security-scanners true +``` + +On Apple Silicon macOS or x86-64 Linux, `. .buildkite/scripts/security-tools.sh` +installs the pinned tools into a temporary directory and exposes them on `PATH`. +Set `TABELLIO_SECURITY_TOOLS_DIR` to reuse a tool directory. The installer verifies +the Gitleaks archive checksum and does not run npm package install scripts. +Configured CI uses this setup before checks; the required security +validator fails when scanners are missing instead of skipping scanner fixtures. +Run the same required check locally with `npm run tabellio:provenance:security:check`. + +The scanner reads immutable Git blobs without running candidate code. It uses +Gitleaks defaults and explicit JavaScript/TypeScript rules for unverified JWT +decoding, unsigned JWT configuration, disabled TLS verification, and dynamic +`eval`. Candidate ignore files and suppression comments cannot grant a pass. +Insecure HTTP dependency references fail; other declared npm dependencies remain +blocked pending vulnerability evidence. These checks cover the listed rules; +they do not establish the absence of all authorization or dependency defects. + +`tabellio-provenance security --input --repo --now