Orchestrata Ultimate › Gates › secrets
Gate: secrets
| Purpose | Make it structurally hard to commit a plaintext secret, not just discouraged |
| Where it runs | scripts/hooks/pre-commit, first stage, before any review; also callable by hand |
| Trigger | Every git commit (it runs whether or not Python is staged) |
| Exact command | .venv/bin/python scripts/check_staged_secrets.py --repo-root <repo> (falls back to python3 when the checkout has no venv) |
| Blocks | Exit 1 = violation(s); exit 2 = could not determine what is staged ("never silently treated as clean"); exit 0 = clean |
| Ruling / ADR | none recorded by id; the module docstring cites the secrets design in scripts/secrets_cli.py. AD-85 (Mohamed, 2026-10-17) added the punctuation-only exemption (ef54b11a8) |
Why it exists
From the module docstring (AST toolkit): SOPS and age keep the tracked
secrets/env.sops.env safe to commit, "but nothing stops a developer (or an agent) from
pasting a live key straight into a .env that gets accidentally git add-ed, or
hardcoding one in source." The gate scans what would actually land: the staged blob
(git show :<path>), not the working-tree file, so a partially staged file is judged by
its staged content. Since 9454e2dc0 (2026-08-13) it judges the staged added lines
(staged_added_lines), because scanning whole files "demanded --no-verify" on every spec
edit.
What it checks
- Exempt path:
secrets/env.sops.env, the ciphertext that is meant to carryENC[...]values. - Assignment shape (
_ASSIGNMENT_RE): a dotenv lineNAME=value, a shellexport NAME=value, or a module-levelNAME = "value"near the start of a line;==is excluded and a trailing kwarg comma is absorbed. - Credential-shaped NAME (
_SECRET_NAME_RE, case-insensitive):SECRET,PASSWORD,PASSWD,PASSPHRASE,CREDENTIAL(S),APIKEY, a whole-wordTOKENor*_TOKEN, andKEYonly with a qualifier that supplies the credential sense (API,SECRET,PRIVATE,ACCESS,SIGNING,SSH,ROOT,ADMIN,SERVICE,WEBHOOKand others). A bareKEYorAUTHis not enough: the first version matched eighteen Redis key names on one commit. - Value: not empty, not a placeholder (
changeme,placeholder,xxx,todo,example,dummy,none,nulland the rest ofPLACEHOLDER_VALUES), not an f-string interpolation, and (AD-85) not a value with no letter and no digit. - Waivers: lines waived through
core.gate_waivers.waived_lines, the one reader of the# gate-ok: <reason>convention.
Typical failures and how to fix them
| Case | What happened | Fix |
|---|---|---|
ROOT_ENV_KEY = "ORCHESTRATA_ROOT" |
a constant NAME ending _KEY with a qualifier is credential-shaped (improvement log D2, 2026-10-22) |
Rename it (3d77084d0 renamed it ROOT_ENV_VAR); never bypass |
A test fixture api_key = "sk-or-v1-" + ... or secret = ... |
test fixtures count (D10, 2026-10-22) | Give the planted value a neutral local name, for example planted |
A real key pasted into .env and staged |
the gate did its job | Unstage it; keep secrets in the SOPS file |
| Exit 2 | not a git repo or git unavailable | Run from inside the checkout |
Check before committing (brief template):
git add <files> && .venv/bin/python scripts/check_staged_secrets.py --repo-root .
Measured cost
reports/precommit-walls.ndjson records secrets_s for every commit; across the ledger
the median is 0.13 s.
Related
- Gates — master index
- Gate: review-staged (the next pre-commit stage)
- Gate: commit-precheck
- Workflow — the commit gate
- Bug fixes