diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f45b60a7..b6de6b39b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,19 +48,6 @@ jobs: with: persist-credentials: false - # Keep source scanning in the required Gate job so a finding blocks merge. - # Scan docs and generated assets too; the container only sees runtime files. - - name: Scan repository for secrets - uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0 - with: - scan-type: fs - scan-ref: . - version: v0.74.0 - scanners: secret - exit-code: 1 - format: table - cache: false - - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod diff --git a/.github/workflows/leaks.yml b/.github/workflows/leaks.yml new file mode 100644 index 000000000..ed0a0dc21 --- /dev/null +++ b/.github/workflows/leaks.yml @@ -0,0 +1,49 @@ +name: Leaks + +# Secrets, private AI session links and local workstation paths stay out of +# this public repository. The rules live in .gitleaks.toml. +on: + push: + branches: [main] + pull_request: + branches: [main] + types: [opened, edited, synchronize, reopened] + +permissions: + contents: read + +env: + GITLEAKS_VERSION: 8.30.1 + GITLEAKS_SHA256: 551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb + +jobs: + leaks: + name: Leaks + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Install gitleaks + run: | + archive="$RUNNER_TEMP/gitleaks.tar.gz" + curl -fsSLo "$archive" "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" + echo "${GITLEAKS_SHA256} $archive" | sha256sum -c - + tar -xzf "$archive" -C "$RUNNER_TEMP" gitleaks + + - name: Scan files + run: '"$RUNNER_TEMP/gitleaks" dir . --redact --no-banner' + + # Edits to the description rerun this job, so the text is checked as posted. + - name: Scan pull request text and commit messages + if: github.event_name == 'pull_request' + env: + TITLE: ${{ github.event.pull_request.title }} + BODY: ${{ github.event.pull_request.body }} + BASE: ${{ github.event.pull_request.base.sha }} + HEAD: ${{ github.event.pull_request.head.sha }} + run: | + { printf '%s\n%s\n' "$TITLE" "$BODY"; git log --format=%B "$BASE..$HEAD"; } | + "$RUNNER_TEMP/gitleaks" stdin --redact --no-banner diff --git a/.gitleaks.toml b/.gitleaks.toml index 4466753f5..28487fb2a 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -1,5 +1,7 @@ -# Default rules, plus known non-secrets: a fixed test-only auth code secret, -# a redaction fixture, a cache key name, and content hashes in fixtures. +# Default secret rules, plus links and paths this public repository must never +# publish, and known non-secrets: a fixed test-only auth code secret, a +# redaction fixture, a cache key name, and content hashes in fixtures. +# Lefthook and the Leaks workflow read this file; add a rule here to extend both. [extend] useDefault = true @@ -12,9 +14,20 @@ regexes = [ ] [[allowlists]] -description = "content hashes in fixtures and the retired Atlas checksum file" +description = "content hashes in fixtures" paths = [ '''^ui/host/e2e/data/mcp\.json$''', '''^ui/host/tests/go-fragments/[^/]+\.json$''', - '''^internal/db/migrations/atlas\.sum$''', ] + +[[rules]] +id = "private-ai-session-link" +description = "Private AI session link or session trailer" +regex = '''(?i)(claude\.ai/(code|chat|share|project)/|Claude-Session:|chatgpt\.com/(c|share|codex)/)''' +keywords = ["claude", "chatgpt"] + +[[rules]] +id = "local-workstation-path" +description = "Local workstation home directory path" +regex = '''(/Users|/home)/[A-Za-z0-9_.-]+/''' +keywords = ["/users/", "/home/"] diff --git a/AGENTS.md b/AGENTS.md index 1cfc90850..3fa15c313 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,6 +39,8 @@ - Keep credentials, private telemetry, local workstation paths, and private AI session links out of tracked files, commit messages, and pull request descriptions. + `.gitleaks.toml` holds the rules; lefthook and the Leaks workflow enforce them. + Squash merges take the pull request title and description. - Keep local agent caches, indexes, evaluation records, and browser evidence out of both Git and the Docker build context. Git ignores do not exclude Docker inputs. - Design proposals live in `docs/proposals/NNNN-slug.md` and decision records diff --git a/lefthook.yml b/lefthook.yml index cab4a0eba..c4ca6fc70 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -12,6 +12,10 @@ pre-commit: parallel: false jobs: + # Secrets, private AI session links and local paths, by .gitleaks.toml. + - name: leaks + run: gitleaks git --pre-commit --staged --redact --no-banner + - name: merge-conflict glob: "*" # Minified single-file bundles legitimately contain long runs of `=`, so @@ -39,6 +43,11 @@ pre-commit: glob: "{ui/host/**/*.{ts,tsx},ui/panels/**/*.{ts,tsx},ui/*.ts}" run: cd ui/host && bun run lint +commit-msg: + jobs: + - name: leaks + run: gitleaks stdin --redact --no-banner < {1} + pre-push: jobs: - name: check