Skip to content

compileKeyedSql corrupts string literals containing :name-shaped text #12

Description

@smiggleworth

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

  • 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 :name substitution reuses the same quote/comment/dollar-quote-aware scanner already used for $n rewriting in rewritePlaceholders, 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions