libpq: decouple the .pgpass lookup port from the connection port

Started by Diegoabout 1 month ago5 messageshackers
Jump to latest
#1Diego
mrstephenamell@gmail.com

Hello hackers,

I would like to float an idea before writing a patch, to find out whether
it is wanted and to get the design right.

Problem
-------

libpq looks up a password in .pgpass using the connection's host and port
as part of the key (host:port:database:user:password). When a client
connects through an SSH tunnel, or through a connection pooler that
listens on a different local port, the port that libpq actually connects
to is not the port of the real server. As a result, the .pgpass lookup is
done against the local/tunnel port and fails to match entries written for
the real server port.

Concretely, suppose the real server is db.example.com:5432 and a user
opens an SSH tunnel so that 127.0.0.1:54321 forwards to it. The natural
.pgpass entry is:

    db.example.com:5432:appdb:alice:secret

The client then connects with host=db.example.com (kept for .pgpass and
TLS), hostaddr=127.0.0.1 and port=54321 (the tunnel). libpq looks up

    db.example.com:54321:appdb:alice

which does not match the 5432 entry, so no password is found and the user
is prompted (or the connection fails under -w).

The host side of this exact problem was already solved
-------------------------------------------------------

libpq already decouples the *host* used for the .pgpass lookup from the
real network endpoint: hostaddr gives the address actually connected to,
while host remains the logical name used for the .pgpass lookup and for
TLS verification. This is the pwhost logic in fe-connect.c, which goes
back to the 2018 thread "Bizarre behavior in libpq's searching of
~/.pgpass":

/messages/by-id/30805.1532749137@sss.pgh.pa.us

The port has no equivalent. passwordFromFile() is called with
conn->connhost[i].port, i.e. the real connection port, with no way to say
"connect to this port, but look up .pgpass under that port". The host has
host/hostaddr; the port only has port. This proposal is to close that
asymmetry.

Why the port wildcard is not enough
-----------------------------------

One can write the entry with a wildcard port:

    db.example.com:*:appdb:alice:secret

and it does match the tunnel. But the wildcard over-matches: a single
local forwarding port (say 54321, or even a fixed local port reused for
several tunnels at different times) ends up matching every server reached
through that port, so the same password line can be applied to different
servers. That is precisely the kind of "password sent to the wrong
server" situation the 2018 host fix was trying to avoid. The wildcard
trades safety for convenience; it is not a substitute for matching the
real server port.

Proposal
--------

Add a libpq connection parameter that specifies the port to be used for
the .pgpass lookup, independently of the port libpq connects to. The
connection still uses port (and hostaddr); only the password-file lookup
key uses the new value. When the new parameter is not set, behavior is
unchanged: the lookup uses port exactly as today.

I do not have a strong opinion on the name and would rather not bikeshed
it before the idea itself is judged. Candidates that came to mind:

    - pgpassport / passfileport (it only affects the password file)
    - portaddr (mirrors hostaddr: "port stays logical, portaddr is the
      real endpoint"), though that would invert today's meaning of port,
      which is probably too invasive

A dedicated parameter that affects only the .pgpass lookup (the first
option) seems the least surprising and the smallest change. It is also
easy to reason about for security: it is an explicit, opt-in assertion by
the user, exactly like hostaddr/host.

This is not hypothetical. I ran into it myself while adding SSH tunnel
support to pgcli (a widely used Postgres CLI): with the tunnel active, an
explicit-port .pgpass entry never matches, because the lookup happens
against the random local forwarding port. The user is prompted for a
password even though the matching entry is right there, and only a
wildcard port papers over it. Other tools hit the same wall:

    - pgcli: SSH tunnel rewrites the port before the .pgpass lookup
https://github.com/dbcli/pgcli/pull/1546
    - DBeaver: .pgpass looked up by 127.0.0.1 through an SSH tunnel
https://github.com/dbeaver/dbeaver/issues/16499
    - pgAdmin 4: control the SSH tunnel local port for .pgpass matching
https://github.com/pgadmin-org/pgadmin4/issues/6903

Questions for the list
----------------------

  1. Is decoupling the .pgpass-lookup port from the connection port
     something libpq wants, given that host/hostaddr already does the
     equivalent for the host?
  2. Is a dedicated lookup-only parameter the right shape, or would you
     prefer a different model?
  3. Naming preferences?

If there is interest, I am happy to write the patch (code, docs and
tests).

Thanks for reading,
Diego

#2Diego
mrstephenamell@gmail.com
In reply to: Diego (#1)
[PATCH] - Re: libpq: decouple the .pgpass lookup port from the connection port

Hi,

Following up on my proposal from last week. Since the idea didn't draw
objections, I went ahead and wrote the patch (code, docs and a TAP test)
so the discussion can be more concrete. v1 attached.

It compiles cleanly, the new TAP test 008_passfileport passes (10
subtests), and the full authentication suite stays green. I also
verified it end-to-end against a real server reached through an SSH
tunnel: the .pgpass entry matches with passfileport set and fails
without it.

I'd really value your read on whether the
host/hostaddr-style decoupling is the right shape here, and on the
parameter name (still tentative).

I'm not a professional C developer and I need help.
Ofc, I used Cursor and Claude to review the code before this mail. ;P

Thanks,
Diego

Show quoted text

On 2026-06-23 12:57, Diego wrote:

Hello hackers,

I would like to float an idea before writing a patch, to find out whether
it is wanted and to get the design right.

Problem
-------

libpq looks up a password in .pgpass using the connection's host and port
as part of the key (host:port:database:user:password). When a client
connects through an SSH tunnel, or through a connection pooler that
listens on a different local port, the port that libpq actually connects
to is not the port of the real server. As a result, the .pgpass lookup is
done against the local/tunnel port and fails to match entries written for
the real server port.

Concretely, suppose the real server is db.example.com:5432 and a user
opens an SSH tunnel so that 127.0.0.1:54321 forwards to it. The natural
.pgpass entry is:

    db.example.com:5432:appdb:alice:secret

The client then connects with host=db.example.com (kept for .pgpass and
TLS), hostaddr=127.0.0.1 and port=54321 (the tunnel). libpq looks up

    db.example.com:54321:appdb:alice

which does not match the 5432 entry, so no password is found and the user
is prompted (or the connection fails under -w).

The host side of this exact problem was already solved
-------------------------------------------------------

libpq already decouples the *host* used for the .pgpass lookup from the
real network endpoint: hostaddr gives the address actually connected to,
while host remains the logical name used for the .pgpass lookup and for
TLS verification. This is the pwhost logic in fe-connect.c, which goes
back to the 2018 thread "Bizarre behavior in libpq's searching of
~/.pgpass":

/messages/by-id/30805.1532749137@sss.pgh.pa.us

The port has no equivalent. passwordFromFile() is called with
conn->connhost[i].port, i.e. the real connection port, with no way to say
"connect to this port, but look up .pgpass under that port". The host has
host/hostaddr; the port only has port. This proposal is to close that
asymmetry.

Why the port wildcard is not enough
-----------------------------------

One can write the entry with a wildcard port:

    db.example.com:*:appdb:alice:secret

and it does match the tunnel. But the wildcard over-matches: a single
local forwarding port (say 54321, or even a fixed local port reused for
several tunnels at different times) ends up matching every server reached
through that port, so the same password line can be applied to different
servers. That is precisely the kind of "password sent to the wrong
server" situation the 2018 host fix was trying to avoid. The wildcard
trades safety for convenience; it is not a substitute for matching the
real server port.

Proposal
--------

Add a libpq connection parameter that specifies the port to be used for
the .pgpass lookup, independently of the port libpq connects to. The
connection still uses port (and hostaddr); only the password-file lookup
key uses the new value. When the new parameter is not set, behavior is
unchanged: the lookup uses port exactly as today.

I do not have a strong opinion on the name and would rather not bikeshed
it before the idea itself is judged. Candidates that came to mind:

    - pgpassport / passfileport (it only affects the password file)
    - portaddr (mirrors hostaddr: "port stays logical, portaddr is the
      real endpoint"), though that would invert today's meaning of port,
      which is probably too invasive

A dedicated parameter that affects only the .pgpass lookup (the first
option) seems the least surprising and the smallest change. It is also
easy to reason about for security: it is an explicit, opt-in assertion by
the user, exactly like hostaddr/host.

This is not hypothetical. I ran into it myself while adding SSH tunnel
support to pgcli (a widely used Postgres CLI): with the tunnel active, an
explicit-port .pgpass entry never matches, because the lookup happens
against the random local forwarding port. The user is prompted for a
password even though the matching entry is right there, and only a
wildcard port papers over it. Other tools hit the same wall:

    - pgcli: SSH tunnel rewrites the port before the .pgpass lookup
https://github.com/dbcli/pgcli/pull/1546
    - DBeaver: .pgpass looked up by 127.0.0.1 through an SSH tunnel
https://github.com/dbeaver/dbeaver/issues/16499
    - pgAdmin 4: control the SSH tunnel local port for .pgpass matching
https://github.com/pgadmin-org/pgadmin4/issues/6903

Questions for the list
----------------------

  1. Is decoupling the .pgpass-lookup port from the connection port
     something libpq wants, given that host/hostaddr already does the
     equivalent for the host?
  2. Is a dedicated lookup-only parameter the right shape, or would you
     prefer a different model?
  3. Naming preferences?

If there is interest, I am happy to write the patch (code, docs and
tests).

Thanks for reading,
Diego

Attachments:

v1-0001-libpq-add-passfileport-to-decouple-the-.pgpass-lo.patchtext/x-patch; charset=UTF-8; name=v1-0001-libpq-add-passfileport-to-decouple-the-.pgpass-lo.patchDownload+200-3
#3Diego
mrstephenamell@gmail.com
In reply to: Diego (#2)
Re: [PATCH] - Re: libpq: decouple the .pgpass lookup port from the connection port

Hello,

It has been a couple of weeks with no comments, so let me add a small,
self-contained reproducer that makes both the problem and the fix easy
to see. I am also cc'ing a few people who have
worked on the relevant code, in case it is of interest: Tom (the
host/hostaddr split for the .pgpass lookup that this mirrors, and the
2018 .pgpass fix I referenced), Michael (the recent passwordFromFile()
work), and Heikki (the multi-host port parsing that the patch reuses).
Apologies for the noise if this is not the right moment.

The reproducer below uses a real SSH tunnel to itself, so the port that
libpq connects to genuinely differs from the real server port. Only the
patch changes the outcome; everything else (server, database, .pgpass)
is identical. It needs a libpq built with the v1 patch posted upstream
in this thread.

daf@t:postgres$ DEMO=/home/daf/scripts/postgres/passfileport-demo
daf@t:postgres$
daf@t:postgres$ BIN_CON="$DEMO/con-el-patch/usr/local/bin"
daf@t:postgres$
daf@t:postgres$ LIB_CON="$DEMO/con-el-patch/usr/local/lib/x86_64-linux-gnu"
daf@t:postgres$
daf@t:postgres$ PSQL_SIN="$DEMO/sin-patch/usr/local/bin/psql"
daf@t:postgres$
daf@t:postgres$ LIB_SIN="$DEMO/sin-patch/usr/local/lib/x86_64-linux-gnu"
daf@t:postgres$
daf@t:postgres$ PSQL_CON="$DEMO/con-el-patch/usr/local/bin/psql"
daf@t:postgres$
daf@t:postgres$ unset PGPASSWORD PGPASSFILEPORT
daf@t:postgres$
daf@t:postgres$ export LD_LIBRARY_PATH="$LIB_CON"
daf@t:postgres$
daf@t:postgres$ export PGDATA=/tmp/pgd-demo
daf@t:postgres$
daf@t:postgres$ export PGPASSFILE=/tmp/pgpass-demo
daf@t:postgres$
daf@t:postgres$ rm -rf "$PGDATA"
daf@t:postgres$
daf@t:postgres$ "$BIN_CON/initdb" -D "$PGDATA" -U postgres -A trust
--no-sync
The files belonging to this database system will be owned by user "daf".
This user must also own the server process.

The database cluster will be initialized with locale "C.UTF-8".
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".

Data page checksums are enabled.

creating directory /tmp/pgd-demo ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default "max_connections" ... 100
selecting default "shared_buffers" ... 128MB
selecting default time zone ... America/Argentina/Buenos_Aires
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok

Sync to disk skipped.
The data directory might become corrupt if the operating system crashes.

Success. You can now start the database server using:

/home/daf/scripts/postgres/passfileport-demo/con-el-patch/usr/local/bin/pg_ctl
-D /tmp/pgd-demo -l logfile start

daf@t:postgres$ printf 'local all all trust\nhost all all 127.0.0.1/32
scram-sha-256\n' > "$PGDATA/pg_hba.conf"
daf@t:postgres$
daf@t:postgres$ "$BIN_CON/pg_ctl" -D "$PGDATA" -o "-p 5440 -c
listen_addresses=127.0.0.1" -w start
waiting for server to start....2026-07-08 13:39:19.186 -03 [666970]
LOG:  starting PostgreSQL 19beta1 on x86_64-linux, compiled by
gcc-12.2.0, 64-bit
2026-07-08 13:39:19.186 -03 [666970] LOG:  listening on IPv4 address
"127.0.0.1", port 5440
2026-07-08 13:39:19.189 -03 [666970] LOG:  listening on Unix socket
"/tmp/.s.PGSQL.5440"
2026-07-08 13:39:19.196 -03 [666975] LOG:  database system was shut down
at 2026-07-08 13:38:56 -03
2026-07-08 13:39:19.200 -03 [666970] LOG:  database system is ready to
accept connections
 done
server started
daf@t:postgres$
daf@t:postgres$ "$BIN_CON/psql" -X -p 5440 -U postgres -h /tmp -d
postgres -c "create role tuser login password 'sekret';"
CREATE ROLE
daf@t:postgres$
daf@t:postgres$ echo "127.0.0.1:5441:postgres:tuser:sekret" > "$PGPASSFILE"
daf@t:postgres$
daf@t:postgres$ chmod 600 "$PGPASSFILE"
daf@t:postgres$
daf@t:postgres$ LD_LIBRARY_PATH="$LIB_SIN" "$PSQL_SIN" -X -w
"host=127.0.0.1 port=5440 user=tuser dbname=postgres" -tAc "select
'AUTENTICO-OK'"
psql: error: connection to server at "127.0.0.1", port 5440 failed:
fe_sendauth: no password supplied
daf@t:postgres$
daf@t:postgres$ LD_LIBRARY_PATH="$LIB_SIN" "$PSQL_SIN" -X -w
"host=127.0.0.1 port=5440 user=tuser dbname=postgres passfileport=5441"
-tAc "select 'AUTENTICO-OK'"
psql: error: invalid connection option "passfileport"
daf@t:postgres$
daf@t:postgres$ LD_LIBRARY_PATH="$LIB_CON" "$PSQL_CON" -X -w
"host=127.0.0.1 port=5440 user=tuser dbname=postgres passfileport=5441"
-tAc "select 'AUTENTICO-OK'"
AUTENTICO-OK
daf@t:postgres$
daf@t:postgres$ cat $PGPASSFILE
127.0.0.1:5441:postgres:tuser:sekret
daf@t:postgres$
daf@t:postgres$ "$BIN_CON/pg_ctl" -D "$PGDATA" -m immediate stop
waiting for server to shut down....2026-07-08 13:44:30.677 -03 [666970]
LOG:  received immediate shutdown request
2026-07-08 13:44:30.688 -03 [666970] LOG:  database system is shut down
 done
server stopped
daf@t:postgres$

The connection port stays 5441; only the password-file lookup key uses
5440. Without passfileport the default is unchanged, so existing setups
behave exactly as before.

As a sanity check that the difference is solely this patch, I built two
libpq trees from the same commit, identical except for the change, and
ran the same command line against both through the same tunnel: with the
unpatched libpq passfileport is simply "invalid connection option", and
with the patched one the connection succeeds. Same server, same .pgpass,
same command; the patch is the only variable.

The design is unchanged from the original proposal, and the v1 patch is
the one already posted in this thread (adds the passfileport conninfo
option and PGPASSFILEPORT env var, splits per host like port, uses it as
the .pgpass lookup key, with docs and a TAP test). I would still welcome
opinions on two points in particular:

- the parameter name (passfileport is only tentative), and
- the security angle: this matches the real server port explicitly,
which is stricter than the port wildcard people use today, so I
believe it improves on the "password sent to the wrong server" risk
rather than adding to it.

Thanks for taking a look.

Regards,
Diego

Show quoted text

On 2026-06-29 14:14, Diego wrote:

Hi,

Following up on my proposal from last week. Since the idea didn't draw
objections, I went ahead and wrote the patch (code, docs and a TAP test)
so the discussion can be more concrete. v1 attached.

It compiles cleanly, the new TAP test 008_passfileport passes (10
subtests), and the full authentication suite stays green. I also
verified it end-to-end against a real server reached through an SSH
tunnel: the .pgpass entry matches with passfileport set and fails
without it.

I'd really value your read on whether the
host/hostaddr-style decoupling is the right shape here, and on the
parameter name (still tentative).

I'm not a professional C developer and I need help.
Ofc, I used Cursor and Claude to review the code before this mail. ;P

Thanks,
Diego

On 2026-06-23 12:57, Diego wrote:

Hello hackers,

I would like to float an idea before writing a patch, to find out whether
it is wanted and to get the design right.

Problem
-------

libpq looks up a password in .pgpass using the connection's host and port
as part of the key (host:port:database:user:password). When a client
connects through an SSH tunnel, or through a connection pooler that
listens on a different local port, the port that libpq actually connects
to is not the port of the real server. As a result, the .pgpass lookup is
done against the local/tunnel port and fails to match entries written for
the real server port.

Concretely, suppose the real server is db.example.com:5432 and a user
opens an SSH tunnel so that 127.0.0.1:54321 forwards to it. The natural
.pgpass entry is:

    db.example.com:5432:appdb:alice:secret

The client then connects with host=db.example.com (kept for .pgpass and
TLS), hostaddr=127.0.0.1 and port=54321 (the tunnel). libpq looks up

    db.example.com:54321:appdb:alice

which does not match the 5432 entry, so no password is found and the user
is prompted (or the connection fails under -w).

The host side of this exact problem was already solved
-------------------------------------------------------

libpq already decouples the *host* used for the .pgpass lookup from the
real network endpoint: hostaddr gives the address actually connected to,
while host remains the logical name used for the .pgpass lookup and for
TLS verification. This is the pwhost logic in fe-connect.c, which goes
back to the 2018 thread "Bizarre behavior in libpq's searching of
~/.pgpass":

/messages/by-id/30805.1532749137@sss.pgh.pa.us

The port has no equivalent. passwordFromFile() is called with
conn->connhost[i].port, i.e. the real connection port, with no way to say
"connect to this port, but look up .pgpass under that port". The host has
host/hostaddr; the port only has port. This proposal is to close that
asymmetry.

Why the port wildcard is not enough
-----------------------------------

One can write the entry with a wildcard port:

    db.example.com:*:appdb:alice:secret

and it does match the tunnel. But the wildcard over-matches: a single
local forwarding port (say 54321, or even a fixed local port reused for
several tunnels at different times) ends up matching every server reached
through that port, so the same password line can be applied to different
servers. That is precisely the kind of "password sent to the wrong
server" situation the 2018 host fix was trying to avoid. The wildcard
trades safety for convenience; it is not a substitute for matching the
real server port.

Proposal
--------

Add a libpq connection parameter that specifies the port to be used for
the .pgpass lookup, independently of the port libpq connects to. The
connection still uses port (and hostaddr); only the password-file lookup
key uses the new value. When the new parameter is not set, behavior is
unchanged: the lookup uses port exactly as today.

I do not have a strong opinion on the name and would rather not bikeshed
it before the idea itself is judged. Candidates that came to mind:

    - pgpassport / passfileport (it only affects the password file)
    - portaddr (mirrors hostaddr: "port stays logical, portaddr is the
      real endpoint"), though that would invert today's meaning of port,
      which is probably too invasive

A dedicated parameter that affects only the .pgpass lookup (the first
option) seems the least surprising and the smallest change. It is also
easy to reason about for security: it is an explicit, opt-in assertion by
the user, exactly like hostaddr/host.

This is not hypothetical. I ran into it myself while adding SSH tunnel
support to pgcli (a widely used Postgres CLI): with the tunnel active, an
explicit-port .pgpass entry never matches, because the lookup happens
against the random local forwarding port. The user is prompted for a
password even though the matching entry is right there, and only a
wildcard port papers over it. Other tools hit the same wall:

    - pgcli: SSH tunnel rewrites the port before the .pgpass lookup
https://github.com/dbcli/pgcli/pull/1546
    - DBeaver: .pgpass looked up by 127.0.0.1 through an SSH tunnel
https://github.com/dbeaver/dbeaver/issues/16499
    - pgAdmin 4: control the SSH tunnel local port for .pgpass matching
https://github.com/pgadmin-org/pgadmin4/issues/6903

Questions for the list
----------------------

  1. Is decoupling the .pgpass-lookup port from the connection port
     something libpq wants, given that host/hostaddr already does the
     equivalent for the host?
  2. Is a dedicated lookup-only parameter the right shape, or would you
     prefer a different model?
  3. Naming preferences?

If there is interest, I am happy to write the patch (code, docs and
tests).

Thanks for reading,
Diego

Attachments:

v1-0001-libpq-add-passfileport-to-decouple-the-.pgpass-lo.patchtext/x-patch; charset=UTF-8; name=v1-0001-libpq-add-passfileport-to-decouple-the-.pgpass-lo.patchDownload+200-3
#4Christoph Berg
myon@debian.org
In reply to: Diego (#1)
Re: libpq: decouple the .pgpass lookup port from the connection port

Re: Diego

Why the port wildcard is not enough
-----------------------------------

One can write the entry with a wildcard port:

    db.example.com:*:appdb:alice:secret
and it does match the tunnel. But the wildcard over-matches: a single
local forwarding port (say 54321, or even a fixed local port reused for
several tunnels at different times) ends up matching every server reached
through that port,

If you are giving the correct remote name to libpq, the rule won't
match several different hosts. If you don't care about that part, you
don't need anything extra and could just match on localhost:54321 (or
localhost:*).

so the same password line can be applied to different
servers. That is precisely the kind of "password sent to the wrong
server" situation the 2018 host fix was trying to avoid. The wildcard
trades safety for convenience; it is not a substitute for matching the
real server port.

Don't use a wildcard, put :54321 in.

TBH I don't see why yet more connection parameters are required when
the problem is already solved by setting the hostname/hostaddr
correctly. (The part that remains when doing that can be avoided by
not reusing ports I think. You should do that anyway for general
sanity.)

    - pgpassport / passfileport (it only affects the password file)

Is this proposal just a giant buildup for a "PG passport" joke? :)

Christoph

#5Diego
mrstephenamell@gmail.com
In reply to: Christoph Berg (#4)
Re: libpq: decouple the .pgpass lookup port from the connection port

Hello Christoph, and thanks for your time and the pushback.

If you are giving the correct remote name to libpq, the rule won't
match several different hosts.

[...]

Don't use a wildcard, put :54321 in.

You are right, and I should concede this clearly: with host set to the
real server name (for the .pgpass key and TLS) and hostaddr pointing at
the tunnel, the lookup key becomes realname:54321, which is specific and
safe, no cross-host over-match. So my "the wildcard is not enough"
argument was aiming at the wrong target: the answer is not a wildcard,
it is host/hostaddr plus the connection port in .pgpass. For a tunnel
you open by hand, with a fixed local port, there is nothing missing and
no new parameter is warranted. I withdraw that part of the motivation.

What that solution still requires is that the .pgpass entry encode the
*local* port. That is fine when the local port is fixed and known in
advance. It breaks when the local port is dynamic:

- ssh -L 127.0.0.1:0:server:5432 (kernel picks the port)
- connection proxies that bind an ephemeral local port per session:
  cloud-sql-proxy, the AlloyDB auth proxy, AWS SSM port forwarding,
  Teleport's "tsh proxy db", and similar
- GUI/CLI clients that manage the tunnel themselves and allocate the
  local port at runtime (this is the case where I first hit it, in a
  libpq-based client, probably most of the pg-ecosystem-apps)

In all of these the local port is not known when .pgpass is written, and
it changes between sessions, so "put :54321 in" is not available. The
real server port, on the other hand, is fixed. So the only thing you
need is to key the .pgpass lookup on the real server port while
connecting to whatever local port the tool handed you. Everything else
(host, database, user, TLS) is already handled by host/hostaddr.

Which brings me to what I think is the more interesting question, and
where your host/hostaddr point actually leads. Rather than "yet another
password-file parameter", the symmetric framing is:

portaddr : port  ::  hostaddr : host

That is: let "port" be the logical port used for the .pgpass lookup (the
real server port), and add "portaddr" as the numeric port actually
connected to, exactly as hostaddr is to host. When portaddr is unset,
libpq connects to port as it does today, so it is fully backward
compatible; a tunnel with a dynamic local port is then just

host=db.example.com hostaddr=127.0.0.1 port=5432 portaddr=54321

with the natural .pgpass entry db.example.com:5432:... -- the same entry
that already works for a direct connection. No password-file-specific
concept at all; it just completes the host/hostaddr model for the port.

The cost is that it re-purposes the primary meaning of "port" (from "the
port connected to" to "the logical port"), the way hostaddr once did to
host. My current patch instead keeps "port" as the connection port and
adds a lookup-only override (spelled passfileport in v1). Both are
backward compatible; the difference is naming and which parameter holds
the real vs. local value.

does completing the model as port/portaddr seem preferable to a lookup-only
passfileport, or is re-purposing "port" a non-starter and the asymmetry
with host/hostaddr intentional? I would rather settle the shape before
rewriting the patch either way.

Is this proposal just a giant buildup for a "PG passport" joke? :)

jajjajja, nono, was to test if someone reads me,but I will happily drop
the passport spelling; portaddr keeps things boring on purpose. :)

Thanks again,
Diego

Show quoted text

On 2026-07-08 14:15, Christoph Berg wrote:

Re: Diego

Why the port wildcard is not enough
-----------------------------------

One can write the entry with a wildcard port:

    db.example.com:*:appdb:alice:secret
and it does match the tunnel. But the wildcard over-matches: a single
local forwarding port (say 54321, or even a fixed local port reused for
several tunnels at different times) ends up matching every server reached
through that port,

If you are giving the correct remote name to libpq, the rule won't
match several different hosts. If you don't care about that part, you
don't need anything extra and could just match on localhost:54321 (or
localhost:*).

so the same password line can be applied to different
servers. That is precisely the kind of "password sent to the wrong
server" situation the 2018 host fix was trying to avoid. The wildcard
trades safety for convenience; it is not a substitute for matching the
real server port.

Don't use a wildcard, put :54321 in.

TBH I don't see why yet more connection parameters are required when
the problem is already solved by setting the hostname/hostaddr
correctly. (The part that remains when doing that can be avoided by
not reusing ports I think. You should do that anyway for general
sanity.)

    - pgpassport / passfileport (it only affects the password file)

Is this proposal just a giant buildup for a "PG passport" joke? :)

Christoph