Skip to content

fix(deparser): escape single quotes in bit-string literals - #357

Merged
pyramation merged 2 commits into
mainfrom
devin/1791230437-deparser-bitstring-escaping
Oct 5, 2026
Merged

pyramation merged 2 commits into
mainfrom
devin/1791230437-deparser-bitstring-escaping

Conversation

@pyramation

@pyramation pyramation commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Fixes #355. sval goes through QuoteUtils.formatEString, but bsval was dropped straight into b'...' / x'...'. So if a programmatically built AST had a quote in bsval, it closed the literal early, and whatever followed was emitted as SQL. Parse → deparse round-trips can't hit this, because a parsed bit string never contains a quote.

Every bit-string emit site now goes through one helper:

function formatBitString(bsval: string): string {
  const quote = (v: string) => v.replace(/'/g, "''");
  if (/^x[0-9A-Fa-f]+$/.test(bsval)) return `x'${bsval.substring(1)}'`;
  if (bsval.startsWith('b')) return `b'${quote(bsval.substring(1))}'`;
  return `b'${quote(bsval)}'`;
}

Sites covered:

  • A_Const: wrapped bsval.bsval, unwrapped bsval, and val.BitString.bsval. That last one used to return the bare value, e.g. b0101, with no quotes at all, which is not valid SQL.
  • The BitString node visitor.

The hex check that A_Const already had now applies to the BitString visitor too. An x-prefixed value with non-hex characters falls through to the escaped b'x…' branch; before, the visitor emitted it raw as x'…'.

With the fix, the payload from the issue deparses to SELECT b'x'' OR ''1''=''1'. libpg-query reads that as one literal and rejects it with a syntax error, so nothing gets injected.

New tests are in __tests__/misc/bit-string.test.ts. 6 of them fail on main. The full deparser suite passes locally.

Link to Devin session: https://app.devin.ai/sessions/a1e43e1e9fb2494fa571e6ecb93e1067
Open in Devin Desktop: https://app.devin.ai/desktop/session/a1e43e1e9fb2494fa571e6ecb93e1067?variant=devin
Requested by: @pyramation

A_Const.bsval (wrapped, unwrapped, val.BitString) and BitString nodes were
interpolated raw into b'...'/x'...', so a quote in a programmatically built
value closed the literal early. Route all sites through formatBitString,
which doubles single quotes like the sval path.
@tenki-reviewer

tenki-reviewer Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Review complete. No blocking issues — approved ✅; 1 nitpick below.

🧹 Nitpicks (1) — 🟢 1 low
  • 🟢 Restore hex validation in formatBitString (deparser.ts:152) — The new formatBitString helper treats any bsval starting with x as hexadecimal without validating the remaining characters (packages/deparser/src/deparser.ts:152).

This PR extracts duplicated bit-string formatting logic in the deparser into a single formatBitString helper used by both A_Const and TypeCast handlers, and adds a test file covering bit-string literal output. The consolidation keeps literal values safely quoted, but the new helper relaxes the prior hex validation for x-prefixed values, which changes output only for malformed or hand-built ASTs.

Files Change
packages/deparser/src/deparser.ts Extracts shared formatBitString helper for A_Const/TypeCast bit-string emission.
packages/deparser/__tests__/misc/bit-string.test.ts Adds deparsing tests for bit-string literals.

Reviewed commit: 24b7869

@devin-ai-integration

Copy link
Copy Markdown
Contributor

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@pyramation
pyramation merged commit 9b53497 into main Oct 5, 2026
31 of 39 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pgsql-deparser: bit-string literal (bsval) not escaped, unlike sibling string literal path (CWE-116)

1 participant