temporalio / temporalio/temporal
PostgreSQL buildDSN does not URL-encode cfg.User — usernames containing ":" (e.g. Vault dynamic credentials) break authentication
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 23.2k
- Forks
- 1.9k
- Avg merge
- 2d 8h
- Merged PRs (30d)
- 228
Description
Expected Behavior
The Temporal server should connect to PostgreSQL regardless of URI-reserved characters in the
username. In particular, a username containing : (colon) — the shape produced by HashiCorp
Vault's database secrets engine for dynamic/per-lease credentials, e.g. entity:serv-xxxx —
must authenticate correctly.
Actual Behavior
Authentication fails when the username contains :. The colon is interpreted as the
user:password separator inside the connection URL userinfo component, so the username is
truncated at the colon and PostgreSQL rejects the login (password authentication failed /
no such user).
The credentials themselves are valid — they authenticate correctly when the username does not
contain URI-reserved characters, or when the username is percent-encoded before building the DSN.
Root cause (in the source)
common/persistence/sql/sqlplugin/postgresql/session/session.go, function buildDSN:
const dsnFmt = "postgres://%v:%v@%v/%v?%v"
// ...
return fmt.Sprintf(
dsnFmt,
cfg.User, // <-- username interpolated RAW (not encoded)
url.QueryEscape(password), // <-- password IS escaped
resolvedAddr,
cfg.DatabaseName,
tlsAttrs.Encode(),
), nil
cfg.User is interpolated raw into the postgres://<user>:<password>@... URL, while the
password is passed through url.QueryEscape. So any URI-reserved character in the username
(:, /, @, ?, #) corrupts the DSN. For entity:serv-xxxx the effective parsed username
becomes entity and serv-xxxx is misread as the start of the password.
Two observations:
- The username is not encoded at all — this is the primary bug.
url.QueryEscapeis also not strictly the correct encoder for the userinfo component
(it is query-string encoding, e.g. it turns a space into+, and does not match RFC 3986
userinfo rules). Building the userinfo vianet/url(e.g.url.User(cfg.User)/
url.UserPassword(cfg.User, password)and lettingurl.URL.String()render it) would encode
both fields correctly. This is likely the same class of issue behind the password-side report
#5729.
Suggested fix
Compose the DSN with net/url so both username and password are correctly percent-encoded for
the userinfo component, instead of interpolating raw values into dsnFmt. For example, build a
url.URL{ Scheme: "postgres", User: url.UserPassword(cfg.User, password), Host: resolvedAddr, Path: cfg.DatabaseName, RawQuery: tlsAttrs.Encode() } and use its String(). At minimum,
percent-encode cfg.User the same way the password is handled.
Steps to Reproduce the Problem
- Create a PostgreSQL login role whose name contains a colon, e.g.
entity:serv-abcd, with a
known password and access to the Temporal database. - Configure the
sqldatastore withuser: "entity:serv-abcd", the matchingpassword, and
thepostgresplugin (pgx). - Start the server →
password authentication failed for user "entity"(username truncated at
the colon). - Rename the role to remove the colon → identical setup works. This isolates the failure to DSN
composition, not the credentials.
This is the exact scenario when delivering DB credentials via Vault dynamic secrets (per-lease
usernames of the form entity:serv-...), synced into the pod by the Vault Secrets Operator.
Specifications
- Version: v1.31.2 (also present on
main— thebuildDSNcode above is current). - Plugin/driver:
postgres(pgx). - Platform: Kubernetes; credentials delivered as a native Secret (username + password), rotated
per Vault lease.
Related
- #5729 — same DSN-encoding class of problem, but reported for the password. The current code
appliesurl.QueryEscapeto the password only; the username (cfg.User) is still
interpolated raw, which is what this issue is about.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in common/persistence/sql/sqlplugin/postgresql/session/session.go at buildDSN, and inspect how the DSN userinfo is assembled. Use net/url to encode both username and password as userinfo rather than interpolating cfg.User raw. Done means a username such as entity:serv-abcd produces a valid PostgreSQL DSN and authenticates without truncation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, postgres
- Domain
- database
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 86/100