Skip to content

Reject backtick/backslash in SQL identifiers to close injection vector#463

Merged
goccy merged 1 commit into
mainfrom
fix/sql-injection-identifier-escaping
May 18, 2026
Merged

Reject backtick/backslash in SQL identifiers to close injection vector#463
goccy merged 1 commit into
mainfrom
fix/sql-injection-identifier-escaping

Conversation

@goccy

@goccy goccy commented May 18, 2026

Copy link
Copy Markdown
Owner

Summary

Follow-up to #462, which addressed the CodeQL go/sql-injection alert
(#5) by adding escapeIdent (backtick doubling).

Escaping alone is not sufficient. A query first passes through the GoogleSQL (ZetaSQL) parser and is then re-emitted for the SQLite backend, and the two layers escape a backtick differently:

  • GoogleSQL quoted identifiers escape a backtick with a backslash (\`).
  • SQLite escapes a backtick by doubling it ( ``).

A crafted name containing both a backtick and a backslash survives one layer's escaping and breaks out of the quoted identifier in the other. This was verified empirically against googlesqlite: a table name of a\`; DROP TABLE victim; -- still executed the injected DROP TABLE despite the doubling applied by escapeIdent.

Fix

Reject identifiers rather than rely on escaping:

  • Add validateIdent, which rejects any identifier containing a backtick or backslash.
  • tablePath / routinePath now return an error, and every public Repository method validates its project / dataset / table / view / routine / column names before building a query.
  • Neither character is valid in a BigQuery identifier, so this closes the injection vector (CWE-89) without depending on the backend's escaping behavior and without affecting any legitimate name.

escapeIdent is kept as defense-in-depth for the remaining characters.

Tests

  • New unit tests in internal/contentdata/repository_test.go covering validateIdent, the new tablePath / routinePath signatures, and the specific injection payload that defeated escaping.
  • go build ./... passes.
  • The full go test ./server/ suite passes — table create/insert/query paths are unaffected.

🤖 Generated with Claude Code

PR #462 added escapeIdent (backtick doubling) for the go/sql-injection
alert, but escaping alone is not sufficient. A query first passes through
the GoogleSQL (ZetaSQL) parser and is then re-emitted for the SQLite
backend, and the two layers escape a backtick differently (GoogleSQL uses
a backslash, SQLite doubles the backtick). A crafted name containing a
backtick and a backslash survives one layer's escaping and breaks out of
the quoted identifier in the other; this was verified to execute an
injected DROP TABLE despite the doubling applied by escapeIdent.

Add validateIdent and reject any project, dataset, table, view, routine
or column name that contains a backtick or backslash. Neither character
is valid in a BigQuery identifier, so this closes the injection vector
(CWE-89) without depending on the backend's escaping behavior and without
affecting any legitimate name. tablePath/routinePath now return an error,
and every public Repository method validates its identifiers before
building a query.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@goccy goccy force-pushed the fix/sql-injection-identifier-escaping branch from 41cb98b to ec4ceb2 Compare May 18, 2026 06:38
@goccy goccy changed the title Escape SQL identifiers to prevent injection via table/column names Reject backtick/backslash in SQL identifiers to close injection vector May 18, 2026
@goccy goccy merged commit db5cfcc into main May 18, 2026
12 checks passed
@goccy goccy deleted the fix/sql-injection-identifier-escaping branch May 18, 2026 07:27
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.

1 participant