fix(acr): SET typo-gates unknown-key false positives (M-BUG-10)
The CC settings schema is passthrough (verified against the 2.1.193 binary): it forwards unrecognized keys unchanged rather than rejecting them, so an arbitrary unknown key is valid/forward-compatible, not an error — the finding's "silently ignored" claim was factually wrong. The only real risk is a TYPO of a real key (the intended setting then silently has no effect). Fix: flag an unknown key only when it closely matches a known key (new levenshtein helper; edit distance <= 2, both keys >= 4 chars); severity medium -> low; honest passthrough framing in the scanner + humanizer. Also refreshed KNOWN_KEYS with 6 binary-verified keys (agentPushNotifEnabled, remoteControlAtStartup, skipAutoPermissionPrompt, skipDangerousModePermissionPrompt, skipWorkflowUsageWarning, tui). Suite 1341/0 (+12). Frozen v5.0.0 snapshots untouched (0 CA-SET findings there), no re-seed. Dogfood ~/.claude/settings.json 6->0 (all 6 keys above were false unknown-key findings; 0 typo flags introduced across 167 walked files).
This commit is contained in:
parent
7e94910566
commit
3cf5c714a2
5 changed files with 174 additions and 16 deletions
|
|
@ -8,11 +8,11 @@ import { readTextFile } from './lib/file-discovery.mjs';
|
|||
import { finding, scannerResult } from './lib/output.mjs';
|
||||
import { SEVERITY } from './lib/severity.mjs';
|
||||
import { parseJson } from './lib/yaml-parser.mjs';
|
||||
import { extractKeys } from './lib/string-utils.mjs';
|
||||
import { extractKeys, levenshtein } from './lib/string-utils.mjs';
|
||||
|
||||
const SCANNER = 'SET';
|
||||
|
||||
/** Known top-level settings.json keys (as of CC 2.1.181 / June 2026) */
|
||||
/** Known top-level settings.json keys (as of CC 2.1.193 / June 2026) */
|
||||
const KNOWN_KEYS = new Set([
|
||||
'additionalDirectories',
|
||||
'agent', 'allowAllClaudeAiMcps', 'allowedChannelPlugins', 'allowedHttpHookUrls',
|
||||
|
|
@ -37,6 +37,9 @@ const KNOWN_KEYS = new Set([
|
|||
'spinnerTipsOverride', 'spinnerVerbs', 'statusLine', 'strictKnownMarketplaces',
|
||||
'useAutoModeDuringPlan', 'voiceEnabled', 'wheelScrollAccelerationEnabled',
|
||||
'worktree', '$schema',
|
||||
// CC 2.1.193 binary-verified (M-BUG-10): present as quoted string literals in the binary
|
||||
'agentPushNotifEnabled', 'remoteControlAtStartup', 'skipAutoPermissionPrompt',
|
||||
'skipDangerousModePermissionPrompt', 'skipWorkflowUsageWarning', 'tui',
|
||||
]);
|
||||
|
||||
/** Deprecated keys with migration info */
|
||||
|
|
@ -75,6 +78,16 @@ const VALID_EFFORT_LEVELS = new Set(['low', 'medium', 'high', 'xhigh', 'max']);
|
|||
* a project root to walks/discovery, inflating per-turn cost and confusing scope. */
|
||||
const ADDITIONAL_DIRS_THRESHOLD = 2;
|
||||
|
||||
/** M-BUG-10: the CC settings schema is passthrough — it forwards unrecognized
|
||||
* keys unchanged rather than rejecting them, so an arbitrary unknown key is
|
||||
* valid/forward-compatible, not an error. The only real risk is a TYPO of a
|
||||
* real key (the intended setting silently does nothing), so an unknown key is
|
||||
* flagged ONLY when it closely matches a known key: edit distance within
|
||||
* TYPO_MAX_DISTANCE and both keys at least TYPO_MIN_LEN chars (short keys are
|
||||
* too noisy for reliable edit-distance matching). */
|
||||
const TYPO_MAX_DISTANCE = 2;
|
||||
const TYPO_MIN_LEN = 4;
|
||||
|
||||
/** The only valid sub-keys of `autoMode`, each a prose-rule string array
|
||||
* (the literal "$defaults" is a valid entry). Verified against
|
||||
* code.claude.com/docs/en/auto-mode-config. */
|
||||
|
|
@ -115,17 +128,32 @@ export async function scan(targetPath, discovery) {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Check for unknown keys
|
||||
// Check for unknown keys — typo gate (M-BUG-10). The CC settings schema is
|
||||
// passthrough, so an unrecognized key is NOT an error; only a typo of a real
|
||||
// key is (the intended setting silently does nothing). Flag a key only when
|
||||
// it closely matches a known key; an unknown key far from every known key is
|
||||
// treated as valid/forward-compatible and emitted nothing.
|
||||
for (const key of Object.keys(parsed)) {
|
||||
if (!KNOWN_KEYS.has(key)) {
|
||||
if (KNOWN_KEYS.has(key)) continue;
|
||||
let nearest = null;
|
||||
let best = Infinity;
|
||||
for (const known of KNOWN_KEYS) {
|
||||
if (Math.min(key.length, known.length) < TYPO_MIN_LEN) continue;
|
||||
const d = levenshtein(key, known);
|
||||
if (d <= TYPO_MAX_DISTANCE && d < best) {
|
||||
best = d;
|
||||
nearest = known;
|
||||
}
|
||||
}
|
||||
if (nearest) {
|
||||
findings.push(finding({
|
||||
scanner: SCANNER,
|
||||
severity: SEVERITY.medium,
|
||||
title: 'Unknown settings key',
|
||||
description: `${file.relPath}: "${key}" is not a recognized settings.json key. It will be silently ignored.`,
|
||||
severity: SEVERITY.low,
|
||||
title: 'Possible typo in settings key',
|
||||
description: `${file.relPath}: "${key}" is not a recognized settings.json key, but it closely matches "${nearest}". Claude Code forwards unrecognized keys unchanged (it does not reject them), so if "${key}" is a typo of "${nearest}" the intended setting silently has no effect.`,
|
||||
file: file.absPath,
|
||||
evidence: key,
|
||||
recommendation: 'Check spelling. See https://json.schemastore.org/claude-code-settings.json for valid keys.',
|
||||
recommendation: `Did you mean "${nearest}"? Fix the spelling, or keep "${key}" if it is intentional (e.g. a newer settings key this audit does not recognize yet).`,
|
||||
autoFixable: false,
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue