diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml index e652404..ea2aeff 100644 --- a/.github/workflows/secret-scan.yml +++ b/.github/workflows/secret-scan.yml @@ -1,38 +1,80 @@ -# Secret-scan backstop. The allowlist export is the gate; this is the seatbelt. -# Uses the gitleaks BINARY (Apache-2.0, free) — NOT gitleaks-action, which -# requires a paid GITLEAKS_LICENSE for organization repos. Scans the current tree -# (--no-git) so it blocks NEW secrets/infra strings without re-flagging old history. +# Secret-scan backstop. Historical exceptions are exact reviewed fingerprints. name: secret-scan on: push: pull_request: +permissions: + contents: read + jobs: gitleaks: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 - - name: install gitleaks + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Install checksum-verified Gitleaks + shell: bash run: | - curl -sSL -o /tmp/gl.tgz \ + set -euo pipefail + curl --fail --show-error --silent --location -o /tmp/gitleaks.tar.gz \ https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz - tar -xzf /tmp/gl.tgz -C /tmp gitleaks + echo "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb /tmp/gitleaks.tar.gz" | sha256sum --check + tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks sudo install /tmp/gitleaks /usr/local/bin/gitleaks - - name: scan working tree - run: gitleaks detect --source . --no-git --config .gitleaks.toml --redact --verbose + - name: Scan complete reachable history + run: gitleaks git . --log-opts=HEAD --config .gitleaks.toml --redact --verbose + - name: Prove broad placeholder labels cannot suppress a secret + shell: bash + run: | + set -euo pipefail + tmp="$(mktemp -d)" + git -C "$tmp" init --quiet + git -C "$tmp" config user.name "Secret Scan Control" + git -C "$tmp" config user.email "security-control@example.invalid" + half='0123456789abcdef0123456789abcdef' + printf 'example_private_key = 0x%s%s\n' "$half" "$half" > "$tmp/leak.txt" + git -C "$tmp" add leak.txt + git -C "$tmp" commit --quiet -m "synthetic leak" + if gitleaks git "$tmp" --log-opts=HEAD \ + --config "$GITHUB_WORKSPACE/.gitleaks.toml" --redact; then + echo "::error::Gitleaks accepted the committed negative-control secret" + exit 1 + fi + echo "Gitleaks rejected the committed negative-control secret as expected" infra-strings: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 + timeout-minutes: 5 steps: - - uses: actions/checkout@v4 - - name: block infra / local-path leaks + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Reject operational infrastructure strings + shell: bash run: | - set -e - patt='172\.(30|31)\.[0-9]+\.[0-9]+|/Users/[a-z0-9._-]+/|aipgcoregen|BEGIN (RSA |EC |OPENSSH |)PRIVATE KEY' - if git grep -nIE "$patt" -- . ':(exclude).gitleaks.toml' ':(exclude)**/secret-scan.yml'; then - echo "::error::infra/secret string found above — scrub before merge"; exit 1 + set -euo pipefail + pattern='172\.(30|31)\.[0-9]+\.[0-9]+|/Users/[a-z0-9._-]+/|aipgcoregen|aipg-transient|BEGIN ((RSA|EC|OPENSSH) )?PRIVATE KEY' + for sample in \ + '172.''30''.1.2' \ + '/Us''ers/operator/' \ + 'aipgcore''gen' \ + 'aipg-trans''ient' \ + 'BEGIN ''PRIVATE KEY' \ + 'BEGIN OPEN''SSH PRIVATE KEY'; do + if ! printf '%s\n' "$sample" | grep -Eq "$pattern"; then + echo "::error::infra-string scanner failed its negative control" + exit 1 + fi + done + result=0 + matches="$(git grep -nIE "$pattern" -- . ':(exclude).gitleaks.toml' ':(exclude)**/secret-scan.yml')" || result=$? + if [ "$result" -gt 1 ]; then + echo "::error::infra-string scanner failed to execute" + exit "$result" fi - if git ls-files | grep -iE 'security_audit|audit_report'; then - echo "::error::audit report file tracked — must not ship"; exit 1 + if [ "$result" -eq 0 ]; then + printf '%s\n' "$matches" + echo "::error::operational infrastructure or secret material found above" + exit 1 fi - echo "infra-string scan clean" diff --git a/.gitleaksignore b/.gitleaksignore new file mode 100644 index 0000000..239a2c5 --- /dev/null +++ b/.gitleaksignore @@ -0,0 +1 @@ +8992df8f44f8ef8827a4973141033d2799baa4d8:AGENTS.md:aipg-local-dev-path:54 diff --git a/AGENTS.md b/AGENTS.md index e8a27ca..35739c6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -51,6 +51,8 @@ submit/poll). The retired horde `/api/v2` async queue is NOT used. Published to - **`tests/`** — pytest suite (`test_client.py`, `test_grid.py`); uses `respx` to mock HTTP. Owned in its own AGENTS.md. - **`pyproject.toml`** — setuptools build, `src/` layout, deps, pytest config. +- **`.github/workflows/secret-scan.yml`, `.gitleaks.toml`, and `.gitleaksignore`** — + checksum-verified complete-history secret scanning with exact historical fingerprints only. - `dist/`, `.venv/`, `*.egg-info/` — build/vendored artifacts; do not edit or document. ## Local Contracts @@ -75,6 +77,8 @@ submit/poll). The retired horde `/api/v2` async queue is NOT used. Published to - `pytest` (install with `pip install -e ".[test]"`). Client and raw-Grid HTTP are mocked via `respx`, so no live Grid is required. +- `gitleaks git . --log-opts=HEAD --config .gitleaks.toml --redact --verbose` + scans the complete history reachable from the candidate commit. ## Child DOX Index