Skip to content

fix: EXPOSED_SECRETS silently misses hardcoded passwords in configs#14

Open
dmchaledev wants to merge 1 commit into
mainfrom
claude/amazing-franklin-S4Iuc
Open

fix: EXPOSED_SECRETS silently misses hardcoded passwords in configs#14
dmchaledev wants to merge 1 commit into
mainfrom
claude/amazing-franklin-S4Iuc

Conversation

@dmchaledev
Copy link
Copy Markdown
Contributor

Summary

A CRITICAL-severity false negative in the flagship secret-scanning feature: a hardcoded password in an MCP config file is not flagged by the EXPOSED_SECRETS rule.

Repro (before this PR)

// config.json
{
  "serverUrl": "https://x.example.com",
  "database": { "password": "hunter2supersecret" }
}
$ node dist/cli.js config.json --format=table
Found 1 finding(s): 0 critical, 0 high, 1 medium, 0 low   # only MISSING_RATE_LIMIT — the password is missed

Root cause

EXPOSED_SECRETS (src/rules/runtime-rules.ts) scans config.rawStrings, which is built by extractStrings() in src/parser.ts. That helper collected string values in isolation:

{ "password": "hunter2supersecret" }  ->  rawStrings: ["hunter2supersecret"]

But the rule's password pattern needs the surrounding key:

/[Pp]assword\s*[=:]\s*\S{8,}/   // needs "password: <value>"

Since the key (password) was stripped away from the value, this pattern could never match a structured JSON/YAML config — it was effectively dead code for the primary use case (scanning config files). The token-shaped patterns (sk-, ghp_, AKIA) happened to still work because they match the value directly.

Fix

extractStrings() now extracts an object's string value with its key context ("key: value") instead of the bare value. Key-aware patterns (like the password detector) can now match, while the unanchored token-shaped patterns continue to match inside the reconstructed string. Strings inside arrays (which have no key context) are still pushed bare.

-      for (const v of Object.values(value as Record<string, unknown>)) {
-        extractStrings(v, result);
+      for (const [key, v] of Object.entries(value as Record<string, unknown>)) {
+        if (typeof v === 'string') {
+          result.push(`${key}: ${v}`);
+        } else {
+          extractStrings(v, result);
+        }
       }

Verification

$ node dist/cli.js config.json --format=table
CRITICAL   | EXPOSED_SECRETS  | Potential Secret Exposed in Configuration
Found 2 finding(s): 1 critical, 0 high, 1 medium, 0 low   # password now detected

examples/secure-config.json     -> 0 findings, PASSED   (no false positives)
examples/vulnerable-config.json -> still detects its secrets
typecheck (tsc --noEmit)  OK
lint (eslint, 0 warnings) OK

Added src/__tests__/parser.test.ts (3 tests): the password regression, token-shaped secrets still firing inside key: value, and no false positive on a clean config. Scoped to a new file so it does not import src/sarif.ts and therefore runs cleanly under the current Jest config.

Note on CI

CI on main is currently red for an unrelated, pre-existing reason — the src/sarif.ts import.meta/createRequire compile error under ts-jest's CommonJS transform (introduced in #9, fixed by the open PR #12). That breaks the scanner.test.ts and sarif.test.ts suites regardless of this change. This PR adds no new failures: the scorer and new parser suites pass locally (13 tests). Once #12 lands, the full suite — including the new regression test — runs green.

This change is independent of #12 (CI) and #13 (docs); it touches only the parser's secret-extraction logic.

https://claude.ai/code/session_01BcFiXXLeus31dWFPBYALgn


Generated by Claude Code

extractStrings() in the parser collected config string values in
isolation, so the key-aware password pattern (password: <value>) in the
EXPOSED_SECRETS rule could never match a structured JSON/YAML config — a
hardcoded "password": "..." field was silently ignored by the CRITICAL
secret-scanning rule.

Extract object string values with their key context ("key: value") so
key-dependent patterns fire, while token-shaped patterns (sk-, ghp_,
AKIA) continue to match unanchored inside the reconstructed string.

Add src/__tests__/parser.test.ts covering the regression and confirming
no false positives on a clean config.

https://claude.ai/code/session_01BcFiXXLeus31dWFPBYALgn
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants