Skip to content

sec(scripts): widen git-secrets DSN detectors and list test credential placeholders - #1273

Draft
IAmJSD wants to merge 2 commits into
LeanerCloud:mainfrom
IAmJSD:fix/issue-368
Draft

IAmJSD wants to merge 2 commits into
LeanerCloud:mainfrom
IAmJSD:fix/issue-368

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

This is the follow-up audit to #1972 that #368 asked for. There were two parts.

1. Placeholder entries in .gitallowed

The bare account-ID/UUID block the issue describes is already gone. #813 (for #804) replaced it with ^path:[0-9]+:...$ entries, and scripts/test-git-secrets-allowlist.sh now checks that every entry is anchored. One loose entry was left. It was a shape-based entry for test credentials:

^[^:]+(_test\.go|\.test\.ts):[0-9]+:[[:space:]]*(password|api_key):[[:space:]]*["'][^"']{0,40}["'],?$

It was anchored, but it accepted any quoted value of up to 40 characters. So a real password, or a 32-character API key, pasted into any _test.go / .test.ts file scanned clean. It's now two entries, one for password and one for api_key, and each lists the placeholder values actually in use: 23 passwords and 4 API keys. A new placeholder needs a one-word addition to the list. The account-ID entry was already value-listed and is unchanged.

2. DSN detector coverage audit

I probed each detector empirically in a throwaway repo with git-secrets 1.3.0 and an isolated HOME.

Case Before After
postgres://u:p@host caught caught
postgresql://u:p@host (libpq's canonical scheme) missed caught
pgx5://u:p@host, pgx:// (golang-migrate; this repo's migrations use pgx5://) missed caught
postgresql+psycopg2://, postgres+asyncpg://, mysql+mysqlconnector:// missed caught
mysql+pymysql:// caught (by luck: "pymysql://" contains "mysql://") caught
mariadb://u:p@host missed caught
Empty user name scheme://:p@host (postgres, mysql, mongodb) missed caught
?password= / &password= query parameter (libpq URI, JDBC) missed caught
URL-encoded credentials (p%40ss) caught caught (now pinned by a test)
IPv6 host (@[2001:db8::1]:5432) caught caught (now pinned by a test)
?sslpassword=, credential-less pgx5://user@host clean clean

The fix is in scripts/setup-git-secrets.sh:

Side effects in .gitallowed:

  • The PostgreSQL and MySQL registration lines no longer contain a literal scheme://, so they stop matching themselves. Their two self-match entries were dead and are removed. The Azure entry stays.

  • Three docs lines are now flagged and get whole-line entries:

    • the documented local docker-compose password in docs/DEVELOPMENT.md (pgx5://cudly:cudly_local_dev@...)
    • a ${DB_PASSWORD} shell reference quoted in the 2026-09-02 audit
    • an audit sentence that names pgx5:// in prose, where the detector runs on to a later : and @

    None of them holds a real credential.

Closes #368

Accepted residual gaps (not fixed here)

Each would need its own design decision and a false-positive pass. File follow-ups if you want any of them:

  • Upper-case schemes (POSTGRES://, MONGODB://). git-secrets greps case-sensitively, and spelling out [Pp][Oo]... makes the patterns much harder to read. Clients and tools emit lower-case schemes.
  • libpq key/value conninfo (host=... user=... password=secret). It isn't a URL DSN, and the obvious detector would flag every fmt.Sprintf("... password=%s") in the tree. The quoted form is already caught by the generic password detector.
  • Go MySQL driver DSN (user:pass@tcp(host:3306)/db, no scheme) and other databases (redis, amqp, mssql/sqlserver, cockroachdb, redshift). These are outside the three detectors the issue scoped.
  • The DSN user part [^:]* can still run across prose on a long line, as in the audit sentence above. I didn't exclude whitespace from it, because that would drop coverage [^:]+ already has: a concatenated "postgres://" + user + ":" + "pw" + "@".
  • Note: CI's pre-commit job registers only git secrets --register-aws. The custom detectors, including these DSN detectors, run in CI only through the git-secrets allowlist self-test job, which scans every tracked file with them. That was already the case and is unchanged.

Fork-branch / fixture compatibility

The brief asked for this before any entry was tightened. I scanned with no allowlist, then applied the old and new .gitallowed, across these trees:

Results:

  • Every line the old credential entry allowed is still allowed, in every tree.
  • The widened detectors flag the same 7 new lines in every tree, all of them on main, and all are covered by the new entries. No branch adds a new flagged DSN line.
  • No branch touches .gitallowed or the setup script.
  • Every tree scans clean with the new detectors and allowlist (setup-script self-matches aside, because those trees carry main's setup script).

Caveat: the #313 worktree is still in progress and edits internal/auth/service_password_test.go. If it adds a new password placeholder value, that value has to be added to the list.

Verification

All of this is real git-secrets 1.3.0 scan evidence, not mocks. Each run used a fresh git init temp repo and an isolated HOME, never the shared git config.

  • bash scripts/test-git-secrets-allowlist.sh on this branch: 183 PASS, 0 FAIL.
  • Fail-before: this branch's test script run against origin/main's .gitallowed and setup-git-secrets.sh gives 157 PASS, 26 FAIL. That's 13 new cases × 2 scan paths (direct scan and installed hook):
    • each newly covered DSN shape (postgresql://, pgx5://, +driver ×2, mariadb, empty user ×3, query password ×2)
    • "other password on the documented dev DSN line is caught"
    • "real-looking password in a Go test is caught" and "real-looking API key in a TS test is caught". These are the near-miss values the old shape entry let through.
  • Each tightened entry still allows the placeholders it's meant for: the listed password placeholders in a Go and a TS test, the listed api_key placeholder, and the documented dev DSN all scan clean. The same placeholder outside a test file is still caught. The whole-tree file-argument scan of all tracked files also passes.
  • Legacy cleanup mutation probe: with the new legacy-DSN removal loop deleted from the setup script, the test fails with setup run left a pre-#368 DSN detector registered.
  • The self-match negative control ("secret appended to a self-matching line itself stays caught") moved from the PostgreSQL line, which no longer self-matches, to the Azure line, which still has a self-match entry.
  • shellcheck scripts/setup-git-secrets.sh scripts/test-git-secrets-allowlist.sh: no new findings. The only findings are the two SC2034 warnings for unused YELLOW/BLUE, which main has too.
  • git diff --check: clean. No Go, Terraform, workflow or frontend files changed.

Combined-tree verification. This branch was tested together with the other queued branches on a local, never-pushed tree: queue/platform @ 936996ad = origin/main a14dc8f2 plus these heads, merged in this order:

Branch Tested head
fix/issue-337 ff3420c
fix/issue-169 7d75eb5
fix/issue-342 66b221f
fix/issue-310 5e11c21
fix/issue-326 5f0ea59
fix/issue-243 365e959
fix/issue-343 6c94036
fix/issue-312 d2848a8
fix/issue-316 acd60dd
fix/issue-339 75ea9b9
fix/issue-339-commitment-type 73bdb19
fix/issue-353 5d25fcd
fix/issue-313 3156bc4
fix/issue-357 53d9e58
fix/issue-368 0ba727d
fix/issue-419 61648c9
fix/issue-111 b04c372
fix/issue-686 0ad90b7
fix/issue-674 793095d
fix/issue-1055 4ec96f1
fix/issue-559 dfa9525
fix/issue-558 034eed4
fix/issue-874 1720539
fix/issue-666 1e120da
fix/issue-668 fb26345
fix/issue-568 ce04173
fix/issue-311 e54f250
fix/issue-177 0cbb88a
fix/issue-151 c21377c
fix/issue-166 41fb77b
fix/issue-27 2d67622
fix/issue-323 69288ae
fix/issue-59 36463e8
fix/issue-43 35e4069
fix/issue-158 6f76f8a
fix/issue-104 bb00491
fix/issue-55 971ef1b
fix/issue-361 89ac220
fix/issue-120 b458719
fix/issue-36 42bac6e
fix/issue-525 57c97dd
fix/issue-712 e5bcda0
fix/issue-72 6cc63ed

fix/issue-72 and fix/issue-312 conflict on one line in internal/api/handler_dashboard.go (both change the calculateCommitmentMetrics call). The tree used #72's side with #312's validated provider variable; whichever PR merges second takes that same resolution.

Linux, Go 1.26.9, AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=true; Docker Postgres via testcontainers:

  • Passed at 936996ad: go build ./..., go vet ./..., go vet -tags integration ./... and the full go test -race -short -count=1 ./....
  • Static checks at 936996ad: golangci-lint v2.10.1 found 0 issues on every changed package, with and without --build-tags integration. tests/e2e is a separate module, so it was vetted under its own e2e tag. gocyclo -over 10 is silent on the changed non-test files.
  • Integration tests, every changed package, one package at a time (-tags integration -count=1, 60-minute local timeout because the VM was shared):
    • At 936996ad, these pass: internal/server, internal/credentials, internal/scheduler, internal/database/postgres/migrations, internal/purchase and internal/api.
    • internal/config passes except TestPostgresStoreDB_ResolveStaleRIExchange_ValidatesCloudCheck, which fails the same way on unmodified main (fixed by fix(config): compare resolve checked_at at the database's microsecond precision #1252).
    • These were run on the first build of this tree (26795376, same branches at earlier heads): internal/analytics, internal/auth, internal/commitmentopts, internal/database and cmd/lambda/clear-rate-limit all pass. The later fixes don't touch those packages.
  • What the combined run caught, each fixed on its own branch before this PR opened:
    • a compile break between fix/issue-104's test and fix/issue-326's httpToLambdaRequest signature change;
    • fix/issue-316 rejecting legacy-keyed plans;
    • fix/issue-120 versus main's global-filter test.
  • Secrets: git-secrets, using the repo's scripts/setup-git-secrets.sh patterns in a fresh repo with an isolated HOME, found nothing in the changed files, and a planted fake key was flagged. scripts/test-git-secrets-allowlist.sh: 137 PASS, 0 FAIL.
  • Nothing was applied to any cloud.

Out of scope

  • The residual detector gaps listed above.
  • The SC2034 shellcheck warnings in setup-git-secrets.sh, which are on main too.

Maintainer: please mirror the issue's triage labels onto this PR: impact/internal, triaged, priority/p3, severity/low, urgency/eventually, effort/s, type/security.

🤖 Generated with Claude Code

Audit follow-up to #1972 (issue LeanerCloud#368). The bare placeholder block was
already replaced with anchored entries in LeanerCloud#813; what remained:

- The shape-based test-credential entry in .gitallowed allowed any quoted
  password or api_key value up to 40 characters in a test file, so a real
  password or a 32-character API key pasted into a fixture scanned clean.
  It now lists the placeholder values in use (on main and the open fork
  branches).
- The DSN detectors missed libpq's postgresql:// scheme, golang-migrate's
  pgx/pgx5 schemes (used by this repo's migrations), "+driver" schemes,
  mariadb://, DSNs with an empty user name, and a password passed as a
  ?password= query parameter. URL-encoded credentials and IPv6 hosts were
  already covered and are now pinned by tests.
- The widened detectors no longer match their own registration lines, so
  the two DSN self-match entries are gone; a re-run of the setup script
  drops the pre-LeanerCloud#368 DSN detectors from an existing clone.
- Three docs lines the widened detectors flag (local docker-compose
  password, a ${DB_PASSWORD} reference, prose naming pgx5) get
  whole-line entries.

Closes LeanerCloud#368

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD IAmJSD added impact/internal Team-internal only triaged Item has been triaged priority/p3 Polish / idea / may never ship severity/low Minor harm urgency/eventually No deadline effort/s Hours type/security Security finding labels Oct 23, 2026
@coderabbitai

coderabbitai Bot commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

Brings in main's CI fixes (LeanerCloud#1236, LeanerCloud#1252) and CI changes since, so this PR gets a fresh CI run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD

IAmJSD commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator Author

Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE 32fe1cd687813cda95c4f92cd3a98b686ffbf1a2

Blocking:

  • none

Non-blocking:

  • .gitallowed (the codebase-audit-2026-09-02.md prose entry): a whole-sentence allowlist entry several hundred characters long is safe, since it is anchored on path and whole line with $, but it's brittle. Any edit to that audit line makes the scan fail. Rewording the sentence, for example by putting the scheme in backticks without a later :...@ run, or splitting the line, would make the entry unnecessary. That's the author's call.
  • scripts/setup-git-secrets.sh DSN detectors: confirmed gaps beyond the ones the body lists. mysqlx://u:pw@h is clean. postgres://host:5432/db ... foo@bar.com on one line is flagged (a false positive from the user-part [^:]* running on to a later @; [^:]+ had the same behavior before). POSTGRES://, redis://, cockroachdb:// are clean, as documented. Issue sec(scripts): audit unanchored .gitallowed placeholder entries and DSN detector coverage (#1972 follow-up) #368's acceptance asks to "file any further fixes as their own PRs". The body lists the residual gaps as accepted but files no follow-up issue for them. Consider opening one so the gap list doesn't live only in a closed PR.

Attacked:

  • Ran bash scripts/test-git-secrets-allowlist.sh on the head (git-secrets 1.3.0): 183 PASS, 0 FAIL.
  • Fail-before: put main's setup-git-secrets.sh and .gitallowed back with the new test script. It stops at "setup run left a pre-sec(scripts): audit unanchored .gitallowed placeholder entries and DSN detector coverage (#1972 follow-up) #368 DSN detector registered". With that legacy block also cut out, the result is 156 PASS, 27 FAIL: all 13 new coverage and placeholder cases x2 scan paths, plus the file-argument tree scan.
  • Archived the head into a fresh repo with an isolated HOME and ran setup, then a no-argument git secrets --scan over the whole tree: rc 0.
  • Probed extra DSN shapes. Flagged: unencoded @ or : in the password, jdbc:postgresql://, +asyncpg, pgx4://, ?...&password=, mongodb+srv, mysql://u@h?password=.
  • Checked the placeholder-list regexes for metacharacters (My\$ecur3... escapes correctly). Checked that whole-line $ anchoring keeps the test-file-only scope. The legacy-removal loop drops only local, exact-match bare and wrapped legacy forms, after the replacements are registered.
  • shellcheck: only the SC2034 warnings that main already has.

Body-stale:

  • The fail-before count is "157 PASS, 26 FAIL". I get 156/27 once the legacy block is cut so the run gets past it; the extra failure is the tracked-file scan. Minor.
  • The "Combined-tree verification" section tests fix/issue-368 at 0ba727d7, not the current head 32fe1cd6, and quotes "137 PASS" for the allowlist test. Both are from an earlier head. This head gives 183.

Reviewer: rB14. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths.

@IAmJSD

IAmJSD commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator Author

Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE 32fe1cd687813cda95c4f92cd3a98b686ffbf1a2

Blocking:

  • none

Non-blocking:

Attacked:

  • Tightening: the old shape entry ([^"']{0,40}) is gone, and its replacement lists literal values. With main's .gitallowed and this branch's detectors, test-git-secrets-allowlist.sh gives 176 PASS, 7 FAIL. The failures are "real-looking password in a Go test is caught" and "real-looking API key in a TS test is caught" (both scan paths), plus the dev-DSN and whole-tree cases. That's the fail-before for the allowlist half. With main's setup script as well, the run stops at "setup run left a pre-sec(scripts): audit unanchored .gitallowed placeholder entries and DSN detector coverage (#1972 follow-up) #368 DSN detector registered", which is the legacy-cleanup check. On this head the script gives 183 PASS, 0 FAIL.
  • Detector widening: the postgres(ql)?|pgx[45]? / mysql|mariadb / +driver / empty-user forms are each pinned by a positive and a negative fixture, on both the direct-scan and installed-hook paths. The two removed self-match entries can't hide anything, because the new registration lines no longer contain a literal scheme://. The three new docs entries are whole-line anchored with a trailing $, so appending a secret to those lines still gets caught.
  • Legacy cleanup only unsets exact (grep -qxF) matches of the old bare and word-wrapped patterns, using --fixed-value, so it can't remove anything else.
  • Ran: bash scripts/test-git-secrets-allowlist.sh on the head and in the two fail-before variants above, shellcheck (only the SC2034 warnings main already has), and secrets-scan.sh over every open PR's changed files using this branch's patterns. No Go code changed, so no go build or tests were needed.

Body-stale:

Reviewer: rB15. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/internal Team-internal only priority/p3 Polish / idea / may never ship severity/low Minor harm triaged Item has been triaged type/security Security finding urgency/eventually No deadline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sec(scripts): audit unanchored .gitallowed placeholder entries and DSN detector coverage (#1972 follow-up)

1 participant