Skip to content

fix(pg-connection-string): keep %XX escapes with hex letters when re-encoding - #3798

Open
breken-ai wants to merge 1 commit into
brianc:masterfrom
breken-ai:fix/connection-string-hex-escapes
Open

breken-ai wants to merge 1 commit into
brianc:masterfrom
breken-ai:fix/connection-string-hex-escapes

Conversation

@breken-ai

@breken-ai breken-ai commented Sep 30, 2026 •

Copy link
Copy Markdown

If a connection string has a space or a stray % anywhere in it, parse() runs it through encodeURI() and then tries to undo the double encoding of escapes that were already there. The undo regex is /%25(\d\d)/g, so it only handles escapes made of two decimal digits. %40 comes through fine, but %2F, %3A, %2B, %3f and any other escape with a hex letter stay double encoded and come back literally.

This hits people who follow the usual advice to percent-encode special characters in the password and also put a readable options value in the URL:

parse('postgres://app:s3cr%2Ft@db/prod?options=-c search_path=app').password
// before: 's3cr%2Ft'
// after:  's3cr/t'

Take away the space (options=-c%20search_path%3Dapp) and the same password parses correctly, so the result depends on some other part of the URL. Against a local Postgres 14 with a role whose password is s3cr/t:, new Client({ connectionString: 'postgres://app:s3cr%2Ft%3A@127.0.0.1:55437/postgres?options=-c search_path=app' }) fails on master with password authentication failed for user "app" and connects with this change (show search_path returns app). Generated cloud passwords often contain /, + and =, which are the escapes that break.

The fix matches any two hex digits, case-insensitively: /%25([0-9a-f]{2})/gi. An escape that was already valid is still restored exactly once, and an invalid % is still encoded as %25.

Tests: added two cases to packages/pg-connection-string/test/parse.ts, one with a space plus uppercase escapes and one with a stray % plus a lowercase escape. Both fail on master (expected 's3cr%2Ft%3A' to equal 's3cr/t:', expected 's3cr%2bt' to equal 's3cr+t') and pass with the fix. The full package suite passes (82) and the 100% coverage check passes. eslint and prettier are clean on the changed files.

This PR was prepared with an AI coding assistant, and the failing tests, fix and local Postgres check above were all run before opening it.

…encoding

When a connection string contains a space or a stray `%`, parse() runs it
through encodeURI() and then tries to undo the double encoding of escapes
that were already there. The undo regex only matched two decimal digits, so
escapes such as %2F, %3A, %2B or %3f stayed double encoded and came back
literally: a password `s3cr%2Ft` became "s3cr%2Ft" instead of "s3cr/t" as
soon as the URL also had, for example, `options=-c search_path=app`.

Match any two hex digits, case-insensitively.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@breken-ai
breken-ai requested a review from hjr3 as a code owner September 30, 2026 07:23
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