diff --git a/src/__tests__/args.test.ts b/src/__tests__/args.test.ts index 711df3a..0d5104f 100644 --- a/src/__tests__/args.test.ts +++ b/src/__tests__/args.test.ts @@ -78,6 +78,14 @@ describe('parseArgs()', () => { }); }); + // Regression for #11: a typo'd rule ID must fail loudly, not silently filter + // the rule set to nothing and report a passing (false-negative) scan. + it('rejects an unknown --rule value with exit code 2', () => { + const result = parseArgs(['--rule=NOAUTH', './mcp.json']); + expect(result).toMatchObject({ kind: 'error', exitCode: 2 }); + expect((result as { message: string }).message).toContain('NOAUTH'); + }); + it('detects URL targets and routes them to serverUrl', () => { for (const url of [ 'http://x.example.com', diff --git a/src/args.ts b/src/args.ts index 7388c92..e2ce54b 100644 --- a/src/args.ts +++ b/src/args.ts @@ -36,6 +36,10 @@ const SEVERITY_BY_NAME: Record = { info: Severity.INFO, }; +/** Valid rule IDs, used to reject typo'd `--rule` values rather than silently + * filtering the rule set down to nothing (a false "PASSED" security gate). */ +const VALID_RULE_IDS = new Set(Object.values(RuleId)); + /** * Parse raw CLI arguments into a structured, side-effect-free result. */ @@ -80,7 +84,17 @@ export function parseArgs(args: string[]): CliParseResult { }; } } else if (arg.startsWith('--rule=')) { - rules.push(arg.slice('--rule='.length) as RuleId); + const val = arg.slice('--rule='.length); + if (!VALID_RULE_IDS.has(val)) { + return { + kind: 'error', + message: + `Error: Unknown rule "${val}". Valid rule IDs: ` + + `${Object.values(RuleId).join(', ')}.`, + exitCode: 2, + }; + } + rules.push(val as RuleId); } else if (!arg.startsWith('--')) { target = arg; } else { diff --git a/src/cli.ts b/src/cli.ts index 9f0033f..43cbe69 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -16,7 +16,7 @@ import { scan } from './scanner.js'; import { toSarif } from './sarif.js'; -import { Severity, Finding } from './types.js'; +import { Severity, Finding, RuleId } from './types.js'; import { parseArgs } from './args.js'; const SEVERITY_ORDER: Record = { @@ -39,6 +39,9 @@ function printHelp(): void { console.log(' --rule=RULE_ID Run only the specified rule (repeatable)'); console.log(' --help, -h Show this help message'); console.log(''); + console.log('Valid rule IDs:'); + console.log(` ${Object.values(RuleId).join(', ')}`); + console.log(''); console.log('Examples:'); console.log(' mcp-security-scanner ./mcp-server.json'); console.log(' mcp-security-scanner --format=table ./mcp-server.json');