From e238eb8d3ca48a22716e6a92fe690b1b4f94bd00 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 16 May 2026 18:06:24 +0100 Subject: [PATCH] fix(scanner): error on unknown --matchers slugs instead of silently ignoring When `--matchers` includes a slug that doesn't exist in the registry, deepsec now throws with the unknown slug names and lists all available matchers. Previously unknown slugs were silently dropped, making invalid scan requests look successful. Fixes #34 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../scanner/src/__tests__/scan-files.test.ts | 12 ++++++++++ packages/scanner/src/index.ts | 22 +++++++++++++++++++ packages/scanner/src/matcher-registry.ts | 5 +++++ 3 files changed, 39 insertions(+) diff --git a/packages/scanner/src/__tests__/scan-files.test.ts b/packages/scanner/src/__tests__/scan-files.test.ts index 7f85987..e32c8ca 100644 --- a/packages/scanner/src/__tests__/scan-files.test.ts +++ b/packages/scanner/src/__tests__/scan-files.test.ts @@ -103,4 +103,16 @@ describe("scanFiles()", () => { const after = readFileRecord(projectId, "src/x.ts")!; expect(after.candidates.length).toBe(candCountBefore); }); + + it("throws on unknown matcher slugs", async () => { + const { root, projectId } = makeProject({ "a.ts": "x\n" }); + await expect( + scanFiles({ + projectId, + root, + filePaths: ["a.ts"], + matcherSlugs: ["xss", "does-not-exist", "also-fake"], + }), + ).rejects.toThrow(/Unknown matcher slugs: does-not-exist, also-fake/); + }); }); diff --git a/packages/scanner/src/index.ts b/packages/scanner/src/index.ts index d953f08..16f6ce0 100644 --- a/packages/scanner/src/index.ts +++ b/packages/scanner/src/index.ts @@ -367,6 +367,17 @@ export async function scan(params: { languageStats: LanguageStats[]; }> { const registry = buildMergedRegistry(); + + if (params.matcherSlugs) { + const unknown = registry.unknownSlugs(params.matcherSlugs); + if (unknown.length > 0) { + throw new Error( + `Unknown matcher slug${unknown.length > 1 ? "s" : ""}: ${unknown.join(", ")}.\n` + + ` Available matchers: ${registry.slugs().sort().join(", ")}`, + ); + } + } + const allSelected = params.matcherSlugs ? registry.getBySlugs(params.matcherSlugs) : registry.getAll(); @@ -550,6 +561,17 @@ export async function scanFiles(params: { skippedMatchers: string[]; }> { const registry = buildMergedRegistry(); + + if (params.matcherSlugs) { + const unknown = registry.unknownSlugs(params.matcherSlugs); + if (unknown.length > 0) { + throw new Error( + `Unknown matcher slug${unknown.length > 1 ? "s" : ""}: ${unknown.join(", ")}.\n` + + ` Available matchers: ${registry.slugs().sort().join(", ")}`, + ); + } + } + const allSelected = params.matcherSlugs ? registry.getBySlugs(params.matcherSlugs) : registry.getAll(); diff --git a/packages/scanner/src/matcher-registry.ts b/packages/scanner/src/matcher-registry.ts index 60350c7..b232b6e 100644 --- a/packages/scanner/src/matcher-registry.ts +++ b/packages/scanner/src/matcher-registry.ts @@ -21,6 +21,11 @@ export class MatcherRegistry { .filter((m): m is MatcherPlugin => m !== undefined); } + /** Returns slug strings from `requested` that don't exist in the registry. */ + unknownSlugs(requested: string[]): string[] { + return requested.filter((s) => !this.matchers.has(s)); + } + slugs(): string[] { return Array.from(this.matchers.keys()); }