Skip to content

secrets: a password or token in URI user info (scheme://user:pass@host) is never detected, so it survives redaction #766

Closed

Description

@sectoramen

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: connectionstring is a positive keyword but only corroborates a candidate found some other way.
  • r02 looks 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.
  • a14 also trips the containsSecretIndicators line 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:

  1. 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 follows user:. The entropy path (:716-753, :487) needs quotes around a run excluding :, @, .. connectionstring (:413) only corroborates.
  2. 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.
  3. No owner: EMAIL's hasURLStructureAfter (email/validator.go:972-1000) rightly treats user@host:port after :// 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 share ValidateContentCtx).
  • Today's hits are accidents: a password query parameter, a key stem (whole URI), a stem-ending user name (overrun), provider prefixes, EMAIL's <pw>@host.

Resolution

  1. Add findURIUserinfo(line), a separate candidate source in ValidateContentCtx'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 adds gateable and gateDrops). The uri_userinfo candidate is non-gateable, like {{U20}}'s structural sources. It is prefiltered on :// and @ and runs outside containsSecretIndicators, changing none of its arms; not a keyword pattern, so {{U36}}'s gate guard does not apply.
  2. Parse the RFC 3986 authority, not a scheme list or label: any scheme (JDBC too), last-@ split (as Go net/url and WHATWG), §3.2.1 characters only (templates fail by grammar), validated host (a path or query @ never matches); hand-written, as net/url.Parse fails JDBC and host lists and echoes the password (below).
  3. 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:postgres can be real).
  4. Score as structural: detection_method: uri_userinfo, threshold 50, a structural flag exempting it from the M24 cap; other adjustments unchanged.
  5. 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): getSecretType decides (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.
  6. Additive overlap: an identical span keeps the first claim (:308); a span inside an emitted SECRETS finding is dropped after thresholds; EMAIL's <pw>@host stays (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).
  7. 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.env 0 → 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.
  8. 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_userinfo and _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) and secrets_uri_userinfo_negative (24 structural decoys empty; 6 default-credential ≤ LOW off localhost); 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 :2627 run-on ("handling.words").
  • Secrets README.md: "Credentials in URIs" (grammar, LOW band, recall cost) and a :81-86 bullet; 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–b03 byte-identical to main.
  • Same run: r01.env and r02.yaml copies written with password_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, unchanged rows 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.
  • a14 reports whatever containsSecretIndicators returns for it; no arm of that function changes.
  • No new type; metadata-key-census.txt unchanged; detection_method is uri_userinfo; the complexity guard has both targets and passes.
  • REDIS_URL=redis://default:<pw>@cache.orders-svc.io is a TestGoldenRedact case; 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


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

  1. added this to the v2.6.0 milestone on Oct 14, 2026
  2. added
    bugSomething isn't working
    P0Audit tier P0: fail-open or cleartext leak; fix first
    on Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P0Audit tier P0: fail-open or cleartext leak; fix firstbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions