Skip to content

mysql: quote column names and bind every WHERE value - #87

Merged
msimerson merged 1 commit into
mainfrom
mysql-escape-identifiers
Oct 16, 2026
Merged

msimerson merged 1 commit into
mainfrom
mysql-escape-identifiers

Conversation

@msimerson

Copy link
Copy Markdown
Contributor

Mysql.insert(), update() and whereConditions() put object keys into the SQL as column names, and those keys can come from a request payload (CodeQL js/sql-injection, alerts 15 and 28). The routes' Joi schemas reject unknown keys, so nothing reaches them today, but the builders depended on that.

  • Each column name is quoted with mysql2's escapeId, so a key such as is_admin=1,first_name is one quoted identifier.
  • update() takes its WHERE as an object of bound values. The stores passed \nt_x_id=${id}`; all 14 callers now pass { nt_x_id: id }`. A string WHERE still works, for SQL whose placeholders the caller binds, as zone_record: PUT merges onto the stored record #77 does.
  • The group and user subgroup IN (...) lists bind their ids.

LIMIT/OFFSET stay interpolated; both are Number.isInteger-guarded.

Merge after #77, #84 and #86, then rebase: each rewrites some of the same Mysql.update calls. lib/mysql.js merges cleanly with the transaction() that #84 and #86 add.

Tests pass on mysql (497), json (467) and toml (467). lib/mysql.test.js covers the quoting, both WHERE forms, and a hostile key in insert and update.

insert(), update() and whereConditions() put object keys into the SQL as
column names, and those keys can come from a request payload. The Joi
schemas reject unknown keys today, so nothing reaches them, but the
builders relied on that. They now quote each column with escapeId.

update() takes its WHERE as an object of bound values; the stores passed
`nt_x_id=${id}`. A string WHERE still works for SQL whose placeholders
the caller binds. The group and user subgroup IN lists bind their ids.

Clears CodeQL js/sql-injection (alerts 15 and 28).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

Static review found no blocking defects in the builders or migrated callers; database-backed tests were not run during review.

0 open findings

What changed in this PR

Hardens the shared MySQL builders and store callers against SQL injection without relying solely on route validation.

Changes:

  • Quotes column identifiers and supports bound object-based UPDATE conditions.
  • Binds store IDs and subgroup list values.
  • Adds identifier-quoting and WHERE compatibility tests.
File Description
lib/​zone/​store/​mysql.js Binds zone update and delete IDs.
lib/​zone_record/​store/​mysql.js Binds record delete IDs.
lib/​user/​store/​mysql.js Binds user IDs and subgroup lists.
lib/​session/​store/​mysql.js Binds session update IDs.
lib/​permission/​store/​mysql.js Binds permission update and delete IDs.
lib/​nameserver/​store/​mysql.js Binds nameserver update and delete IDs.
lib/​mysql.test.js Tests quoting and both UPDATE condition forms.
lib/​mysql.js Quotes identifiers and binds object-based conditions.
lib/​group/​store/​mysql.js Binds group IDs and subgroup lists.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@msimerson
msimerson merged commit 053dc3b into main Oct 16, 2026
14 checks passed
@msimerson
msimerson deleted the mysql-escape-identifiers branch October 16, 2026 03:58
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.

2 participants