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
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.
23 lines
1.6 KiB
Plaintext
23 lines
1.6 KiB
Plaintext
# secret-patterns.tsv — checked-in pattern list for scripts/secret-scan.sh
|
|
#
|
|
# Format: <rule-id><TAB><POSIX ERE><TAB><description><TAB><check>
|
|
# Blank lines and lines whose first field starts with '#' are ignored.
|
|
# <check> is optional; the only value today is "value", which tells the scanner
|
|
# to run the matched value through its inert-value classifier (see
|
|
# value_is_inert in secret-scan.sh) so bare identifiers, env refs and dotted
|
|
# code access are not reported as credentials. Omit the column to report every
|
|
# regex hit.
|
|
# Matching is case-insensitive, so `API_KEY` and `api_key` both count.
|
|
#
|
|
# Add a rule here, never inline in secret-scan.sh: this file is the single
|
|
# auditable list of what the guard considers credential-shaped.
|
|
openai-key \bsk-[A-Za-z0-9_-]{16,} OpenAI/LiteLLM-style "sk-" secret key (also hyphenated sk-proj- keys)
|
|
openrouter-key \bsk-or-v1-[A-Za-z0-9_-]{8,} OpenRouter API key
|
|
stripe-live-key \bsk_live_[A-Za-z0-9]{8,} Stripe live secret key
|
|
proxmox-token PVEAPIToken=[^[:space:]"']+ Proxmox API token literal
|
|
bearer-token bearer[[:space:]]+["']?(«.{3,}»|[A-Za-z0-9_./+=-]{20,}) literal Bearer token (http header or prose)
|
|
auth-header authorization:[[:space:]]+["']?(«.{3,}»|[A-Za-z0-9_./+=-]{20,}) Authorization header carrying a raw literal value
|
|
private-key -----BEGIN [A-Z ]*PRIVATE KEY----- PEM private key block
|
|
cred-prose credentials?[[:space:]]*[:=][[:space:]]*[^[:space:]] prose credential line carrying a value
|
|
secret-assign (api[_-]?key|apikey|passwd|password|secret|token)s?["']?[[:space:]]*[:=][[:space:]]*["']?(«.{3,}»|[A-Za-z0-9_./+=-]{8,}) credential assignment carrying a literal value value
|