Summary
compileKeyedSql's :name-placeholder regex (src/sql.ts:286-308) has no awareness of string-literal boundaries, unlike rewritePlaceholders in src/placeholders.ts which carefully tracks quotes/comments/dollar-quoting before touching $n placeholders. Any substring of the SQL text shaped like :paramName gets replaced with a positional placeholder, even inside a quoted string literal — silently rewriting literal SQL content with caller-supplied parameter data.
Steps to reproduce
const q = sql.key("notes.search", { email: "" })`
SELECT id FROM users WHERE note = 'contact via :email for help' AND email = :email
`;
compileKeyedSql(q, { email: "attacker@example.com" }).text
// => "...WHERE note = 'contact via $1 for help' AND email = $1"
Expected
Text inside a string literal is inert SQL content and must never be treated as a parameter reference.
Actual
It's silently replaced, changing the query's literal content to whatever the caller passes for that parameter name.
Why it matters
Not an external attacker's direct injection vector (still goes through $n binding), but a silent correctness/data-integrity bug: any keyed query whose text happens to contain a colon-prefixed word matching a declared parameter name (:email, :id, :status are common both as bind names and as ordinary literal text — URLs, help text, JSON snippets) gets that literal text silently substituted. A query author could unknowingly ship a query whose apparent literal text is actually parameter-substituted at runtime.
Acceptance criteria
Definition of done
Fix implemented; regression test added and green; existing SQL-compilation test suite green.
Severity
Medium-High — silent data-integrity corruption, not an external injection vector.
Found via an adversarial stress pass on @askrjs/orm.
Summary
compileKeyedSql's:name-placeholder regex (src/sql.ts:286-308) has no awareness of string-literal boundaries, unlikerewritePlaceholdersin src/placeholders.ts which carefully tracks quotes/comments/dollar-quoting before touching$nplaceholders. Any substring of the SQL text shaped like:paramNamegets replaced with a positional placeholder, even inside a quoted string literal — silently rewriting literal SQL content with caller-supplied parameter data.Steps to reproduce
Expected
Text inside a string literal is inert SQL content and must never be treated as a parameter reference.
Actual
It's silently replaced, changing the query's literal content to whatever the caller passes for that parameter name.
Why it matters
Not an external attacker's direct injection vector (still goes through
$nbinding), but a silent correctness/data-integrity bug: any keyed query whose text happens to contain a colon-prefixed word matching a declared parameter name (:email,:id,:statusare common both as bind names and as ordinary literal text — URLs, help text, JSON snippets) gets that literal text silently substituted. A query author could unknowingly ship a query whose apparent literal text is actually parameter-substituted at runtime.Acceptance criteria
Guardrail (prevent this class of bug): Add fuzz/property-based tests generating random SQL text containing
:word-shaped substrings inside string literals for any keyed-parameter compiler — placeholder rewriting needs the same quote/comment-aware scanning test coverage on every substitution pass, not just the primary `` one.compileKeyedSql's:namesubstitution reuses the same quote/comment/dollar-quote-aware scanner already used for$nrewriting inrewritePlaceholders, instead of a bare global regex.Regression test: the exact repro above, asserting the literal text inside the string is untouched.
Definition of done
Fix implemented; regression test added and green; existing SQL-compilation test suite green.
Severity
Medium-High — silent data-integrity corruption, not an external injection vector.
Found via an adversarial stress pass on
@askrjs/orm.