Repository navigation
Description
Versions: pg 8.23.1, pg-protocol 1.16.1, Node.js 26.13.0. Compared with libpq from PostgreSQL 19rc1.
What happens: during startup, Client takes the first ReadyForQuery as a finished connection, whatever came before it. A server that answers the startup message with ReadyForQuery alone, sending no authentication message, connects. The client has a password, the server never asks for it, and connect() resolves. libpq refuses the same server.
Reproduction (no PostgreSQL needed: a scripted server stands in for one):
import net from 'node:net'
import pg from 'pg'
// A server that answers the startup message with ReadyForQuery, sending no authentication message
const message = (type, body) => {
const head = Buffer.alloc(5)
head.write(type, 0)
head.writeInt32BE(body.length + 4, 1)
return Buffer.concat([head, body])
}
const server = net.createServer((socket) => {
socket.once('data', () => socket.write(message('Z', Buffer.from('I'))))
})
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve))
const client = new pg.Client({
host: '127.0.0.1',
port: server.address().port,
user: 'app',
password: 'secret',
database: 'app',
})
try {
await client.connect()
console.log('connected')
} catch (error) {
console.log('refused:', error.message)
}
client.end().catch(() => {})
server.close()Output:
connected
libpq against the same server (psql "host=127.0.0.1 port=<port> user=app dbname=app sslmode=disable", with PGPASSWORD=secret):
psql: error: connection to server at "127.0.0.1", port <port> failed: expected authentication request from server, but received Z
Where, in the installed sources:
pg/lib/client.js:261hands every ReadyForQuery to_handleReadyForQuery.pg/lib/client.js:373-385(_handleReadyForQuery): while_connecting, it marks the client connected and calls the connect callback, with no check that authentication happened.- The client has no
authenticationOklistener (:250-261), so nothing records that the server finished authenticating.
Expected: as libpq does, a ReadyForQuery (or any message other than an authentication request, an ErrorResponse or a NegotiateProtocolVersion) before AuthenticationOk ends the connection attempt with an error, for example "expected authentication request from server, but received ReadyForQuery".
Impact: a server can already skip authentication by sending AuthenticationOk, so this is protocol conformance rather than a way in by itself. It matters for code that enforces libpq's require_auth or channel_binding=require on top of pg: it can check the authentication messages it sees, but cannot tell that none came, short of overriding the private _handleReadyForQuery. pgx recently fixed the related bypass on its side (GHSA-95fj-3fxw-g62g).
Related: #1508 ("Unknown authenticationOk message type") is about parsing that message, not about requiring it. I found no issue about this one.
Activity