Files
prose-contracts/scripts/secret-allowlist.tsv
abiba-bot 8f1e5eebc4
PR Pipeline — Authorize → Validate → Review → Merge / auth (pull_request) Successful in 5s
PR Pipeline — Authorize → Validate → Review → Merge / validate (pull_request) Successful in 3s
PR Pipeline — Authorize → Validate → Review → Merge / lint (pull_request) Successful in 18s
PR Pipeline — Authorize → Validate → Review → Merge / ai-review (pull_request) Successful in 3s
PR Pipeline — Authorize → Validate → Review → Merge / gate (pull_request) Successful in 1s
feat(security): commit-time secret guard that FAILS the build on a committed credential
The 2026-09-17 purge removed six live credentials that had sat in this repo
for weeks, several in .md prose. Nothing blocked that class of commit, so a
warning in a stream nobody reads was the only signal. This adds a guard that
fails the build instead of warning.

Guard
- scripts/secret-scan.sh: bash + coreutils + grep/sed/awk + git only (the Gitea
  Actions runner executes job steps inside the runner container — BusyBox grep,
  no node/python). Modes: --tree (git-tracked, default), --path DIR (no git),
  --staged (pre-commit), --diff REF. Exit 1 on a finding, 2 on config error.
- scripts/secret-patterns.tsv: checked-in pattern list — sk-, sk-or-v1-,
  sk_live_, literal Bearer tokens, PVEAPIToken=, raw Authorization values, PEM
  private-key blocks, prose credential lines, and password/api_key/secret/token
  assignments carrying a literal value. Prose is scanned exactly like code.
- scripts/secret-allowlist.tsv: one entry per deliberate synthetic example, each
  with a reason. A missing reason is a hard error (fail closed). The 2026-09-17
  purge's `«vault: ...»` placeholders are listed explicitly rather than filtered
  by a general "vault"/"synthetic" rule, so a new occurrence still needs a
  reviewed, reasoned entry.
- A small inert-value classifier drops env refs, paths, dotted code access,
  variable names and right-truncated redactions; it does not know the words
  "synthetic"/"example", so a fabrication is always an explicit exception.
- Findings are printed with the credential masked; a scan never echoes a full
  secret into the log.

Wiring
- .gitea/workflows/pr-pipeline.yaml lint job: explicit "Committed-credential
  scan" step plus the self-test. A finding fails the required
  `pr-pipeline / lint` context, which the merge gate depends on.
- scripts/prose-lint.sh (the local gate): a "Secret scan" section, so
  `bash scripts/prose-lint.sh` before pushing is equivalent to CI.

Tests
- tests/test_secret_scan.sh: 20 cases. Plants pattern-matching fixtures in temp
  trees (outside every allowlisted path) and asserts the guard FAILS, including
  the --staged commit-time path; asserts the tree is quiet; asserts allowlisted
  text at an unlisted path still fails (path-explicit, not word-based); asserts
  a reasonless allowlist entry exits 2.

Verified: guard run against 8245716^ (the pre-fix revision, before the purge)
fails on the real OpenRouter/LiteLLM/Zulip/Proxmox/Stirling credentials; guard
run over the current tree is clean.
2026-09-22 15:04:37 +00:00

6.1 KiB

1# secret-allowlist.tsv — exceptions for scripts/secret-scan.sh, every entry with a reason.
2#
3# Format: <rule-id|*><TAB><path-glob><TAB><literal-substring><TAB><reason>
4# Blank lines and lines whose first field starts with '#' are ignored.
5# A finding is suppressed only when ALL THREE of rule, path and literal match:
6# * the rule id equals the finding's rule id, or is '*'
7# * the finding's repo-relative path matches <path-glob> (bash glob)
8# * the finding's line contains <literal-substring> verbatim
9# An entry whose reason is empty is a hard error (exit 2) — no silent exceptions.
10#
11# RULE: never allowlist a live credential, and never broaden an entry (rule '*',
12# a wide path glob, or a short generic literal) just to silence a finding.
13# If the finding is real, remove the credential from the file.
14#
15# Entries are one per deliberate synthetic example, so the file reads as an
16# audit trail of reviewed exceptions rather than a list of things to ignore.
17# Rule '*' is used only where the same literal is matched by more than one rule.
18#
19# ── The 2026-09-17 purge placeholders ─────────────────────────────────────
20# PR #112 replaced six live credentials with `«vault: <project>/<env> <SECRET>»`
21# references. Those references are safe by construction (they name where the
22# secret is read from), but they are listed here explicitly rather than being
23# deliberate, reasoned entry.
24secret-assignlitellm-api-keys.prose.mdMUMUNI_LITELLM_API_KEY=«vault: agents/production LITELLM_API_KEY»2026-09-17 purge: replaced the live Mumuni LiteLLM key with its vault reference; no literal credential.
25secret-assignlitellm-api-keys.prose.mdMUMUNI_ZULIP_API_KEY=«vault: agents/production ZULIP_API_KEY»2026-09-17 purge: replaced the live Mumuni Zulip key with its vault reference; no literal credential.
26*infrastructure-control.prose.mdPVEAPIToken=«vault: infrastructure/production PVE_API_TOKEN»2026-09-17 purge: Proxmox API token is read from the vault; the line only names the vault path.
27cred-proseinfrastructure-control.prose.mdAdmin credentials:2026-09-17 purge: the Stirling admin user/password are two `«vault: ...»` references; no literal credential.
28*scripts/daily-infra-report.pyPVEAPIToken=«vault: infrastructure/production PVE_API_TOKEN»2026-09-17 purge: Proxmox API token is read from the vault; the line only names the vault path.
29secret-assignstirling-pdf-agent-access.prose.md«vault: infrastructure/production STIRLING_API_KEY»2026-09-17 purge: Stirling PDF API key is read from the vault; the curl example only names the vault path.
30bearer-tokenagent-zero-fix-summary.md«vault: agents/production OPENROUTER_API_KEY»2026-09-17 purge: OpenRouter key is read from the vault; the example curl only names the vault path.
31# ── Deliberate synthetic examples in contracts (not from the purge) ───────
32# These exist to teach the rule they illustrate. They are listed here so the
33# example is always an explicit exception, never a pattern-level exemption.
34*hermes-key-enforcement.prose.mdsk-synthetic-external-exampleRule 15 illustration of a hardcoded external key that is tolerated; fabricated, never a live key.
35*hermes-key-enforcement.prose.mdsk-synthetic-example-12345Rule 15 illustration of a forbidden hardcoded key; fabricated, never a live key.
36openai-keyhermes-key-enforcement.prose.mdsk-synthetic-litellm-Fabricated key name inside a `grep 'LITELLM_API_KEY=...'` example; not a live key.
37secret-assignhermes-key-enforcement.prose.mdsk-NEW_KEYPlaceholder standing for the rotated key in an `infisical secrets set` command; not a literal key.
38openrouter-keyagent-zero-openrouter-key.prose.mdsk-or-v1-syntheticSynthetic key prefix in the contract's example response; the real key is read from the vault.
39openai-keylitellm-api-keys.prose.mdsk-synthetic-tanko-exampleFabricated key name in migration history prose; not a live key.
40openai-keylitellm-self-heal.prose.mdsk-syslog-local-master-keyDeprecated local LiteLLM master key name documented as no-live-usage; kept for history, not a usable credential.
41# ── Redacted evidence, not a credential ──────────────────────────────────
42secret-assigndocs/probe-drift-round2-evidence.md=sk-...Probe evidence records redacted key trailers (`sk-...x6uw`); the usable part of the key is not present.
43# ── tests/test_secret_scan.sh fixtures ───────────────────────────────────
44# The self-test plants these fabricated values into a TEMP tree, whose path no
45# entry here covers, so each still fails the guard when planted (see the test's
46# the test file itself stays quiet.
47*tests/test_secret_scan.shsk-or-v1-00000000000000000000000000000000000000000000000000000000deadbeefSelf-test fixture: fabricated OpenRouter-shaped key written to a temp tree; the guard must fail on it there.
48bearer-tokentests/test_secret_scan.shBearer aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbSelf-test fixture: fabricated Bearer token written to a temp tree; the guard must fail on it there.
49proxmox-tokentests/test_secret_scan.shPVEAPIToken=root@pam!monitor=11111111-2222-3333-4444-555555555555Self-test fixture: fabricated Proxmox token written to a temp tree; the guard must fail on it there.
50private-keytests/test_secret_scan.sh-----BEGIN OPENSSH PRIVATE KEY-----Self-test fixture: fabricated PEM banner written to a temp tree; the guard must fail on it there.
51cred-prosetests/test_secret_scan.shAdmin credentials:Self-test fixture: fabricated prose credential line written to a temp tree; the guard must fail on it there.
52secret-assigntests/test_secret_scan.shDB_PASSWORD=correct-horse-battery-stapleSelf-test fixture: fabricated password assignment written to a temp tree; the guard must fail on it there.