Skip to content

fix(pg): make prepared-statement caches prototypeless - #3767

Closed
nirmal-shaji wants to merge 1 commit into
brianc:masterfrom
nirmal-shaji:fix/parsed-statements-prototype-3625
Closed

fix(pg): make prepared-statement caches prototypeless#3767
nirmal-shaji wants to merge 1 commit into
brianc:masterfrom
nirmal-shaji:fix/parsed-statements-prototype-3625

Conversation

@nirmal-shaji

Copy link
Copy Markdown

Problem

A prepared statement whose name collides with an Object.prototype key — most obviously constructor, but also hasOwnProperty, toString, __proto__, etc. — is mishandled. The client believes the statement has already been prepared when it has not, so it skips the Parse message and sends a Bind for a statement the backend has never seen, producing a protocol error rather than running the query.

Cause

Connection tracks which named statements have been prepared using plain object literals:

this.parsedStatements = {}
this.submittedNamedStatements = {}

The native Client does the same with this.namedQueries = {}. These caches are keyed by user-supplied statement names, and Query#hasBeenParsed / Query#submit test membership with a simple truthy lookup:

connection.parsedStatements[this.name] || connection.submittedNamedStatements[this.name]

Because the objects inherit from Object.prototype, parsedStatements['constructor'] resolves to Object.prototype.constructor (truthy) on a brand-new connection, so Parse is never sent.

Fix

Create the three caches with Object.create(null) so lookups only ever reflect statements that were actually prepared. This matches the request in #3625 (make parsedStatements prototypeless, alongside namedQueries on the native client); submittedNamedStatements is fixed too since it is consulted in the same || expression and shares the flaw. No API surface changes.

Testing

Added a pure-Node unit test (no live Postgres) at packages/pg/test/unit/connection/prepared-statement-name-tests.js, mirroring the existing test/unit/connection tests and using the in-memory stream helper. It asserts that:

  • a fresh connection reports no prepared statements for Object.prototype names;
  • a statement named constructor sends a Parse before its Bind;
  • hasBeenParsed is false for unprepared prototype-named statements.

The test fails on master and passes with this change. The full packages/pg unit suite (make test-unit, run file-by-file with node) remains green.

Targeting the default branch (master); the issue is tagged toward the pg@9.0 milestone — happy to retarget if a release branch is preferred.

Fixes #3625

The connection tracks already-prepared statements in `parsedStatements`
and `submittedNamedStatements` (and `namedQueries` on the native client),
all created as plain `{}` objects. Because those inherit from
`Object.prototype`, a prepared statement named after a prototype key such
as `constructor` reads back as already-prepared even though it was never
sent to the backend. The client then skips the `Parse` message and issues
a `Bind` for a statement the server has never seen.

Create these caches with `Object.create(null)` so lookups only ever see
statements that were actually prepared.

Fixes brianc#3625
@charmander

Copy link
Copy Markdown
Collaborator

#3732 (comment)

@charmander charmander closed this Sep 5, 2026
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.

Make Connection’s parsedStatements a prototypeless object

2 participants