Skip to content

fix: split DSN credentials on the last @ of the authority - #394

Merged
tianzhou merged 1 commit into
bytebase:mainfrom
DANze11:fix/dsn-password-at
Aug 6, 2026
Merged

fix: split DSN credentials on the last @ of the authority#394
tianzhou merged 1 commit into
bytebase:mainfrom
DANze11:fix/dsn-password-at

Conversation

@DANze11

@DANze11 DANze11 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

SafeURL takes the first @ as the userinfo separator. @ is a legal password character, so a password carrying one is cut short there and its tail is folded into the hostname:

postgres://user:pa@ss@dbhost:5432/mydb
→ username "user", password "pa", hostname "ss@dbhost"

Scope

Not connector-specific. SafeURL parses the DSN for all five connectors, so any of them rejects a password containing @:

connector hostname password
postgres ss@dbhost pa
mysql ss@dbhost pa
mariadb ss@dbhost pa
sqlserver ss@dbhost pa

The symptom is a DNS failure on the mangled host (getaddrinfo EAI_FAIL), or — when the truncated prefix happens to be a valid password for that account — a plain Login failed. Neither points at the password, which makes this slow to diagnose from the outside.

It also weakens the DSN masking

obfuscateDSNPassword masks the DSN before it is logged, and relies on the same split. Given the DSN above it produced:

postgres://user:**@ss@dbhost:5432/mydb

The part of the password after the @ stays in the string. DBHub prints that line per source at startup, so the tail reaches the server's log — and on the stdio transport, the client's.

I have filed this as a bug rather than through the advisory process, because the exposure is narrow: it is the operator's own password, on a host that already holds the config file containing it in full, and it only arises in a deployment that cannot connect at all — a password with @ means DBHub fails fast and never serves anything. Happy to move it if you would rather handle it the other way.

The fix

Standard URL parsing ends userinfo at the last @ of the authority, so match that.

The search is bounded by the start of the path, because the path is still attached at that point and may legitimately contain @ — a database named a@b. An unbounded lastIndexOf would then pick that one as the separator, trading one bug for another.

Percent-encoding already worked, since the parser decodes username and password. This makes the unencoded form work too, which is what a DSN assembled from an environment variable ends up carrying:

dsn = "postgres://user:${DB_PASSWORD}@dbhost:5432/mydb"

Testing

Six cases added for SafeURL: a password with one @, with several, @ in the path both with and without credentials, the percent-encoded form, and a password with @ alongside an explicit port — which the previous behaviour also mangled.

One case added for obfuscateDSNPassword, which had no coverage for this at all.

Verified to fail against the previous behaviour: reverting the one-line change turns four of the six SafeURL cases red, and the masking case too.

Full unit suite green on top of this branch (952 passing; the one failure is a pre-existing Windows-only SSH-config test that fails identically on an unmodified checkout), and no new TypeScript errors — 137 before and after.

SafeURL took the first '@' as the userinfo separator. '@' is a legal
password character, so a password carrying one was cut short there and
its tail folded into the hostname. For

  postgres://user:pa@ss@dbhost:5432/mydb

the password became "pa" and the host "ss@dbhost".

This is not connector-specific: SafeURL parses the DSN for all five
connectors, so any of them rejects a password containing '@'. The
symptom is a DNS failure (getaddrinfo EAI_FAIL on the mangled host) or,
when the truncated prefix happens to be a valid password elsewhere, a
plain login failure — neither points at the password.

It also weakens obfuscateDSNPassword, which masks the DSN before it is
logged and relies on the same split. Given the DSN above it produced

  postgres://user:**@ss@dbhost:5432/mydb

leaving the part of the password after the '@' in the string. DBHub
prints that line per source at startup, so the tail reached the server's
log — and, on the stdio transport, the client's. The exposure is limited
to an operator's own password on a host that already holds the config,
and only arises in a deployment that cannot connect at all, so this is
filed as a bug rather than through the advisory process; say the word if
you would rather handle it the other way.

Standard URL parsing ends userinfo at the last '@' of the authority, so
match that. The search is bounded by the start of the path, because the
path is still attached at this point and may legitimately contain '@'
(a database named "a@b"): an unbounded lastIndexOf would then pick that
one as the separator.

Percent-encoding already worked, since the parser decodes username and
password. This makes the unencoded form work too, which is what a DSN
assembled from an environment variable ends up carrying.

Tests cover the multi-'@' password, '@' in the path (with and without
credentials), the percent-encoded form, the case with a port, and the
masking above, which had no test for this at all.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes DSN parsing in SafeURL so credentials are split on the last @ in the authority (bounded to the start of the path), which correctly supports passwords containing @ and prevents accidental hostname mangling and incomplete DSN masking.

Changes:

  • Update SafeURL authority parsing to use lastIndexOf('@') bounded by the path start.
  • Add unit tests covering @ in passwords (single/multiple), @ in path, percent-encoded @, and port preservation.
  • Add a regression test ensuring obfuscateDSNPassword fully masks passwords containing @.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/utils/safe-url.ts Fixes authority credential splitting to handle @ in passwords safely (bounded by path start).
src/utils/tests/safe-url.test.ts Adds coverage for @ in password/path scenarios and port preservation.
src/utils/tests/dsn-obfuscate.test.ts Adds regression coverage to ensure password masking remains complete when passwords contain @.

@tianzhou tianzhou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM. Thanks for the fix

@tianzhou
tianzhou merged commit 918f8e2 into bytebase:main Aug 6, 2026
2 checks passed
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.

3 participants