world-postgres: LISTEN clients lose non-enumerable pg password credentials
Maintainer thường phản hồi trong vòng 1 ngày
@vishalrajofficial đang làm issue này rồi.
Từ ngày 15/11/2026.
- #5453 của @vishalrajofficial — đang mở
Đánh giá
- Độ khó
- 3/5
- Thời gian dự kiến
- Nửa ngày
- Mức phù hợp với người mới
- 25/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- nodejs, postgresql, typescript
- Lĩnh vực
- authentication, backend, databases
Hướng nghiên cứu
Start with listenChannel in packages/world-postgres/src/streamer.ts, where the reconnect path builds its config with { ...pool.options, connectionTimeoutMillis }, and with the same spread in packages/world-postgres/src/invocation-notifications.ts. Confirm that pool.options.password is non-enumerable in pg, then check that replacement clients keep the password callback after a reconnect. Pull request #5453 is already open against this issue, so review or test that fix rather than starting a parallel one.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Summary
The dedicated PostgreSQL notification clients lose authentication credentials when they copy pool.options with object spread. pg.Pool deliberately makes options.password non-enumerable, so the copy omits both static passwords and async password callbacks.
Two paths are affected:
listenChannelin streamer.ts: the initial subscription receivespool.optionsdirectly and succeeds, but reconnect addsconnectionTimeoutMillisthrough{ ...pool.options, connectionTimeoutMillis }. After a connection drop, replacement clients cannot authenticate. The loop retries without restoring the listener.createInvocationNotificationsin invocation-notifications.ts: the same spread happens on every subscription, including the first. Authenticated configurations usingpasswordcan remain on fallback reads from startup.
Ordinary pooled queries can continue working while notifications fail. Existing live stream readers may stop seeing newly persisted chunks; a fresh read can see the data. This particularly affects token-based database authentication, where the password must remain a callback invoked for each new connection.
Versions checked
- Reproduced against
@workflow/world-postgres5.1.1, Node 24.22.2, a supplied pool frompg8.23.1, and the package's ownpg8.20.0 client. - Real-database validation used PostgreSQL 17.
- Inspected 5.2.0: the stream reconnect path still uses the credential-dropping spread.
- Both paths remain in the source at
b1f92691bcc3bc18b9df094d45379c6464cf4d65.
Minimal reproduction of the credential loss
This requires only pg and does not open a connection:
import assert from 'node:assert/strict';
import { Pool } from 'pg';
const password = async () => 'example-token';
const pool = new Pool({ password, connectionTimeoutMillis: 15000 });
const reconnectConfig = {
...pool.options,
connectionTimeoutMillis: 15000,
};
assert.equal(pool.options.password, password);
assert.equal(
Object.getOwnPropertyDescriptor(pool.options, 'password').enumerable,
false,
);
assert.equal(reconnectConfig.password, undefined);
console.log({
originalPasswordType: typeof pool.options.password, // 'function'
copiedPasswordType: typeof reconnectConfig.password, // 'undefined'
});
await pool.end();
A real listener reproduction follows the same failure path:
- Use a password-authenticated PostgreSQL instance and pass an injected
Poolwith separate host/user/database fields andpassword: async () => currentPassword. Do not embed the password inconnectionString, or supplyPGPASSWORD, since those can mask the omission. - Open
listenChannel(pool, 'workflow_event_chunk', onPayload, { onReconnect })from the builtstreamer.js; verify an initial notification is delivered. - From a separate administrative connection, terminate only that listener backend using
pg_terminate_backend. - Its reconnect creates a client without the password callback.
onReconnectnever fires and new notifications are not delivered, although pooled reads still work. - For the invocation notifier, subscribing a watch is sufficient to expose the failure: its initial dedicated client is already missing the callback.
Expected behavior and proposed correction
Every dedicated client should preserve the pool's authentication configuration, including the live password callback, while applying its existing timeout and application-name overrides. Do not resolve the callback once and cache the resulting token.
A local correction that preserves non-enumerability is:
const config = Object.defineProperties(
{},
Object.getOwnPropertyDescriptors(pool.options),
);
if (connectionTimeoutMillis) {
config.connectionTimeoutMillis = connectionTimeoutMillis;
}
const client = new Client(config);
Apply equivalent copying before the invocation notifier's overrides.
Validation
Regression tests against the installed listener implementations and real pg.Pool/pg.Client constructors fail before correction and pass after it:
- Stream reconnect retains a static password.
- Stream reconnect invokes the dynamic password callback again after token rotation and resumes notifications.
- Invocation notification subscription authenticates initially and after a disconnect.
The network methods are stubbed in those unit regressions. A separate real PostgreSQL probe also terminated the listener backends, changed the database user's password, verified that replacement clients obtained the new password through the callback, and confirmed notification delivery resumed for both paths after the correction.
Related: #4630 concerns surviving a listener disconnect and re-subscribing. This report concerns credentials being lost by the options copy in those subscription paths.
- Ngôn ngữ chính
- TypeScript
- Star
- 2.5k
- Fork
- 401
- Merge trung bình
- 2 ngày 36 phút
- Pull request đã merge (30 ngày)
- 150
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Không có hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của vercel/workflow
-
node-module-esbuild-plugin: getImportedIdentifier only checks the first named import when locating a violationCó thể đã có người làm @div-cowboy đã nhận 2 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
vercel/workflow#5057 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
Maintainer thường phản hồi trong vòng 1 ngày
-
code-transform.mdx §ID Generation omits the package-shipped `name@version` ID formCó thể đã có người làm @Mohith26 đã nhận 109 ngày trước. Đang mở
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 90/100
Maintainer thường phản hồi trong vòng 1 ngày
-
[world-local] SQLite never uses events_by_correlation, so hook_created writes scan the whole runĐang mở
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 55/100
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của vercel/workflow
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
AykutSarac/jsoncrack.com#616 ·
-
nip27: long hashtag swallows the rest of the content, lone # gives an empty hashtagCó thể đã có người làm @bardrop đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
nbd-wtf/nostr-tools#578 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
backnotprop/plannotator#3427 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
microsoft/Power-CAT-Copilot-Studio-Kit#942 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[BUG]: Duplicate preposition in heading and unformatted feedback text on Strengthen Argument pageĐang mởbug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
AOSSIE-Org/DebateAI#801 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày