Repository navigation
secrets: a password or token in URI user info (scheme://user:pass@host) is never detected, so it survives redaction #766
Description
Summary
SECRETS never reports a credential in URI user info (RFC 3986 §3.2.1), scheme://user:PASSWORD@host or https://TOKEN@host, the usual shape of DATABASE_URL, broker, proxy and registry URLs and JDBC DSNs. Through the CLI, 0 of 185 cases (25 schemes × 7 placements, plus lengths) report anything from any validator, every file processed, while DB_PASSWORD=<pw> reports the same password at 100. Since only findings reach the redactor, a DATABASE_URL-only .env gets no redacted copy, a YAML file with an extra api_token: gets a "redacted" copy still holding the password, the --stdin --enable-redaction gateway passes it through and --pre-commit-mode exits 0. The cause is structural: every capture path needs a provider prefix, a stem before =/: or a quoted base64/hex run, and the M24 cap then holds an uncorroborated candidate at 55, under the threshold of 60. A throwaway prototype of the Resolution reports 185/185 at ≥ 85, changes 0 of main's findings (11,515 SECRETS on 202 public files) and adds 10 MEDIUM in 164,054 public text files, all published examples. --help SECRETS and docs/checks.md:160 promise password detection.
Reproduction
Measured on main @ 3df48ac (v2.5.3); line references are at d5c4da1 (main, 2026-10-14). main has since gained dependency patch bumps and #751, #752 and #759 (changelog CI, perfguard, build tooling), none of which changes detection or redaction.
Isolated config, no suppressions, consonant-and-digit values, .invalid hosts.
pw=$(LC_ALL=C tr -dc 'BCDFGHJKLMNPQRSTVWXZbcdfghjkmnpqrstvwxz23456789' </dev/urandom | head -c 16)
u="DATABASE_URL=postgresql://app_user:$pw@db1.orders.invalid:5432/appdb"
for l in "$u" "DB_PASSWORD=$pw"; do printf '%s\n' "$l" | ferret-scan --stdin --format json --confidence all --quiet | jq -c '[.results[] | "\(.type) \(.confidence)"]'; done
printf '%s\n' "$u" | ferret-scan --stdin --enable-redaction --quiet 2>/dev/null | grep -c -F "$pw"Prints [] (URI), ["API_KEY_OR_SECRET 100"] (keyword), then 1 (password in gateway output).
Full script, output
#!/bin/sh
set -eu
FERRET=${FERRET:-ferret-scan}
W=$(mktemp -d "${TMPDIR:-/tmp}/uri-userinfo.XXXXXX"); D="$W/in"; mkdir "$D" "$W/red"
rnd() { LC_ALL=C tr -dc 'BCDFGHJKLMNPQRSTVWXZbcdfghjkmnpqrstvwxz23456789' </dev/urandom | head -c "$1"; }
PW=$(rnd 16); TOK=$(rnd 40)
URI="postgresql://app_user:$PW@db1.orders.invalid:5432/appdb"
# A. URI user info in keyword-free placements
printf 'DATABASE_URL=%s\n' "$URI" > "$D/a01.env"
printf 'REDIS_URL=redis://default:%s@cache.orders.invalid:6379/0\n' "$PW" > "$D/a02.env"
printf 'spring:\n datasource:\n url: jdbc:mysql://app_user:%s@db1.orders.invalid:3306/shop\n' "$PW" > "$D/a03.yaml"
printf '{"mongo": {"uri": "mongodb+srv://app_user:%s@cluster0.orders.invalid/appdb?retryWrites=true"}}\n' "$PW" > "$D/a04.json"
printf 'engine = create_engine("postgresql+psycopg2://app_user:%s@db1.orders.invalid/appdb")\n' "$PW" > "$D/a05.py"
printf "broker_url = 'amqp://app_user:%s@mq.orders.invalid:5672//'\n" "$PW" > "$D/a06.py"
printf 'services:\n api:\n environment:\n - DATABASE_URL=mysql://app_user:%s@db:3306/shop\n' "$PW" > "$D/a07-compose.yml"
printf 'Connect with `psql postgresql://app_user:%s@db1.orders.invalid/appdb` and run the migration.\n' "$PW" > "$D/a08.md"
printf '2026-10-01T12:00:00Z ERROR could not connect to postgres://app_user:%s@db1.orders.invalid:5432/appdb: timeout\n' "$PW" > "$D/a09.log"
printf 'CONNECTION_STRING=sqlserver://app_user:%s@db1.orders.invalid:1433?database=shop\n' "$PW" > "$D/a10.env"
printf 'HTTPS_PROXY=http://proxy_user:%s@proxy.orders.invalid:3128\n' "$PW" > "$D/a11.env"
printf 'git clone https://%s@git.orders.invalid/orders/app.git\n' "$TOK" > "$D/a12.sh"
printf -- '--extra-index-url https://__token__:%s@pypi.orders.invalid/simple/\n' "$TOK" > "$D/a13-requirements.txt"
printf ' "resolved": "https://deploy:%s@registry.orders.invalid/left-pad/-/left-pad-1.3.0.tgz",\n' "$TOK" > "$D/a14-package-lock.json"
# B. controls: the same password through a keyword
printf 'DB_PASSWORD=%s\n' "$PW" > "$D/b01.env"
printf 'DATABASE_URL=postgres://db1.orders.invalid:5432/appdb?user=app_user&password=%s\n' "$PW" > "$D/b02.env"
printf 'DB_PASSWORD=%s\n' "$URI" > "$D/b03.env"
"$FERRET" --file "$D" --recursive --format json --confidence all --show-match --limit 0 --quiet > "$W/scan.json"
jq -r '.stats | "files_processed=\(.files_processed)/\(.total_files) skipped=\(.files_skipped) findings=\(.total_findings)"' "$W/scan.json"
for f in $(ls "$D"); do
printf '%-22s %s\n' "$f" "$(jq -r --arg f "$f" --arg pw "$PW" --arg tok "$TOK" '[.results[] | select(.filename == $f)
| "\(.type) \(.confidence) \(.metadata.detection_method // "-") span=\(if .text == $pw or .text == $tok then "value" elif (.text | contains($pw)) or (.text | contains($tok)) then "wider(\(.text | length))" else "other" end)"]
| if length == 0 then "NONE" else join("; ") end' "$W/scan.json")"
done
# C. the sink: redaction, the stdin gateway, pre-commit
printf 'DATABASE_URL=%s\n' "$URI" > "$W/red/r01.env"
printf 'database:\n url: %s\napi_token: "%s"\n' "$URI" "$(rnd 32)" > "$W/red/r02.yaml"
printf 'DB_PASSWORD=%s\nDATABASE_URL=%s\n' "$PW" "$URI" > "$W/red/r03.env"
"$FERRET" --file "$W/red" --recursive --enable-redaction --redaction-output-dir "$W/out" --format json --limit 0 --quiet > "$W/red.json"
for f in r01.env r02.yaml r03.env; do
p=$(find "$W/out" -name "$f" 2>/dev/null | head -1)
n=$(jq --arg f "$f" '[.results[] | select(.filename == $f)] | length' "$W/red.json")
if [ -n "$p" ]; then echo "$f findings=$n redacted_copy=written password_in_copy=$(grep -c -F "$PW" "$p" || true)"
else echo "$f findings=$n redacted_copy=none"; fi
done
echo "stdin results=$(printf 'DATABASE_URL=%s\n' "$URI" | "$FERRET" --stdin --format json --quiet 2>/dev/null | jq '.results | length')"
echo "stdin gateway: password_in_stdout=$(printf 'DATABASE_URL=%s\n' "$URI" | "$FERRET" --stdin --enable-redaction --quiet 2>/dev/null | grep -c -F "$PW" || true)"
rc=0; "$FERRET" --file "$W/red/r01.env" --pre-commit-mode >/dev/null 2>&1 || rc=$?; echo "pre-commit r01.env exit=$rc"
rc=0; "$FERRET" --file "$D/b01.env" --pre-commit-mode >/dev/null 2>&1 || rc=$?; echo "pre-commit b01.env exit=$rc"Two runs with fresh values gave identical output (the 14 a lines, each NONE, folded into one), as did the heads of PRs #760 (d24b470), #761 (a0ba05b) and #762 (64e6589) and a build of #764's proposed [_-]? change; matrices A–C and the decoys also match main on all four builds:
files_processed=17/17 skipped=0 findings=3
a01.env … a14-package-lock.json NONE
b01.env API_KEY_OR_SECRET 100 keyword_pattern span=value
b02.env API_KEY_OR_SECRET 100 keyword_pattern span=value
b03.env API_KEY_OR_SECRET 100 keyword_pattern span=wider(68)
r01.env findings=0 redacted_copy=none
r02.yaml findings=1 redacted_copy=written password_in_copy=1
r03.env findings=1 redacted_copy=written password_in_copy=0
stdin results=0
stdin gateway: password_in_stdout=1
pre-commit r01.env exit=0
pre-commit b01.env exit=1
b03: a key carrying a stem reports the whole 68-character URI, today's only route to a URI password, and it depends on the key name.a10:connectionstringis a positive keyword but only corroborates a candidate found some other way.r02looks safe (a copy is written because of the token) but holds the password.r03: redaction masks every occurrence of a found value, so the leak is confined to files where the password appears only in the URI.a14also trips thecontainsSecretIndicatorsline skip (secrets: a "url", "checksum" or version/boolean member anywhere on a line skips token and keyword detection on that line #765); it pins the new detector outside that gate.
Matrices A, B, C; decoys
A. postgres, postgresql, postgresql+psycopg2, mysql, mysql+pymysql, mariadb, mongodb, mongodb+srv, redis, rediss, amqp, amqps, ftp, sftp, smtp, smtps, sqlserver, mssql, oracle, http, https, ldap, clickhouse, nats, cockroachdb, each with default port and path, in bare .txt, .env unquoted and quoted, YAML url:, JSON {"ConnectionStrings": {"connectionString": "<uri>"}}, Python create_engine("<uri>") and prose .md: 0/25 each; length sweep 12/16/24/32/48 in .env and Python 0/10; total_files=185 processed=185 skipped=0 findings=0.
B. main → prototype
| form | main | prototype |
|---|---|---|
.properties jdbc:postgresql://u:<pw>@h:5432/db, jdbc:mysql://… |
NONE | 85, 85 |
mongodb://u:<pw>@m1:27017,m2:27017,m3:27017/db?replicaSet=rs0; redis://:<pw>@cache:6379/0; percent-encoded; uppercase scheme; trailing # primary |
NONE | 100 each |
alembic sqlalchemy.url =; YAML dsn:; k8s stringData:; Terraform locals; Go const dsn |
NONE | 97; 100; 100; 70; 85 |
token as user name; pip __token__:; npm "resolved" |
NONE | 85; 85; 100 |
Oracle thin jdbc:oracle:thin:u/<pw>@h:1521/svc |
NONE | NONE (not URI syntax) |
?user=u&password=<pw>; jdbc:sqlserver://h;…;password=<pw> |
85; 97 | unchanged |
DB_SECRET=<uri>, DB_PASSWORD=<uri> |
100, whole URI (68) | unchanged |
https://x-access-token:<tok>@…, https://gitlab-ci-token:<tok>@… |
85, span <tok>@host/path.git (token: stem fires on the user name) |
unchanged |
C. EMAIL on REDIS_URL=redis://default:<pw>@<host><end> (hosts cache.orders-svc.io, mail.orders-svc.com, cache.orders.invalid, localhost; ends EOL, /0, :6379, :6379/0, ?ssl=true): 6 of 20, only at EOL and ?ssl=true (BUSINESS 100, or 45 on .invalid), none with a port or path. Each spans <pw>@host; format_preserving writes redis://default:X***************@cache.orders-svc.io, X = the password's first character (re-measured).
Decoys (re-run on main: 30/30 processed, 0 SECRETS; … hosts are under orders.invalid). Structural (24): http://localhost:5173/@vite/client, https://cdn…:443/npm/@scope/pkg@1.2.3/…, https://api…:8000/v1/users/@me, registry…:5000/team/app@sha256:<64 hex>, ssh://git@git…/app.git, https://alice@git…/app.git, https://cal…/book?slot=10:30@room4, http://[::1]:8080/@admin, https://proxy…/github.com/x/y/@v/list, postgres://${DB_USER}:${DB_PASSWORD}@db:5432/app, the same with $DB_USER/$DB_PASSWORD and with Jinja double-brace placeholders, postgres://user:<password>@host:5432/db, postgres://user:****@host:5432/db, redis://:%s@cache:6379/0, https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab…/repo.git, postgresql://app_user@db1…:5432/appdb, jdbc:postgresql://db1…:5432/appdb, a credential-free multi-host MongoDB URI, https://maps…/@51.5007,-0.1246,15z, postgres://app:$(DB_PASSWORD)@db:5432/app, file:///home/app/data@2026/report.csv, https://h…/a:b@c, mailto:ops@… (an EMAIL-check finding only). Default-credential (6, on orders.invalid hosts): user:password, guest:guest, postgres:postgres, root:changeme, user:REDACTED, user:xxxxxxxx.
Root cause
In internal/validators/secrets/validator.go:
- No capture path anchors on URI syntax. Quoted (
:499,:556-569) and unquoted (:1000, stems:961-971) patterns need a stem right before=/:; a URI password followsuser:. The entropy path (:716-753,:487) needs quotes around a run excluding:,@,..connectionstring(:413) only corroborates. - The M24 cap (
:2406-2413) holds a candidate with no specific pattern or token-bounded keyword at 55, below the keyword path's > 60 (:653,:2032): 5 of 7 line shapes score 55, 85–100 uncapped. - No owner: EMAIL's
hasURLStructureAfter(email/validator.go:972-1000) rightly treatsuser@host:portafter://as a URI; SECRETS never claimed it.
Root cause 2 scores
calculateEnhancedConfidenceWithCacheAndEnvAndLine, line-keyword state as processScopedCandidates computes it:
| line | score | cap lifted |
|---|---|---|
DATABASE_URL=postgresql://app_user:<pw>@… (.env) |
55 | 100 |
REDIS_URL=redis://default:<pw>@… (.env) |
55 | 100 |
engine = create_engine("postgresql+psycopg2://…") |
55 | 100 |
prose (.md) |
55 | 85 |
url: jdbc:mysql://… (.yaml) |
55 | 85 |
{"ConnectionStrings": {"connectionString": "…"}} |
97 | 97 |
DB_PASSWORD_URL=… |
100 | 100 |
Width of the class
- Every scheme (0/185) and user-info shape (empty user, JDBC,
+psycopg2, multi-host, percent-encoded, uppercase); tokens as password or user name;--file(13 types),--stdin, the gateway (library paths shareValidateContentCtx). - Today's hits are accidents: a
passwordquery parameter, a key stem (whole URI), a stem-ending user name (overrun), provider prefixes, EMAIL's<pw>@host.
Resolution
- Add
findURIUserinfo(line), a separate candidate source inValidateContentCtx's line loop (:648-654), the region fix(secrets): stop reporting code expressions as credentials in Python, JS/TS, Go and HCL files (#753) #762 rewrites (it addsgateableandgateDrops). Theuri_userinfocandidate is non-gateable, like {{U20}}'s structural sources. It is prefiltered on://and@and runs outsidecontainsSecretIndicators, changing none of its arms; not a keyword pattern, so {{U36}}'s gate guard does not apply. - Parse the RFC 3986 authority, not a scheme list or label: any scheme (JDBC too), last-
@split (as Gonet/urland WHATWG), §3.2.1 characters only (templates fail by grammar), validated host (a path or query@never matches); hand-written, asnet/url.Parsefails JDBC and host lists and echoes the password (below). - Value-intrinsic dispositions, no line or host veto (author-controlled, TM-11): references and
***give no candidate; default vocabulary and implausible values are LOW (postgres:postgrescan be real). - Score as structural:
detection_method: uri_userinfo, threshold 50, astructuralflag exempting it from the M24 cap; other adjustments unchanged. - Existing type, exact span, no new metadata key (validators: every finding's metadata map sits exactly at a Go bucket cliff — the next key added costs ~342 B per finding #621):
getSecretTypedecides (ghs_…stays GITHUB_TOKEN); the span is the password as written, so the redacted URI still parses; no URI part in metadata, logs or--explain. - Additive overlap: an identical span keeps the first claim (
:308); a span inside an emitted SECRETS finding is dropped after thresholds; EMAIL's<pw>@hoststays (cross-validator deconfliction: 5.15% of findings are sub-spans of a wider, more-confident finding, and only redaction knows it #687); redacting SECRETS inside it is redaction: overlapping findings leave part of a value in cleartext; identical spans render by sort order, not a rule #773's rule; this adds the fixture (land after redaction: overlapping findings leave part of a value in cleartext; identical spans render by sort order, not a rule #773 or mark it pending). - Compatibility: no existing finding's Type, Text, line, context or confidence changes, so suppression hashes (
internal/suppressions/suppression.go:301-348) still match; new findings change exit status (r01.env0 → 1) and add redacted copies.b03's whole-URI span stays (PR fix(secrets): end unquoted value at a query-string parameter separator #760 leaves it); narrowing it would move its hash. - Linear: each authority ends at the next
/, so O(line length); no length cap (a suppressor); the complexity guard pins dense and no-terminator lines.
Design tables, measurements
| step | rule |
|---|---|
| scheme | longest [A-Za-z][A-Za-z0-9+.-]* before :// (§3.1); jdbc:mysql:// yields mysql |
| authority | to the first / ? #, whitespace, ", ', backtick, <, > or \, then trim trailing , ; . ! ) ] } (untrimmed, the prototype missed postgres://u:<pw>@host, then …) |
@ |
split at the last @ |
| user info | unreserved / pct-encoded / sub-delims / ":" plus @; rejects ${X}, double-brace templates, <password>, %s, %(pw)s |
| host | non-empty reg-name, IPv4 or [IP-literal], optional :port, optional comma list; rejects /@vite, ?slot=10:30@room, [::1]:8080/@admin |
| password | after the first :; empty → no candidate |
| user name only | candidate only if ≥ 20 chars, letters and digits, Shannon ≥ 3.5 bits/char (git@, alice@ silent) |
net/url.Parse (Go 1.27, re-run): jdbc:postgresql://u:p@h/db → scheme jdbc, opaque, no user; mongodb://u:p@h1:27017,h2/db → invalid port ":27017,h2" after host; that error and a bad-escape error both quote the whole raw URI, password included.
| value | disposition |
|---|---|
whole value $NAME / $(…), or only * (isAllMaskCharacters) |
no candidate |
password, passwd, pass, pwd, secret, guest, postgres, root, admin, changeme, user, username, default, test, example, demo (any case), or equal to the user name |
LOW, ≤ 55 |
fails plausibleUnquotedSecret (< 8, all-lowercase < 16, digits only, UUID) or shoutySentinel |
LOW, ≤ 55 |
| anything else | scored normally; test/dev, negative-keyword, global-test-pattern and shell-variable (:2015) adjustments and the +30 clamp (:2374) apply |
Prototype (CLI; differs only in: exactly one @, no punctuation trim, prefix reference test ^\$[A-Za-z_{(], no redaction change):
| set | main | prototype |
|---|---|---|
| matrix A, 185 | 0 | 185, all ≥ 85 (157–158 HIGH by template): .env/YAML/Python 100, bare 85–100, JSON 97–100, prose 85–97 |
| matrix B user-info forms, 19 | 4 (whole-URI/overrun) | 19: 15 new, the 4 unchanged |
| matrix C, 20 | 6 EMAIL | +20 SECRETS (15 HIGH, 5 MEDIUM), exact password; EMAIL unchanged |
a01–a14 |
0 | 14 (85–100), exact span |
| decoys | 0 | 0 structural; postgres:postgres, root:changeme, user:REDACTED, user:xxxxxxxx LOW 55; guest:guest dropped; user:password dropped on localhost, LOW 55 elsewhere |
Public corpus, 164,054 text files (1.57 GB: Go module cache with toolchain, Homebrew Cellar, global npm packages, a Python site-packages tree); 241,690 lines hold ://, 202 files an @ in an authority. A naive scheme://[^\s:@]*:[^\s@]+@ matches 304, 65 (21%) not user info. Regex simulation: 361 candidates, 165 user-only (0 pass the token gate), 3 empty, 9 ***, 170 LOW (134 default vocabulary, 36 short or single-class), 14 would report. CLI on the 202 files: main 11,515 SECRETS (33,877 findings in all; pre-#762: those 202 files include Go, npm and Python, so re-state the baseline at the merged head if #762 lands first; the +10 / 0-lost delta comes from a non-gateable source) → 11,525; 0 lost or changed in any validator; +10, all MEDIUM (60–85), all published examples (Go net/url tests ×4, dotenv's documented example keys ×4, a SOCKS proxy README ×2); no LOW candidate survived the scorer. Last-@ split (regex): +14, 4 would report, all Go net/url examples. This repo's 1,502 tracked text files: 0 password candidates (.gitlab-ci.yml:343,402 ${GITLAB_RELEASE_TOKEN} fails the grammar).
Recall cost to document: an unencoded / ? # in a password ends the authority, as for every URI parser.
CPU (user+sys, --checks SECRETS, 3 interleaved samples, load 78–286):
| input | main | prototype |
|---|---|---|
| 4,000 URIs on one line (224 KB) | 0.69–0.71 s, 0 findings | 1.11–1.21 s, 4,000 |
| 8,000 URIs (448 KB) | 1.53–1.59 s | 2.66–3.71 s (median 2.73), 8,000 |
x:// + 256 / 512 KB of a: + @h |
0.80–0.84 / 1.60–1.73 s | 0.82–0.85 / 1.63–1.76 s |
x:// + 256 / 512 KB of a@ |
0.81–0.85 / 1.61–1.75 s | 0.83–0.85 / 1.57–1.77 s |
Doubling the dense line: 2.3× prototype, 2.2× main. The 512 KB no-terminator finding's Text is 524,286 characters.
Not in scope: Oracle thin / EZConnect user/pass@host (own grammar, 0 in the corpus); .netrc, curl -u ({{U20}}); .pgpass, mysql -p, redis-cli -a, SQL IDENTIFIED BY, requirepass (need file or command gating; NONE ×5); DB_PASS=, PGPASSWORD= (keyword stems; NONE ×2); {"client_secret": "<16>"} NONE vs {"secret": …} 97 (quoted anchoring).
Tests
- goldencorpus
secrets_uri_userinfoand_negative, Tier 1 FileCases, redaction goldens, unit and self-policing tests (below); goldens unchanged.
Test detail, recall cases, fixtures
- goldencorpus
secrets_uri_userinfo(cases below, exact span) andsecrets_uri_userinfo_negative(24 structural decoys empty; 6 default-credential ≤ LOW offlocalhost); Tier 1 FileCases; goldens unchanged. - Redaction goldens: no planted password byte under any strategy; the EMAIL-overlap line as redaction: overlapping findings leave part of a value in cleartext; identical spans render by sort order, not a rule #773's case.
- Unit: grammar and disposition rows; a gate-rejected line still yields the candidate; no password byte in error or debug paths;
DB_PASSWORD=<uri>,x-access-token:equal main (written first);DATABASE_URL=≥ 60. - Self-policing: all 25 × 7 report;
TestValidatorComplexityIsSubQuadratic(complexity_guard_test.go:346) gains both targets.
Recall cases: postgres, mysql, mongodb+srv, redis with empty user, amqp, sqlserver, https proxy, ftp, smtp, ldap; JDBC mysql/postgresql, multi-host MongoDB, percent-encoded, uppercase scheme, trailing # comment; quoted, unquoted, JSON, YAML, Python, prose, ,- and )-terminated; token as user name, pip __token__:, npm "resolved" with a registry. host. FileCases: .env with only DATABASE_URL, docker-compose, Spring .properties, alembic .ini. Fixtures: synthetic values with no test/example/demo/dev, .invalid hosts, provider tokens via buildTestToken, URIs assembled from parts if push protection flags them.
Docs & CHANGELOG
--help SECRETS(:2603): list URI user info; fix the:2627run-on ("handling.words").- Secrets
README.md: "Credentials in URIs" (grammar, LOW band, recall cost) and a:81-86bullet;docs/checks.md:156-162. CHANGELOG.md[Unreleased]🔒 Security: the detection, its exit-status and redacted-copy effect; existing findings and hashes unchanged.
Acceptance criteria
- Full script: one SECRETS finding per
a01–a14,span=value, ≥ 60;b01–b03byte-identical to main. - Same run:
r01.envandr02.yamlcopies written withpassword_in_copy=0,stdin results=1,password_in_stdout=0,pre-commit r01.env exit=1. - Matrix A 185/185 (text = password, ≥ 60); matrix B forms give the exact span,
unchangedrows equal main's with no nested second finding; 24 structural decoys silent, 6 default-credential decoys ≤ LOW. - Additivity: goldens unchanged; on a public corpus named in the PR every main finding keeps Type, Text, LineNumber, confidence.
-
a14reports whatevercontainsSecretIndicatorsreturns for it; no arm of that function changes. - No new type;
metadata-key-census.txtunchanged;detection_methodisuri_userinfo; the complexity guard has both targets and passes. -
REDIS_URL=redis://default:<pw>@cache.orders-svc.iois aTestGoldenRedactcase; with redaction: overlapping findings leave part of a value in cleartext; identical spans render by sort order, not a rule #773 in, no password byte survives any strategy (main:X***************). - The Docs & CHANGELOG items above are present.
Related
- secrets: a "url", "checksum" or version/boolean member anywhere on a line skips token and keyword detection on that line #765 (line skip,
:856-863), redaction: overlapping findings leave part of a value in cleartext; identical spans render by sort order, not a rule #773 (ResolveOverlaps; first-character leak when SECRETS nests in EMAIL), secrets: Stripe, GitLab and Slack tokens are matched at a fixed length, so redaction leaves each tail in the clear #769/{{U19}}/{{U36}} (editcontainsSecretIndicatorsfirst; {{U36}} owns per-line cost), {{U20}} (auth headers,.netrc,curl -u). - secrets: an unquoted value runs past '&' in a query string, so two parameters are reported (and redacted) as one #758 / PR fix(secrets): end unquoted value at a query-string parameter separator #760: a query-string value ends at
&key=; measured at its head,?user=u&password=<pw>&sslmode=requirechanges from<pw>&sslmode=requireto<pw>. Matrix B (?user=u&password=<pw>at end of line;;password=JDBC) and b02 are unchanged. - secrets: the #749 list-tail splitter is quadratic on separated assignments and reports every element of a minified line #754 / PR fix(secrets): make the list-tail and collection scans linear, and stop reporting every element of a minified line (#754) #761: on
…;Password=<pw>;Encrypt=True;TrustServerCertificate=Falsemain also reportsEncrypt=TrueandTrustServerCertificate=Falseat 100; PR fix(secrets): make the list-tail and collection scans linear, and stop reporting every element of a minified line (#754) #761's head (a0ba05b) reports only<pw>(100). - cross-validator deconfliction: 5.15% of findings are sub-spans of a wider, more-confident finding, and only redaction knows it #687 (EMAIL's
<pw>@host), validators: every finding's metadata map sits exactly at a Go bucket cliff — the next key added costs ~342 B per finding #621 (metadata cliff); closed: Generic secret assignments (password=, api_key=, token=) are not detected at all, despite the code claiming to catch them #360, secrets: unquoted-assignment detection is single-value, single-line and keyword-adjacent — quoted secrets in JSON arrays, nested objects and YAML sequences are missed entirely #746.
Audit item U42 · tier P0 · effort M · rank 2/51. Measurements pinned to 3df48acd (v2.5.3); a {{U##}} token is a sibling item not yet filed.
Activity
fix(secrets): do not cap a hex password in URI user info as an unlabe…