Skip to content

fix(parser): preserve numeric credentials instead of dropping them (false NO_AUTH + missed WEAK_API_KEY)#50

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

fix(parser): preserve numeric credentials instead of dropping them (false NO_AUTH + missed WEAK_API_KEY)#50
dmchaledev wants to merge 1 commit into
mainfrom
claude/amazing-franklin-c3vwjz

Conversation

@dmchaledev

Copy link
Copy Markdown
Contributor

Summary

An MCP config whose auth.apiKey/auth.token is written as an unquoted all-digit value is parsed as a number — by JSON.parse, and by the YAML scalar coercion in parseYamlScalar. But normalizeConfig only accepted typeof === 'string' credentials, so the value was silently dropped. Consequences, both on the tool's headline CI-gate path:

  • False positive (CRITICAL): an authenticated config with a strong numeric credential is flagged NO_AUTH and fails the gate.
  • False negative (HIGH): a weak (short) numeric token/apiKey slips past WEAK_API_KEY.

The YAML path was additionally corrupting long numeric credentials via lossy Number() coercion — a 32-digit key 92345678901234567890123456789012 became 9.2345678901234568e+31.

Repro (built CLI at main, before this change)

// only-numkey.yaml — authenticated with a strong 32-digit apiKey
transport:
  url: https://x.example.com
  tls: true
  auth:
    apiKey: 92345678901234567890123456789012
rateLimit:
  enabled: true
$ node dist/cli.js only-numkey.yaml --format=table
CRITICAL | NO_AUTH | No Authentication Configured        # <- false positive; FAILED

The JSON twin ("apiKey": "92345678901234567890123456789012") PASSES — same config, opposite verdict.

// weak numeric token
auth: { type: bearer, token: 12345678 }
$ node dist/cli.js weak.yaml --format=table
Found 0 finding(s)  →  PASSED       # WEAK_API_KEY silently skipped

Root cause

  • src/parser.ts parseYamlScalar: if (!isNaN(Number(value))) return Number(value); coerces any numeric-looking scalar to a number, losing precision for long digit strings and leading zeros.
  • src/parser.ts normalizeConfig: if (typeof a['apiKey'] === 'string') … (and the token sibling) reject a numeric credential, so it never reaches NO_AUTH / WEAK_API_KEY.

Fix

  • normalizeConfig — coerce a numeric apiKey/token to its string form via a small coerceCredential helper, so both rules evaluate it. This also fixes JSON configs with an unquoted numeric credential, not just YAML.
  • parseYamlScalar — only coerce a scalar to a number when it round-trips exactly (String(Number(v)) === v). Long numeric credentials/IDs (beyond Number.MAX_SAFE_INTEGER), leading-zero values, and exponent/hex forms are preserved as strings instead of being mangled. Genuine numeric fields (rateLimit.requestsPerMinute: 60) round-trip and stay numbers.

Both changes are localized to src/parser.ts; no new dependencies, still zero-dependency indentation-driven YAML.

Tests

Adds regression coverage in src/__tests__/parser.test.ts:

  • a strong all-digit apiKey → preserved exactly, no NO_AUTH, no WEAK_API_KEY;
  • a weak all-digit token → flagged WEAK_API_KEY;
  • JSON/YAML agree on a numeric credential;
  • requestsPerMinute still parses as a number; leading-zero scalars stay strings.

Full suite: typecheck + lint clean, 123 tests pass.

Scope / relation to backlog

Distinct from the existing backlog. Issue #19 (already resolved in code) was about nested transport.auth maps and tools: sequences being dropped entirely; this is a separate root cause — numeric scalar coercion — that survives that fix. No open issue or PR touches numeric-credential handling.

Acceptance criteria

  • A config authenticated with a numeric apiKey/token does not emit a false NO_AUTH.
  • A weak numeric credential is flagged by WEAK_API_KEY.
  • Long numeric and leading-zero values are preserved, not corrupted.
  • rateLimit.requestsPerMinute and other genuine numeric fields are unaffected.
  • JSON and YAML produce identical results for numeric credentials.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UkdL3AQvdFGEZDdzwbzpDj


Generated by Claude Code

An unquoted all-digit `auth.apiKey`/`auth.token` is parsed as a number
(by JSON, and by YAML scalar coercion). `normalizeConfig` only accepted
string credentials, so the value was silently dropped — yielding a false
CRITICAL NO_AUTH on an authenticated config and skipping WEAK_API_KEY on a
weak one. A long numeric string was additionally corrupted by lossy
Number() coercion in the YAML path (e.g. a 32-digit key became 9.2e+31).

- normalizeConfig: coerce numeric apiKey/token to their string form so
  NO_AUTH and WEAK_API_KEY evaluate them.
- parseYamlScalar: only coerce to a number when it round-trips exactly,
  preserving long numeric and leading-zero values as strings.

Genuine numeric fields (e.g. rateLimit.requestsPerMinute) are unaffected.
Adds parser/scan regression tests covering strong and weak numeric
credentials and JSON/YAML parity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkdL3AQvdFGEZDdzwbzpDj
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