Skip to content

Commit be36bb8

Browse files
committed
ci(security): add gitleaks secret scanning
1 parent 0d0c5c4 commit be36bb8

7 files changed

Lines changed: 299 additions & 0 deletions

File tree

.github/CODEOWNERS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Security scanning controls require the repository security owner.
2+
/.gitleaksignore @coreyt
3+
/.pre-commit-config.yaml @coreyt
4+
/.github/workflows/gitleaks.yml @coreyt

.github/workflows/gitleaks.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: gitleaks
2+
3+
on:
4+
pull_request:
5+
branches: [main]
6+
push:
7+
branches: [main]
8+
workflow_dispatch:
9+
schedule:
10+
- cron: "17 4 * * 1"
11+
12+
permissions:
13+
contents: read
14+
pull-requests: read
15+
16+
jobs:
17+
scan:
18+
name: scan
19+
runs-on: ubuntu-latest
20+
timeout-minutes: 5
21+
steps:
22+
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
23+
with:
24+
fetch-depth: 0
25+
- uses: gitleaks/gitleaks-action@e0c47f4f8be36e29cdc102c57e68cb5cbf0e8d1e # v3
26+
env:
27+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28+
GITLEAKS_VERSION: "8.30.0"
29+
GITLEAKS_ENABLE_COMMENTS: "false"
30+
GITLEAKS_ENABLE_UPLOAD_ARTIFACT: "false"
31+
GITLEAKS_ENABLE_SUMMARY: "false"

.gitleaksignore

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
dev/plans/runs/0.5.1-design-review-20260625T003640Z.md:generic-api-key:4872
2+
dev/plans/runs/0.5.1-design-review-20260625T003640Z.md:generic-api-key:4884
3+
dev/plans/runs/0.5.1-design-review-20260625T003640Z.md:generic-api-key:4917
4+
docs/getting-started/installation.md:curl-auth-header:78
5+
scripts/setup.sh:curl-auth-header:89
6+
tests/conftest.py:generic-api-key:141
7+
tests/test_admin_tokens.py:generic-api-key:20
8+
tests/test_admin_tokens.py:generic-api-key:74
9+
tests/test_client_identity.py:generic-api-key:166
10+
tests/test_client_identity.py:generic-api-key:204
11+
tests/test_client_identity.py:generic-api-key:228
12+
tests/test_fast_guardian.py:generic-api-key:30
13+
tests/test_fast_guardian.py:generic-api-key:34
14+
tests/test_fast_guardian.py:generic-api-key:57
15+
tests/test_fast_monitor.py:generic-api-key:52
16+
tests/test_fast_monitor.py:generic-api-key:64
17+
tests/test_fast_monitor.py:generic-api-key:97
18+
tests/test_guardrail_overrides.py:generic-api-key:14
19+
tests/test_mcp_tool_guard.py:generic-api-key:118
20+
tests/test_paid_services.py:generic-api-key:160
21+
tests/test_paid_services.py:generic-api-key:175
22+
tests/test_response_scanner.py:generic-api-key:151
23+
10bbda93e66bbd3c3c373ec9c830e02ee3b1bbbf:tests/test_fast_guardian.py:generic-api-key:53
24+
24c16b891855871c50e4730de88212dd2ab45a4f:docs/getting-started/installation.md:curl-auth-header:66
25+
285fecf6b6290f686df58912f6fdb45d9484125d:tests/test_mcp_tool_guard.py:generic-api-key:116
26+
285fecf6b6290f686df58912f6fdb45d9484125d:tests/test_response_scanner.py:generic-api-key:150
27+
404b9e7e28429e718ad09bff674c577fdc543fcf:tests/test_client_identity.py:generic-api-key:196
28+
404b9e7e28429e718ad09bff674c577fdc543fcf:tests/test_client_identity.py:generic-api-key:214
29+
4218d2b2de93abeca9dc837dccd3be35dfe8f578:tests/conftest.py:generic-api-key:48
30+
4218d2b2de93abeca9dc837dccd3be35dfe8f578:tests/test_fast_guardian.py:generic-api-key:24
31+
4218d2b2de93abeca9dc837dccd3be35dfe8f578:tests/test_fast_guardian.py:generic-api-key:28
32+
4218d2b2de93abeca9dc837dccd3be35dfe8f578:tests/test_fast_monitor.py:generic-api-key:22
33+
4218d2b2de93abeca9dc837dccd3be35dfe8f578:tests/test_fast_monitor.py:generic-api-key:33
34+
4218d2b2de93abeca9dc837dccd3be35dfe8f578:tests/test_fast_monitor.py:generic-api-key:43
35+
819406e902ba0e0c505f8bd0d69b44bfe2032842:tests/test_fast_monitor.py:generic-api-key:35
36+
8f6b151ef72bcf38dc967395f12ef8f63844e384:scripts/setup.sh:curl-auth-header:87
37+
daf23856dcf634aa3eb0248502ba3e13cdd0c7fe:README.md:curl-auth-header:74
38+
f5626f2959006ab8636c237162388765f9b38251:tests/test_guardrail_overrides.py:generic-api-key:15

.pre-commit-config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
repos:
2+
- repo: https://github.com/gitleaks/gitleaks
3+
rev: v8.30.0
4+
hooks:
5+
- id: gitleaks
6+
stages: [pre-commit]
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# 0.5.15 Slice 10 — Gitleaks secret-scan implementation plan
2+
3+
**Status:** repository-controlled implementation complete locally — updated
4+
2026-08-15. External GitHub merge-blocking enforcement remains pending; see
5+
`10-gitleaks-ci-secret-scan-status.md` for the exact evidence and limitation.
6+
7+
## Admission check
8+
9+
| Gate | Current evidence | Required before implementation starts |
10+
| --- | --- | --- |
11+
| Previous slice closure | Slices 0–5 and Slice 6 are complete; Slice 6 includes Slice 10 with external enforcement conditional. | **Passed.** Recheck the decision and all changes since this plan before beginning the slice. |
12+
| Draft contract | Slice 3 proposes DFR-35/DAC-35, and Slice 4 confirms isolated architecture alignment. | Ratify the draft as the Slice 10 contract at slice start. |
13+
| Security design review | `70-gitleaks-ci-secret-scan-design.md` and review `71` conditionally pass. | Security reviewer confirms the current pins, 38-entry baseline, and external-enforcement posture still match the accepted design. |
14+
| Repository authority | Wake records approval of the least-privilege control direction and scoped scanner ownership. | GitHub administrator confirms authority for a disposable PR, required check, branch protection, and code-owner enforcement. |
15+
16+
Until all four gates pass, do not commit, push, enable the workflow, configure
17+
GitHub settings, add dependencies, or claim that merge-blocking enforcement is
18+
active.
19+
20+
## Changes since the original design
21+
22+
| Original assumption | Current reconciled state | Plan update |
23+
| --- | --- | --- |
24+
| Slice 0 found an incomplete Python 3.11 environment. | The worktree was rebuilt on Python 3.12.3 with `make sync && make verify`; the ordinary non-live CI baseline, docs, lint, mypy, Docker build, and audit passed. | Treat this as a healthy pre-feature baseline only. Slice 10 has no Python/runtime dependency and must not add one. |
25+
| Local and historical baselines needed reconciliation. | `.gitleaksignore` now has 38 exact entries: the original local findings plus the 16 reviewed commit-qualified history findings described by design review 71. | Re-review the entries as exact fingerprints; do not broaden paths, disable rules, or add wildcard suppression. Prove both directory and history scans before commit. |
26+
| Action SHAs were to be resolved. | The untracked workflow pins checkout v6 to `d23441a48e516b6c34aea4fa41551a30e30af803` and Gitleaks Action v3 to `e0c47f4f8be36e29cdc102c57e68cb5cbf0e8d1e`; scanner remains `8.30.0`. | Independently reconfirm release provenance at slice start. Do not substitute mutable tags or combine this work with Actions-major PR #44. |
27+
| Gitleaks and pre-commit tooling were absent from `PATH`. | They remain absent; only `actionlint` is presently available. | Use a reviewed, ephemeral pinned local-tool invocation or a checksum-verified Gitleaks 8.30.0 binary without adding it to `pyproject.toml`, `uv.lock`, Docker, or Airlock runtime. Record the exact invocation and version in the slice status. |
28+
| Controls were proposed repository changes. | `.gitleaksignore`, `.pre-commit-config.yaml`, `.github/CODEOWNERS`, and `.github/workflows/gitleaks.yml` are still untracked worktree baseline. | Preserve them exactly until review; stage only these named files and the Slice 10 status/documentation files—never broad staging. |
29+
30+
## Scope and non-goals
31+
32+
**In scope:** an isolated Gitleaks GitHub workflow; a Gitleaks 8.30.0
33+
pre-commit hook; a narrow exact-fingerprint baseline; scanner-file code
34+
ownership; minimal developer instructions; local, disposable-PR, and external
35+
GitHub-enforcement evidence.
36+
37+
**Out of scope:** Airlock runtime code, `config.yaml`, Docker image, provider
38+
credentials, deployment/release workflows, Dependabot PRs, Action major
39+
upgrades, GitHub Advanced Security/code scanning uploads, broad scanner
40+
exceptions, and any secret-management redesign.
41+
42+
## Implementation sequence
43+
44+
1. **Ratify and snapshot scope.** Record the Slice 6 include decision, DFR-35/
45+
DAC-35 acceptance criteria, designated security owner, GitHub administrator,
46+
and the reviewed file list. Recheck that the branch/worktree still contains
47+
only the known untracked baseline; preserve unrelated user files.
48+
2. **Re-verify supply chain and baseline.** Independently verify both full
49+
Action SHAs against the stated official releases. Obtain the exact Gitleaks
50+
`8.30.0` scanner by a reviewed ephemeral/checksum-verified path. Run
51+
redacted `gitleaks dir .` and `gitleaks git .`; review any result
52+
individually. A clean result is required before the controls are committed.
53+
3. **Record RED evidence.** In a disposable branch/PR, add a synthetic,
54+
non-usable detector fixture that is designed to be redacted. Demonstrate
55+
local-hook and CI failure without copying the value to logs, plans, or PR
56+
comments; remove the fixture immediately after evidence capture.
57+
4. **Review and commit the repository controls.** Review only
58+
`.pre-commit-config.yaml`, `.gitleaksignore`, `.github/CODEOWNERS`, and
59+
`.github/workflows/gitleaks.yml`. Confirm stable `gitleaks / scan` naming,
60+
full checkout, PR/push/manual/weekly triggers, read-only permissions, no
61+
`pull_request_target`, no OIDC/write scope, and comments/artifact/summary
62+
disabled. Add a short developer instruction only if review identifies an
63+
existing authoritative location; do not create duplicate authority.
64+
5. **Record GREEN evidence.** Re-run local directory/history scans, the staged
65+
pre-commit hook, and `actionlint`. Open a disposable PR, prove a redacted CI
66+
failure then a green run after fixture removal; validate manual dispatch and
67+
a normal `main`-equivalent push only through authorized repository workflow.
68+
6. **Apply external enforcement.** The GitHub administrator configures `main`
69+
to require `gitleaks / scan` and code-owner review for the three scanner
70+
files. Prove a failing scan and an unapproved scanner/baseline modification
71+
cannot merge. Do not report this as active before this external evidence.
72+
7. **Close the slice.** Write `10-gitleaks-ci-secret-scan-status.md` with
73+
ratified contract, exact pins/version, baseline review count, commands and
74+
redacted results, CI/PR URLs, GitHub-rule evidence, residual runtime-binary
75+
provenance risk, and rollback disposition.
76+
77+
## Required verification and acceptance evidence
78+
79+
| Boundary | Evidence |
80+
| --- | --- |
81+
| Local detection parity | Gitleaks `8.30.0` directory and reachable-history scans both produce zero reviewed findings; the hook invokes the same scanner version. |
82+
| False-positive discipline | Baseline entries are exact fingerprints, individually reviewed, and line/path or commit changes create a new finding. No rule, directory, or generic test/docs exemption is introduced. |
83+
| Workflow isolation | `actionlint` passes; workflow has only `contents: read` and `pull-requests: read`, full checkout, no repository/deployment secrets, no artifact/SARIF/comment/summary publication, and no runtime invocation. |
84+
| Detection regression | Disposable synthetic fixture fails locally and in CI with redaction, then removal restores green. The fixture is not retained in mainline or copied into durable evidence. |
85+
| Merge enforcement | GitHub administrator supplies branch-protection and code-owner proof that the stable check and designated review cannot be bypassed on `main`. |
86+
| Existing CI health | Re-run the affected workflow validation plus the repository’s ordinary CI evidence proportionate to touched files; no Python dependency or product-test expansion is expected. |
87+
88+
## Failure handling and rollback
89+
90+
- A new finding is treated as potentially real until security review proves it
91+
is a non-usable fixture. Rotate/revoke any usable credential before changing
92+
a baseline.
93+
- A false positive is resolved only by a reviewed exact fingerprint after
94+
preserving detector coverage. Never suppress the rule or an entire path to
95+
unblock a merge.
96+
- If the workflow itself is defective, revert the named scanner-control commit
97+
or have the GitHub administrator temporarily remove the required check with a
98+
recorded security decision. Do not use an unprotected direct push as a
99+
workaround.
100+
- If the official Action’s runtime-binary provenance becomes unacceptable,
101+
pause enforcement and design a separately reviewed digest-pinned alternative;
102+
do not add a second divergent scanner as an emergency replacement.
103+
104+
## Completion criteria
105+
106+
Slice 10 closes only when DFR-35/DAC-35 is ratified, repository controls are
107+
committed and reviewable, all local/CI redacted evidence passes, the controlled
108+
failure proof is complete, the GitHub administrator has demonstrated required
109+
check and code-owner enforcement, and the status record names all residual
110+
risks. If external GitHub authority is unavailable, close only the
111+
repository-controlled portion as conditional and explicitly state that
112+
merge-blocking enforcement is not active.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# 0.5.15 Slice 10 — Gitleaks secret-scan status
2+
3+
**Status:** repository-controlled implementation complete locally — 2026-08-15.
4+
GitHub branch-protection and code-owner enforcement are **not active yet**;
5+
authenticated external verification and enforcement are in progress.
6+
7+
## Ratified contract and changed controls
8+
9+
Slice 6 includes Slice 10. DFR-35/DAC-35 is now ratified in
10+
`dev/requirements.md`: the control is a non-deploying Gitleaks `8.30.0`
11+
pre-commit hook plus an isolated `gitleaks / scan` workflow. It has no Airlock
12+
runtime, configuration, image, provider, or deployment-credential impact.
13+
14+
| File | Delivered control |
15+
| --- | --- |
16+
| `.pre-commit-config.yaml` | Staged Gitleaks hook from the official `v8.30.0` repository revision. It is a developer-local installation step and is not a Python project dependency. |
17+
| `.gitleaksignore` | 38 individually reviewed exact fingerprints: local fixture findings plus commit-qualified history findings. No broad path/rule exception or inline allow comment is present. |
18+
| `.github/workflows/gitleaks.yml` | Isolated `gitleaks / scan` workflow on PR/push to `main`, manual dispatch, and weekly history scan; full checkout; scanner `8.30.0`; read-only permissions; comments, artifacts, and summaries disabled. |
19+
| `.github/CODEOWNERS` | `@coreyt` owns the baseline, hook configuration, and workflow paths. This becomes enforcement only after GitHub branch rules require code-owner review. |
20+
| `dev/requirements.md` | Ratified DFR-35/DAC-35 contract and acceptance boundary. |
21+
22+
## Pin and scanner provenance
23+
24+
The workflow’s full-SHA pins were independently checked with read-only remote
25+
tag lookups:
26+
27+
| Component | Required tag | Verified SHA |
28+
| --- | --- | --- |
29+
| `actions/checkout` | `v6` | `d23441a48e516b6c34aea4fa41551a30e30af803` |
30+
| `gitleaks/gitleaks-action` | `v3` / `v3.0.0` | `e0c47f4f8be36e29cdc102c57e68cb5cbf0e8d1e` |
31+
| Local verifier | Gitleaks `v8.30.0` | `ghcr.io/gitleaks/gitleaks@sha256:691af3c7c5a48b16f187ce3446d5f194838f91238f27270ed36eef6359a574d9` |
32+
33+
The container digest was used read-only against the worktree and shared Git
34+
metadata. It verifies the specified scanner version; the GitHub Action’s own
35+
runtime-binary download remains the accepted no-secret-job residual risk from
36+
the design review.
37+
38+
## RED/GREEN and local verification evidence
39+
40+
| Check | Result |
41+
| --- | --- |
42+
| `gitleaks dir . --redact` | Passed; 18.47 MB scanned, no leaks found. |
43+
| `gitleaks git . --redact` | Passed; 686 commits / 10.43 MB scanned, no leaks found. The shared `.git` directory was mounted read-only because this is a linked worktree. |
44+
| Controlled RED detector fixture | Passed: a temporary synthetic non-usable `sk-test-…` fixture produced one finding with `--redact`; it was deleted immediately and never added to the worktree. |
45+
| Configured hook | Passed via ephemeral `pre-commit==4.1.0`: `pre-commit run gitleaks --all-files`. No project dependency, lockfile, or image change. |
46+
| Workflow syntax | `actionlint .github/workflows/gitleaks.yml` passed. |
47+
| Baseline integrity | 38 entries, no duplicate fingerprints. |
48+
| Ordinary local readiness | `make ensure-spacy && make verify` passed after restoring `en_core_web_lg==3.8.0`; strict MkDocs build and documentation contract tests passed (8). |
49+
50+
The first container history scan could not see the linked worktree’s common Git
51+
metadata and scanned zero commits. Re-running with that metadata mounted
52+
read-only produced the 686-commit clean result above; this was a test-topology
53+
correction, not a scanner suppression.
54+
55+
## Outstanding external acceptance criteria
56+
57+
The local/repository-control portion is ready for focused commit and push. The
58+
following remains incomplete and must not be represented as active:
59+
60+
1. A GitHub administrator must configure `main` to require the stable
61+
`gitleaks / scan` check and code-owner review for the three scanner-control
62+
files.
63+
2. A disposable PR must show a redacted CI failure for the synthetic fixture,
64+
then a green result after removal; manual-dispatch, main-push, and fork-PR
65+
behavior must be evidenced without repository secrets or elevated token.
66+
3. The branch rule must prove that a scanner/baseline change cannot merge
67+
without its designated owner.
68+
69+
Authenticated `gh` inspection confirms that `main` currently has no legacy
70+
branch-protection rule. The only `protect-main` ruleset is disabled and covers
71+
deletion/non-fast-forward only. Apply the narrow required-check/code-owner rule
72+
only after the feature PR has produced the stable `gitleaks / scan` check, then
73+
record the resulting PR/run URLs and branch-rule response before closing Slice
74+
10 as merge-blocking.
75+
76+
## Residual risk and rollback
77+
78+
False positives require individual security review and an exact fingerprint;
79+
they are not resolved by disabling a detector or excluding a directory. A
80+
usable finding requires rotation/revocation before any baseline action. If the
81+
workflow itself proves defective, revert only the named scanner-control change
82+
or have an administrator temporarily remove the required check with a recorded
83+
security decision—never bypass it by direct unprotected push.

dev/requirements.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,3 +344,28 @@ single-owner and optional. Selected separate-process reads SHALL use a
344344
loopback-only proxy-admin bridge (and require its local admin configuration),
345345
never a second engine open. A FathomDB erasure receipt SHALL continue to state
346346
that JSONL retention/deletion is a separate obligation.
347+
348+
---
349+
350+
## 0.5.15 ratified requirements
351+
352+
### DFR-35 / DAC-35: Secret-scan delivery control
353+
354+
Airlock's repository SHALL use a dedicated, non-deploying Gitleaks control at
355+
scanner version `8.30.0`: a staged pre-commit hook and an isolated GitHub
356+
Actions `gitleaks / scan` job for pull requests to `main`, pushes to `main`,
357+
manual dispatch, and scheduled reachable-history scans. The workflow SHALL use
358+
full-SHA-pinned actions, a full checkout, and only `contents: read` and
359+
`pull-requests: read` permissions. It SHALL not use `pull_request_target`,
360+
write/OIDC permissions, repository/deployment secrets, comments, artifact or
361+
SARIF upload, or result summaries.
362+
363+
The baseline SHALL contain only individually reviewed exact fingerprints;
364+
broad path/rule exclusions and inline allow comments are prohibited. Scanner
365+
configuration files SHALL have designated code ownership, and `main` SHALL
366+
require both that review and the stable `gitleaks / scan` check once a GitHub
367+
administrator configures the external rule. Directory and reachable-history
368+
scans must be clean for the reviewed baseline; a synthetic non-usable detector
369+
fixture must fail redacted and pass after removal. The control SHALL not alter
370+
Airlock runtime behavior, configuration, image contents, or provider/deployment
371+
credential access.

0 commit comments

Comments
 (0)