Skip to content

Commit d13ff9f

Browse files
security: rust-secrets — require assignment to a literal, and cover static (#523)
Follow-up to #518, from triaging every remaining `rust-secrets` failure in the estate. ## The false positives The patterns matched `= ... "` — a quote **anywhere** after an equals. That flags code which hardcodes nothing at all: ```rust let api_key = request["api_key"].as_str().unwrap_or(""); // a lookup let api_key_file = temp_dir.path().join("config.js"); // a path Query::new("Here's my api_key=sk-12345"); // a demo string ``` A hardcoded secret is an assignment **directly to a string literal**. Each pattern now requires the quote to follow the `=`, with a minimum value length of 8 — a credential shorter than that is not a credential. ## The missed detection (the more important half) `const.*KEY.*=` never matched: ```rust static AUTH_KEY: &str = "abcdef0123456789abcdef0123456789"; ``` That is a genuine hardcoded secret the job was **silently passing**. I found it because a 5-secret canary fixture detected only 4 — not by reading the regex. `static` and `mut` are now accepted alongside `const`/`let`. ## Verified, not assumed Canary run against the script **extracted from this YAML** (`yaml.safe_load` → execute), not a hand-copy: ``` PASS 5 real hardcoded secrets -> BLOCKS (detected 5/5) PASS clean (env::var, comment, pragma, OAuth URL) PASS secret in ./src -> BLOCKS PASS crates/ -> ADVISORY today PASS crates/ -> BLOCKS after cutoff PASS literal + env mention after ; -> BLOCKS PASS malformed cutoff -> refuses to run 7 passed, 0 failed ``` Replayed over **all 64 Rust repos** at both `2026-07-21` and post-cutoff `2026-09-01`: **0 regressions, 3 verdicts fixed** (`aerie`, and `heterogenous-mobile-computing` post-cutoff). ## What deliberately still fails `conative-gating`, `echidnabot`, `filesoup` carry genuinely secret-shaped fixtures — a fuzz corpus, and two secret-scanners' own detection test data inside `r#"…"#` raw strings. Those are **real matches on real secret-shaped literals**, so they get the explicit `// scanner-allow: rust-secrets` pragma rather than a pattern loophole that would weaken the gate for everyone. Separate one-line PRs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent aaee1af commit d13ff9f

1 file changed

Lines changed: 17 additions & 5 deletions

File tree

.github/workflows/secret-scanner-reusable.yml

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,13 +175,25 @@ jobs:
175175
fi
176176
echo "Scanning ${#FILES[@]} Rust file(s)."
177177
178+
# Each pattern requires an assignment DIRECTLY to a string literal of
179+
# meaningful length. The previous forms matched `= ... "` — a quote
180+
# anywhere after an equals — which flagged code that hardcodes
181+
# nothing at all. Measured across the 64 Rust repos in the estate
182+
# sweep, every one of these was a false positive:
183+
# let api_key = request["api_key"].as_str().unwrap_or("") (lookup)
184+
# let api_key_file = temp_dir.path().join("config.js") (path)
185+
# Query::new("Here's my api_key=sk-12345") (a demo)
186+
# A credential shorter than 8 characters is not a credential either.
187+
# `static` is accepted alongside `const`: the old `const.*KEY` form
188+
# missed `static AUTH_KEY: &str = "…"`, a genuine hardcoded secret
189+
# (caught by canary, not by inspection).
178190
PATTERNS=(
179-
'const.*SECRET.*=.*"'
180-
'const.*KEY.*=.*"[a-zA-Z0-9]{16,}"'
181-
'const.*TOKEN.*=.*"'
182-
'let.*api_key.*=.*"'
191+
'(const|static)[[:space:]]+(mut[[:space:]]+)?[A-Za-z0-9_]*SECRET[A-Za-z0-9_]*[[:space:]]*(:[^=]*)?=[[:space:]]*b?"[^"]{8,}"'
192+
'(const|static)[[:space:]]+(mut[[:space:]]+)?[A-Za-z0-9_]*KEY[A-Za-z0-9_]*[[:space:]]*(:[^=]*)?=[[:space:]]*b?"[a-zA-Z0-9]{16,}"'
193+
'(const|static)[[:space:]]+(mut[[:space:]]+)?[A-Za-z0-9_]*TOKEN[A-Za-z0-9_]*[[:space:]]*(:[^=]*)?=[[:space:]]*b?"[^"]{8,}"'
194+
'(let|static)[[:space:]]+(mut[[:space:]]+)?[a-z0-9_]*api_key[a-z0-9_]*[[:space:]]*(:[^=]*)?=[[:space:]]*b?"[^"]{8,}"'
183195
'HMAC.*"[a-fA-F0-9]{32,}"'
184-
'password.*=.*"[^"]+"'
196+
'[a-z0-9_]*password[a-z0-9_]*[[:space:]]*(:[^=]*)?=[[:space:]]*b?"[^"]{8,}"'
185197
)
186198
187199
# Exemptions, mirroring the four layers already documented on

0 commit comments

Comments
 (0)