From ba5c796b3621749edfc057b0ef67f79d8febb27e Mon Sep 17 00:00:00 2001 From: Claude Opus 5 Date: Mon, 7 Sep 2026 03:11:19 +0200 Subject: [PATCH] docs(analytics): anchor two security claims to the measurements behind them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neither changes behaviour; both replace an assertion with a measurement. BI-P014's escaping strategy rests on DuckDB treating a backslash as a literal character, so that doubling `'` is sufficient. Verified on 1.5.5 rather than assumed: `SELECT length('a\\b')` returns 4, and `SELECT 'a\''` is a parser error ("unterminated quoted string") — which is exactly what a NON-escaping backslash produces. In a dialect where `\'` closed the literal, doubling would not be enough, so the claim is worth pinning to a run. BI-P017's FROM/JOIN scan is conservative where it is unsure, and that has a cost worth naming rather than discovering: ClickHouse's `ARRAY JOIN arr` takes a COLUMN, not a table, and this guard refuses it. A false refusal an analyst can see and report is the right side of that trade; a false ADMISSION is not. Supporting it means teaching the scan which JOIN flavours take a non-table target, which is the direction that grows holes — so it stays refused until someone needs it and can add it WITH a corpus entry proving the narrowing did not open anything else. Co-Authored-By: Claude Opus 5 --- src/analytics/sql/statement-guard.ts | 9 +++++++++ tests/analytics/duckdb-provider.test.ts | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/src/analytics/sql/statement-guard.ts b/src/analytics/sql/statement-guard.ts index 2255b8f..c9ba31c 100644 --- a/src/analytics/sql/statement-guard.ts +++ b/src/analytics/sql/statement-guard.ts @@ -38,6 +38,15 @@ * table reference is checked positively against the tenant's catalog. * * Outside string literals the statement must be ASCII. Homoglyph and * zero-width tricks against keyword matching stop being expressible. + * * The FROM/JOIN scan is CONSERVATIVE where it is unsure, and that costs + * something worth naming: ClickHouse's `ARRAY JOIN arr` is a legitimate + * construct whose target is a COLUMN, not a table, and this guard refuses + * it because the token after JOIN is neither a CTE nor a qualified object. + * A false refusal an analyst can see and report is the right side of that + * trade; a false ADMISSION is not. Supporting it means teaching the scan + * which JOIN flavours take a non-table target, which is the direction that + * grows holes, so it stays refused until someone needs it and can add it + * WITH a corpus entry proving the narrowing did not open anything else. * * Comments and string literals are removed BEFORE any keyword scan, using * ClickHouse's own lexical rules (`--` and `#` to end of line, `/* *\/` * non-nesting, `'` with backslash and doubled-quote escapes, `"` and diff --git a/tests/analytics/duckdb-provider.test.ts b/tests/analytics/duckdb-provider.test.ts index d6ddcc1..9039821 100644 --- a/tests/analytics/duckdb-provider.test.ts +++ b/tests/analytics/duckdb-provider.test.ts @@ -217,6 +217,12 @@ describe('BI-P014 — the query builder has no caller-SQL surface', () => { }); it("escapes a literal by DOUBLING the quote, which is DuckDB's only escape", () => { + // MEASURED on DuckDB 1.5.5 rather than assumed, because the whole escaping + // strategy rests on it: `SELECT length('a\\b')` returns 4, so a backslash + // is a literal character; and `SELECT 'a\''` is a PARSER ERROR + // ("unterminated quoted string"), which is what a non-escaping backslash + // produces. A dialect where `\'` closed the literal would make doubling + // insufficient. expect(escapeLiteral("O'Brien")).toBe("O''Brien"); // A backslash is NOT an escape inside a DuckDB single-quoted string, so it // must survive untouched rather than being "helpfully" doubled.