From 81fee21b34ad72a22bf91e76ac594c8cfb41dc35 Mon Sep 17 00:00:00 2001 From: Cid-oe Date: Wed, 9 Sep 2026 00:00:59 +0530 Subject: [PATCH 1/2] fix(engine): ignore FROM in SQL string literals and comments (#77) --- packages/engine/src/resource-access.ts | 16 +++++++++++++++- tests/resource-access-coverage.test.mjs | 15 +++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/engine/src/resource-access.ts b/packages/engine/src/resource-access.ts index 33ea88b..05b82ad 100644 --- a/packages/engine/src/resource-access.ts +++ b/packages/engine/src/resource-access.ts @@ -554,7 +554,19 @@ function normalizeSqlSelector(selector: string): string { return normalizedSelector.trim(); } +function stripSqlCommentsAndLiterals(sql: string): string { + // Strip block comments /* ... */ + // Strip single-line comments -- ... + // Strip single-quoted string literals '...' (handling escaped '' or \') + // Note: We preserve double-quoted identifiers ("table_name") for selector matching. + return sql + .replace(/\/\*[\s\S]*?\*\//g, " ") + .replace(/--.*$/gm, " ") + .replace(/'(?:''|\\'|[^'])*'/g, "''"); +} + function sqlTableAccesses(text: string): Array<{ access: "read" | "write"; selector: string }> { + const sanitized = stripSqlCommentsAndLiterals(text); const accesses: Array<{ access: "read" | "write"; selector: string }> = []; const identifier = String.raw`(?:"[^"]+"|[A-Za-z_][A-Za-z0-9_$]*)(?:\s*\.\s*(?:"[^"]+"|[A-Za-z_][A-Za-z0-9_$]*))*`; const sqlPattern = new RegExp( @@ -562,7 +574,7 @@ function sqlTableAccesses(text: string): Array<{ access: "read" | "write"; selec "gi", ); let match: RegExpExecArray | null; - while ((match = sqlPattern.exec(text)) !== null) { + while ((match = sqlPattern.exec(sanitized)) !== null) { const verb = match[1].toLowerCase(); const selector = normalizeSqlSelector(match[2]); accesses.push({ access: verb === "from" || verb === "join" ? "read" : "write", selector }); @@ -1802,5 +1814,7 @@ export const resourceAccessTestHooks = { routeReceiverLooksHttp, queueReceiverLooksExternal, selectorLooksQueueTopic, + sqlTableAccesses, + stripSqlCommentsAndLiterals, }; // Stryker restore all diff --git a/tests/resource-access-coverage.test.mjs b/tests/resource-access-coverage.test.mjs index 2edb5f4..06f05a3 100644 --- a/tests/resource-access-coverage.test.mjs +++ b/tests/resource-access-coverage.test.mjs @@ -2286,3 +2286,18 @@ test("bug #52 supported Fastify route methods do not depend on scan hints", (tes } } }); + +test("bug #77 FROM in SQL string literals and comments is not mistaken for a table reference", () => { + const { sqlTableAccesses } = resourceAccessTestHooks; + assert.deepEqual(sqlTableAccesses("SELECT 'FROM ghost_table' AS label"), []); + assert.deepEqual(sqlTableAccesses("SELECT 1 /* FROM ghost_table */"), []); + assert.deepEqual(sqlTableAccesses("SELECT 1 -- FROM ghost_table"), []); + assert.deepEqual(sqlTableAccesses("SELECT id FROM actual_table"), [ + { access: "read", selector: "actual_table" }, + ]); + assert.deepEqual( + sqlTableAccesses("SELECT id FROM actual_table WHERE note = 'FROM false_table' /* JOIN ignored_table */"), + [{ access: "read", selector: "actual_table" }], + ); +}); + From 774ee874e23bcf92d1e8e27150fc3c9eabe2c5e7 Mon Sep 17 00:00:00 2001 From: pushnanashi2 Date: Thu, 10 Sep 2026 00:42:04 +0900 Subject: [PATCH 2/2] fix(engine): harden SQL comment and literal masking --- packages/engine/src/resource-access.ts | 63 +++++++++++++++++--- tests/resource-access-coverage.test.mjs | 79 +++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 8 deletions(-) diff --git a/packages/engine/src/resource-access.ts b/packages/engine/src/resource-access.ts index 05b82ad..0da6f4b 100644 --- a/packages/engine/src/resource-access.ts +++ b/packages/engine/src/resource-access.ts @@ -555,14 +555,61 @@ function normalizeSqlSelector(selector: string): string { } function stripSqlCommentsAndLiterals(sql: string): string { - // Strip block comments /* ... */ - // Strip single-line comments -- ... - // Strip single-quoted string literals '...' (handling escaped '' or \') - // Note: We preserve double-quoted identifiers ("table_name") for selector matching. - return sql - .replace(/\/\*[\s\S]*?\*\//g, " ") - .replace(/--.*$/gm, " ") - .replace(/'(?:''|\\'|[^'])*'/g, "''"); + const sanitizedCharacters = sql.split(""); + let state: + | "single-quoted-literal" + | "double-quoted-identifier" + | "line-comment" + | "block-comment" + | undefined; + for (let index = 0; index !== sql.length; index += 1) { + const currentCharacter = sql[index]; + const nextCharacter = sql[index + 1]; + + if (state === "single-quoted-literal") { + sanitizedCharacters[index] = " "; + if (currentCharacter === "'") state = undefined; + continue; + } + + if (state === "double-quoted-identifier") { + if (currentCharacter === "\"") state = undefined; + continue; + } + + if (state === "line-comment") { + sanitizedCharacters[index] = " "; + if (currentCharacter === "\n") state = undefined; + continue; + } + + if (state === "block-comment") { + sanitizedCharacters[index] = " "; + if (currentCharacter === "*" && nextCharacter === "/") { + sanitizedCharacters[index + 1] = " "; + index += 1; + state = undefined; + } + continue; + } + + if (currentCharacter === "'") { + sanitizedCharacters[index] = " "; + state = "single-quoted-literal"; + } else if (currentCharacter === "\"") { + state = "double-quoted-identifier"; + } else if (currentCharacter === "-" && nextCharacter === "-") { + sanitizedCharacters[index] = " "; + state = "line-comment"; + } else if (currentCharacter === "/" && nextCharacter === "*") { + sanitizedCharacters[index] = " "; + sanitizedCharacters[index + 1] = " "; + index += 1; + state = "block-comment"; + } + } + + return sanitizedCharacters.join(""); } function sqlTableAccesses(text: string): Array<{ access: "read" | "write"; selector: string }> { diff --git a/tests/resource-access-coverage.test.mjs b/tests/resource-access-coverage.test.mjs index 06f05a3..9e804f8 100644 --- a/tests/resource-access-coverage.test.mjs +++ b/tests/resource-access-coverage.test.mjs @@ -2301,3 +2301,82 @@ test("bug #77 FROM in SQL string literals and comments is not mistaken for a tab ); }); +test("bug #77 SQL masking preserves lexical boundaries and table separators", () => { + const { sqlTableAccesses, stripSqlCommentsAndLiterals } = resourceAccessTestHooks; + const readAccesses = (...selectors) => selectors.map((selector) => ({ access: "read", selector })); + const cases = [ + { + sql: "SELECT '-- literal', '/*', '*/', 'it''s FROM ghost' FROM actual_table", + expected: readAccesses("actual_table"), + }, + { + sql: "SELECT 'abc\\' FROM actual_table", + expected: readAccesses("actual_table"), + }, + { + sql: "SELECT '/*' AS marker FROM swallowed_table WHERE note = '*/'", + expected: readAccesses("swallowed_table"), + }, + { + sql: "SELECT'x'FROM/* JOIN ghost */actual_table", + expected: readAccesses("actual_table"), + }, + { + sql: "SELECT 1 -- ' FROM ghost_one\nFROM actual_table\n-- JOIN ghost_two", + expected: readAccesses("actual_table"), + }, + { + sql: "SELECT 1 -- FROM ghost_one\rFROM actual_table", + expected: [], + }, + { + sql: "SELECT 1 -- FROM ghost_one\r\nFROM actual_table", + expected: readAccesses("actual_table"), + }, + { + sql: 'SELECT * FROM "orders--archive" JOIN "items/*current*/"', + expected: readAccesses("orders--archive", "items/*current*/"), + }, + { + sql: 'SELECT * FROM "actual_table" /* JOIN ghost_table */', + expected: readAccesses("actual_table"), + }, + { + sql: "SELECT 1 /* ' -- FROM ghost */ FROM actual_table", + expected: readAccesses("actual_table"), + }, + { + sql: "SELECT * /*/*/ FROM actual_table", + expected: readAccesses("actual_table"), + }, + { + sql: "SELECT 1/*x*/*2 FROM actual_table", + expected: readAccesses("actual_table"), + }, + { + sql: "SELECT 8 / 2 * 3 - 1 FROM actual_table", + expected: readAccesses("actual_table"), + }, + { + sql: "SELECT 1 /* 8 / 2 * 3 FROM ghost_table */ FROM actual_table", + expected: readAccesses("actual_table"), + }, + { sql: "SELECT 1 /* FROM ghost_table", expected: [] }, + { + sql: "SELECT 1/*/ FROM ghost_table */ FROM actual_table", + expected: readAccesses("actual_table"), + }, + { sql: "SELECT 'FROM ghost_table", expected: [] }, + ]; + + for (const { sql, expected } of cases) { + assert.equal(stripSqlCommentsAndLiterals(sql).length, sql.length, sql); + assert.deepEqual(sqlTableAccesses(sql), expected, sql); + } + + const entirelyIgnored = "'x'--y\n/*z*/"; + assert.equal(stripSqlCommentsAndLiterals(entirelyIgnored), " ".repeat(entirelyIgnored.length)); + + const quotedIdentifiers = 'SELECT * FROM "orders--archive" JOIN "items/*current*/"'; + assert.equal(stripSqlCommentsAndLiterals(quotedIdentifiers), quotedIdentifiers); +});