Skip to content

PostgreSQL buildDSN does not URL-encode cfg.User — usernames containing ":" (e.g. Vault dynamic credentials) break authentication #12192

Description

@Roxyrob

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:

  1. The username is not encoded at all — this is the primary bug.
  2. url.QueryEscape is 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 via net/url (e.g. url.User(cfg.User) /
    url.UserPassword(cfg.User, password) and letting url.URL.String() render it) would encode
    both fields correctly. This is likely the same class of issue behind the password-side report
    Temporal fails to connect to Google Cloud SQL Postgres when password contains certain characters #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

  1. 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.
  2. Configure the sql datastore with user: "entity:serv-abcd", the matching password, and
    the postgres plugin (pgx).
  3. Start the server → password authentication failed for user "entity" (username truncated at
    the colon).
  4. 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 — the buildDSN code above is current).
  • Plugin/driver: postgres (pgx).
  • Platform: Kubernetes; credentials delivered as a native Secret (username + password), rotated
    per Vault lease.

Related

Activity

  1. ayushsarode commented on Sep 23, 2026

    @ayushsarode
    Contributor

    Reproduced on current main (server 1.32.0, postgres12 plugin).

    Setup: Postgres role "entity:serv-abcd" with password s3cret, both postgres-default and postgres-visibility pointed at that user. Starting the server fails during the schema version check:

    pq: password authentication failed for user "entity" (28P01) sql schema version compatibility check failed: unable to read DB schema version keyspace/database: temporal error: no usable database connection found

    The same password works when the role name has no colon. A percent-encoded username (entity%3Aserv-abcd) also authenticates as entity:serv-abcd, so the credentials are valid and the failure is in buildDSN.

    I'd like to work on this. Plan is to build the DSN with url.UserPassword so the username and password are both encoded for the userinfo component, and add a unit test for a colon in the username.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions