POC: Carefully exposing information without authentication

Started by Greg Sabino Mullaneover 1 year ago18 messageshackers
Beta feature

Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.

appliessuccessCI history

You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:

docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t51653
psql -h localhost -U postgres

Built from patchset v14 (message #14), September 19, 2026 at 06:10 PM.

Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:

git clone --branch t51653_14 https://github.com/hackorum-dev/postgres.git

In a checkout you already have, add the fork once:

git remote add hackorum https://github.com/hackorum-dev/postgres.git

then, for this patchset and every later one:

git fetch hackorum t51653_14 && git checkout t51653_14

Patchset v14 (message #14) is on t51653_14

Jump to latest
#1Greg Sabino Mullane
greg@turnstep.com

Proposal: Allow a carefully curated selection of information to be shown
without authentication.

A common task for an HA system or a load balancer is to quickly determine
which of your Postgres clusters is the primary, and which are the replicas.
The canonical way to do this is to log in to each server with a valid
username and password, and then run pg_is_in_recovery().
That's a lot of work to determine if a server is a replica or not, and it
struck me that this true/false information about a running cluster is not
super-sensitive information. In other words, would it really be wrong if
there was a way to advertise that information without having to log in? I
toyed with the idea of Postgres maintaining some sort of signal file, but
then I realized that we already have a process, listening on a known port,
that has that information available to us.

Thus, this POC (proof of concept), which lets the postmaster scan for
incoming requests and quickly handle them *before* doing forking and
authenticating. We scan for a simple trigger string, and immediately return
the information to the client.

It also occured to me that since we are going to need to provide a
non-Postgres-protocol special trigger string, and we might as well do
something like "GET /info" to allow existing programs to treat Postgres as
a mini http server. To that end, we end
up with something like this:

$ psql -p 5432 -tc 'select pg_is_in_recovery()'
t

$ curl http://localhost:5432/foobar
curl: (52) Empty reply from server

$ curl http://localhost:5432/info
RECOVERY: 1

To accomplish this, we have boolean GUC flags (defaulting to false) that
control which information is exposed. For the example above, the
expose_recovery boolean has been set to true. If any of these GUCs are
true, we take a slight detour right after we accept() but before we
actually fork. We use recv with the MSG_PEEK flag to take a quick scan of
the incoming data, and use strncmp to see if it matches. If it does, we
send() some information and move on without forking. If it doesn't, we
simply move on as if we were never there, and proceed to the next step of
forking a new backend to start the authentication process.

There are three pieces of information that can be exposed with this patch.
There may be more in the future, but these are all simple, global, and not
(IMO) security leaks. The GUCs are expose_recovery, expose_sysid, and
expose_version. Each one adds a line to the output in a KEY: VALUE format
for the GET /info endpoint. The raw value is output for the direct
endpoints:

* GET /replica
* GET /sysid
* GET /version

The expose_recovery GUC uses RecoveryInProgress() to return a 1 or a 0.
This is returned by the GET /replica endpoint.

The expose_sysid GUC returns GetSystemIdentifier(). Since this can be
thought of as a fingerprint for the server, it's a nice way for external
programs to determine if the cluster is the same one it saw last time, or
for leader/replica matching.

The purpose of the expose_version GUC is to output PG_VERSION_NUM. This
will allow external tools - particularly security scanners - to know the
exact version of Postgres that is running. While some may consider this
privileged information, tools are already taking advantage of our debug
loophole to make an educated guess about the version. See my old post about
this:
https://www.endpointdev.com/blog/2010/05/finding-postgresql-version-without/
Note that this guess by security scanners is sometimes wrong, or only able
to cover a range of versions. Thus, we should give them the correct answer,
rather than providing a dubious one via some trickery.

Here's some example output with all three enabled:

$ psql -c 'alter system set expose_recovery=on'
$ psql -c 'alter system set expose_version=on'
$ psql -c 'alter system set expose_sysid=on'
$ psql -c 'select pg_reload_conf()'

$ curl http://localhost:5432/version
180000

$ curl http://localhost:5432/info
RECOVERY: 1
SYSID: 7504513530771111839
VERSION: 180000

But wait! We can do more. For the recovery, we don't even need a string
that spells out "RECOVERY:", we only need to know if the server is in
recovery or not. In short, a boolean. Patroni does this in its API with a
call of HEAD /replica. It returns a different HTTP code if the server is a
replica (200) or not a replica (503). We can do the same thing! What's
more, we can do it in a way that will allow existing calls to simply point
to the postgres server instead of a Patroni process, and get the same
result back, but faster. Here's an example of what that looks like:

## Calling Patroni
$ curl -s -w "%{http_code}" -o /dev/null -I http://localhost:8008/replica
200

## Calling Postgres directly
$ curl -s -w "%{http_code}" -o /dev/null -I http://localhost:5432/replica
200

Here's a simple Python program showing how easy it is to grab this
information:

import socket

try:
with socket.create_connection(('localhost', 5432), timeout=1) as s:
s.sendall(b'GET /sysid')
print(s.recv(200).split(b'\r\n\r\n',1)[1].decode())
except Exception as e:
print(f"Error: {e}")

That's the basic idea: proof of concept patch is attached. Additional
things to do:

* handling socket quirks (esp. Win32)
* docs (once details are hashed out)
* moving things around (everything is in one function right now for reading
ease)

Cheers,
Greg

--
Crunchy Data - https://www.crunchydata.com
Enterprise Postgres Software Products & Tech Support

Attachments:

0001-Allow-specific-information-to-be-output-directly-by-Postgres.patchapplication/octet-stream; name=0001-Allow-specific-information-to-be-output-directly-by-Postgres.patchDownload+339-2
#2Antonin Houska
ah@cybertec.at
In reply to: Greg Sabino Mullane (#1)
Re: POC: Carefully exposing information without authentication

Greg Sabino Mullane <htamfids@gmail.com> wrote:

Proposal: Allow a carefully curated selection of information to be shown without authentication.

A common task for an HA system or a load balancer is to quickly determine which of your Postgres clusters is the primary, and which are the
replicas. The canonical way to do this is to log in to each server with a valid username and password, and then run pg_is_in_recovery().
That's a lot of work to determine if a server is a replica or not, and it struck me that this true/false information about a running cluster is not
super-sensitive information. In other words, would it really be wrong if there was a way to advertise that information without having to log in?
I toyed with the idea of Postgres maintaining some sort of signal file, but then I realized that we already have a process, listening on a known
port, that has that information available to us.

Thus, this POC (proof of concept), which lets the postmaster scan for incoming requests and quickly handle them *before* doing forking and
authenticating. We scan for a simple trigger string, and immediately return the information to the client.

Why is it important not to fork? My understanding is that pg_is_ready also
tries to start a regular connection, i.e. forks a new backend. I think this
functionality would fit into libpq. (I've got no strong opinion on the amount
of information to be revealed this way. In any case, a GUC to enable the
feature only if the DBA wants it makes sense.)

--
Antonin Houska
Web: https://www.cybertec-postgresql.com

#3Greg Sabino Mullane
greg@turnstep.com
In reply to: Antonin Houska (#2)
Re: POC: Carefully exposing information without authentication

On Fri, May 30, 2025 at 11:02 AM Antonin Houska <ah@cybertec.at> wrote:

Why is it important not to fork?

Good question. Forking is expensive, and there is also a lot of
housekeeping associated with it that is simply not needed here. We want
this to be lightweight, and simple. No need to fork if we are just going to
do a few strncmp() calls and a send(). However, I'm not highly opposed to
fork-first, as I understand that we want to not slow down postmaster. My
testing showed a barely measurable impact, but I will defer to whatever
decision the elder Postgres gods decide on.

My understanding is that pg_is_ready also tries to start a regular
connection, i.e. forks a new backend.

Yep. I consider pg_isready a spiritual cousin to this feature, but it's not
something that can really do what this does.

Cheers,
Greg

--
Crunchy Data - https://www.crunchydata.com
Enterprise Postgres Software Products & Tech Support

#4Tom Lane
tgl@sss.pgh.pa.us
In reply to: Greg Sabino Mullane (#3)
Re: POC: Carefully exposing information without authentication

Greg Sabino Mullane <htamfids@gmail.com> writes:

Good question. Forking is expensive, and there is also a lot of
housekeeping associated with it that is simply not needed here. We want
this to be lightweight, and simple. No need to fork if we are just going to
do a few strncmp() calls and a send().

send() can block. I think calling it in the postmaster is a
nonstarter. For comparison, we make an effort to not do any
communication with incoming clients until after forking a child
to do the communication. The one exception is if we have to
report fork failure --- but we don't make any strong guarantees
about that report succeeding. (IIRC, we put the port into nonblock
mode and try only once.) That's probably not a behavior you want
to adopt for non-edge-case usages.

Another point is that you'll recall that there's a lot of
interest in switching to a threaded model. The argument that
"fork is too expensive" may not have a long shelf life.

I'm not taking a position on whether $SUBJECT is a good idea
in the first place.

regards, tom lane

#5Greg Sabino Mullane
greg@turnstep.com
In reply to: Tom Lane (#4)
Re: POC: Carefully exposing information without authentication

On Fri, May 30, 2025 at 9:34 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

I think calling it in the postmaster is a nonstarter.

Thanks for the feedback. Please find attached version two, which moves the
code to the very start of BackendInitialize in
tcop/backend_startup.c. If we handle the request, we simply proc_exit and
avoid all the other backend startup stuff. So still a big win. I also made
a first rough pass at the documentation.

Cheers,
Greg

--
Crunchy Data - https://www.crunchydata.com
Enterprise Postgres Software Products & Tech Support

Attachments:

0002-Allow-specific-information-to-be-output-directly-by-Postgres.patchapplication/octet-stream; name=0002-Allow-specific-information-to-be-output-directly-by-Postgres.patchDownload+308-1
#6Greg Sabino Mullane
greg@turnstep.com
In reply to: Greg Sabino Mullane (#5)
Re: POC: Carefully exposing information without authentication

Please find attached version 3, rebased for PG 19 and now featuring some
tests.

Cheers,
Greg

Attachments:

0003-Allow-specific-information-to-be-output-directly-by-Postgres.patchapplication/octet-stream; name=0003-Allow-specific-information-to-be-output-directly-by-Postgres.patchDownload+391-1
#7Greg Sabino Mullane
greg@turnstep.com
In reply to: Greg Sabino Mullane (#6)
Re: POC: Carefully exposing information without authentication

Version 4 attached, rebased to account for new tests, plus a new
instra-test check to make sure LWP::UserAgent is available before running.

Cheers,
Greg

Attachments:

0004-Allow-specific-information-to-be-output-directly-by-Postgres.patchapplication/octet-stream; name=0004-Allow-specific-information-to-be-output-directly-by-Postgres.patchDownload+397-1
#8Antonin Houska
ah@cybertec.at
In reply to: Greg Sabino Mullane (#7)
Re: POC: Carefully exposing information without authentication

Greg Sabino Mullane <htamfids@gmail.com> wrote:

Version 4 attached, rebased to account for new tests, plus a new instra-test
check to make sure LWP::UserAgent is available before running.

I'm still not sure it's necessary to handle the problem at socket level. I
imagine it can be implemented this way:

1. Add a new field to the PGconn structure, indicating that the client is only
requesting the server status information, and adjust pg_isready so it sets
this option.

2. Adjust libpq frontend (pqBuildStartupPacket3) so it adds the corresponding
option to the startup packet.

3. On server, if ProcessStartupPacket() sees that option, call ereport(FATAL)
with a specific error code, and let the appropriate GUCs control the contents
of the error message. pg_isready would then just print out the message.

I haven't tried to write any code, so it's possible that I'm missing
something.

Regarding configuration, I'd prefer a single GUC. The value can be a
comma-separated list of keywords, each representing particular piece of
information to be exposed.

--
Antonin Houska
Web: https://www.cybertec-postgresql.com

#9Greg Sabino Mullane
greg@turnstep.com
In reply to: Antonin Houska (#8)
Re: POC: Carefully exposing information without authentication

On Fri, Jan 9, 2026 at 8:56 AM Antonin Houska <ah@cybertec.at> wrote:

1. Add a new field to the PGconn structure

This kind of defeats one of the major strengths of this patch, which is
allowing systems that don't speak the protocol to get at this information.

Regarding configuration, I'd prefer a single GUC. The value can be a
comma-separated list of keywords, each representing particular piece of
information to be exposed.

Yes, I could see some advantages to that, although I still like the
simplicity of separate boolean values. I've no strong feelings either way.
Let's see if others weigh in.

Thanks for looking over this patch!

Cheers,
Greg

#10Greg Sabino Mullane
greg@turnstep.com
In reply to: Greg Sabino Mullane (#9)
Re: POC: Carefully exposing information without authentication

Please find attached a rebased and lightly reworked version of this patch.
The most significant change is the test file now uses IO::Socket::INET via
$node->raw_connect. Also changed to allow case-insensitive calls, moved to
a better docs group, moved the defines and typedefs up, and changed the
exit to just a simple _exit()

Cheers,
Greg

Attachments:

0005-Allow-specific-information-to-be-output-directly-by-Postgres.patchapplication/octet-stream; name=0005-Allow-specific-information-to-be-output-directly-by-Postgres.patchDownload+336-1
#11Andres Freund
andres@anarazel.de
In reply to: Greg Sabino Mullane (#10)
Re: POC: Carefully exposing information without authentication

Hi,

On 2026-02-17 14:42:48 -0500, Greg Sabino Mullane wrote:

Subject: [PATCH] Allow specific information to be output directly by Postgres.

I strongly encourage you to include a justification for why this is desirable,
so a casual reviewer doesn't have to reread the thread.

@@ -148,6 +172,14 @@ BackendInitialize(ClientSocket *client_sock, CAC_state cac)
StringInfoData ps_data;
MemoryContext oldcontext;

+	/*
+	 * Scan for a simple GET / HEAD request. If this is detected and
+	 * handled, we are done and can immediately exit
+	 */
+	if ((expose_recovery || expose_sysid || expose_version)
+		&& ExposeInformation(client_sock->sock))
+		_exit(0); /* Safe to use exit: no state or resources created yet */
+
/* Tell fd.c about the long-lived FD associated with the client_sock */
ReserveExternalFD();

What about direct TLS connections?

How can a cluster coordinator trust unauthenticated plain text communication
that can just be man-in-the-middled?

It's not obvious that it's a good idea to expose this on the same socket as
normal client connections. IMO you'd want to limit this to a smaller set of
interfaces than normal client connections.

+/*
+ * ExposeInformation
+ *
+ * Handle early socket probe before full backend startup.
+ * Responds to small set of predefined endpoints (e.g. GET /info)
+ *
+ * Requires at least one "expose_" GUC to be true.
+ *
+ * Returns true if any endpoint is recognized.
+ */
+
+static bool
+ExposeInformation(pgsocket fd)
+{
+	static endpoint_action endpoint_actions[] =
+	{
+		{
+			"HEAD /replica", &expose_recovery, EXPOSE_HEAD_REPLICA
+		},
+		{
+			"GET /replica", &expose_recovery, EXPOSE_GET_REPLICA
+		},
+		{
+			"GET /sysid", &expose_sysid, EXPOSE_GET_SYSID
+		},
+		{
+			"GET /version", &expose_version, EXPOSE_GET_VERSION
+		},
+		{
+			"GET /info", NULL, EXPOSE_GET_ALL
+		}
+	};
+
+	ssize_t		n;
+	char		buf[EXPOSE_MAX_QUERY + 1];
+	ExposeReturnType	type;
+
+	Assert(expose_recovery || expose_sysid || expose_version);
+
+	do
+	{
+		n = recv(fd, buf, EXPOSE_MAX_QUERY, MSG_PEEK);
+	} while (n < 0 && errno == EINTR);
+	/*
+	 * Leave as soon as possible if no chance we are interested.
+	 * (we also leave on partial reads from slow clients)
+	 * We also simply return false for n == -1
+	 */
+	if (n < EXPOSE_MIN_QUERY)
+		return false;

IIRC the socket is in blocking mode at this point (that's only changed in
pq_init()), therefore this might actually block? While it's unlikely, I don't
see any guarantee that a single receive would actually get the whole message
from the client either, so this seems like it might fail spuriously.

diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 271c033952e..3e99d9f6b7c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1010,6 +1010,25 @@
boot_val => 'false',
},
+{ name => 'expose_recovery', type => 'bool', context => 'PGC_SIGHUP', group => 'CONN_AUTH_AUTH',
+  short_desc => 'Exposes if the server is in recovery mode without a login.',
+  variable => 'expose_recovery',
+  boot_val => 'false',
+},
+
+{ name => 'expose_sysid', type => 'bool', context => 'PGC_SIGHUP', group => 'CONN_AUTH_AUTH',
+  short_desc => 'Exposes the system identifier without a login.',
+  variable => 'expose_sysid',
+  boot_val => 'false',
+},
+
+{ name => 'expose_version', type => 'bool', context => 'PGC_SIGHUP', group => 'CONN_AUTH_AUTH',
+  short_desc => 'Exposes the server version without a login.',
+  variable => 'expose_version',
+  boot_val => 'false',
+},
+

If we were to do this, I'd recommend a single expose GUC that has the
different values as a comma separated list, instead a growing list of GUCs.

Greetings,

Andres Freund

#12Greg Sabino Mullane
greg@turnstep.com
In reply to: Andres Freund (#11)
Re: POC: Carefully exposing information without authentication

Thank you for looking over this. New version attached.

On Tue, Feb 17, 2026 at 2:58 PM Andres Freund <andres@anarazel.de> wrote:

What about direct TLS connections?

Not handled.

How can a cluster coordinator trust unauthenticated plain text

communication that can just be man-in-the-middled?

They cannot. But that's why this is only exposing non-critical information.
Right now the security scanners that are banging on port 5432 and scraping
the returned error lines are not worried about man-in-the-middle. :)
Obviously, if your threat model is people capturing and modifying
non-encrypted traffic to your Postgres server, you would not use this.

It's not obvious that it's a good idea to expose this on the same socket as

normal client connections. IMO you'd want to limit this to a smaller set
of interfaces than normal client connections.

I'm not entirely clear what that smaller set would mean in practice.

IIRC the socket is in blocking mode at this point (that's only changed in
pq_init()), therefore this might actually block? While it's unlikely, I
don't see any guarantee that a single receive would actually get the whole
message from the client either, so this seems like it might fail spuriously.

Yes, there are some very unlikely edge cases, but this is meant to be good
enough, not a perfectly bulletproof HTTP server. Clients should try again
on failures. Which if they do occur for this trivial amount of traffic
probably indicates much bigger problems.

If we were to do this, I'd recommend a single expose GUC that has the

different values as a comma separated list, instead a growing list of GUCs.

Done - see attached for a new version which consolidates the bools into a
single comma-separated GUC called "expose_information". I also added some
docs, and changed the "replica" to return "REPLICA" instead of "RECOVERY".
I like the latter better, but replica lines up better with existing tools.

--
Cheers,
Greg

Attachments:

t51653_12
0006-Allow-specific-information-to-be-output-directly-by-Postgres.patchapplication/octet-stream; name=0006-Allow-specific-information-to-be-output-directly-by-Postgres.patchDownload+466-1
#13Greg Sabino Mullane
greg@turnstep.com
In reply to: Greg Sabino Mullane (#12)
Re: POC: Carefully exposing information without authentication

Please find attached a new version. I recently was working with a client
that had an F5 BIG-IP and needed to know how to tell which of a list of
servers was the primary (and which were replicas). If one has Patroni, then
it has an API for that. If not (as was the case here), the answer is to
write a shell script that connects to the local database, runs
pg_is_in_recovery(), and then outputs the result in a form readable by the
F5. Then make that script executable somehow on an open port, such that the
F5 can reach it. And possibly create a database user for this as well,
storing its credentials locally. Which is a whole lot of trouble just to
answer the question "is this the primary?". This reminded me once again of
this patch, so I took a new look at it and cleaned it up. Some changes I
made this round:

* Removed the "GET /info". Seems unlikely to be used: people are going to
want to know the version, or the role, but not both. So this was removed to
simplify the code
* Made the send() and recv() a lot more robust (including a timeout for
send(), and win32 socket handling [mostly based on be-secure.c: but
untested by myself on Windows]).
* Added a HEAD and GET for /primary, in addition to the existing /replica.
While this will never be a drop-in replacement for the full Patroni API,
the use of /primary and /replica should cover most cases (e.g. the F5 case)
* Changed the name of the GUC entry from "replica" to "role"
* Tightened up the checks to allow exact strings only, and added more tests

Cheers,
Greg

Attachments:

t51653_13
0007-Allow-specific-information-to-be-output-directly-by-Postgres.patchapplication/octet-stream; name=0007-Allow-specific-information-to-be-output-directly-by-Postgres.patchDownload+597-1
#14Greg Sabino Mullane
greg@turnstep.com
In reply to: Greg Sabino Mullane (#13)
Re: POC: Carefully exposing information without authentication

New, rebased version with an expanded commit message, and a small sleep in
the tests to try an overcome an issue with the WIN32 CI tests. Here's the
new message:

Allow specific information to be output directly by Postgres

Add a new GUC 'expose_information' that lets a small, fixed set of
facts about the current server (recovery/role status, system identifier,
and version) be queried over a plain HTTP request (GET or HEAD) on
the same port PostgreSQL already listens on - without requiring
authentication. This lets external tools have a way to quickly obtain
information without requiring an account or ability to speak the protocol.

expose_information takes a comma-separated list of options:

role - whether the server is currently a primary or a replica
version - the server's server_version_num
sysid - the system identifier

The GUC defaults to an empty string, so nothing is returned until
explicitly enabled. It can be changed with a reload.

When enabled, we check immediately after we fork by using
MSG_PEEK to scan the first few bytes the client has sent.
The socket is set non-blocking, and then restored to its
original state. If the bytes match a small selection of
strings, we handle it then and there: GET /replica, GET /primary,
HEAD /replica, HEAD /primary, GET /version, and GET /sysid.
Exact matches only: GET /versionx will not work.
A minimal HTTP/1.1 response is sent back, the connection is
closed, and the backend exits immediately. The outgoing message
has a timeout to prevent the client from keeping the connection
open.

The HEAD /replica and HEAD /primary are meant to be drop in
replacements for the Patroni REST API items. They return an
HTTP code (200 for true, 503 for false), with no content. This
allows for a lightweight health check, without requiring
creation of an account and other overhead.

The GET /version was designed to replace the common practice of
monitoring systems that send a bad login message to the server, and
use the debugging information about what line in our source code triggered
the error as a very rough indication as to what version the Postgres
server is running. Rather than all those workaround, they can simply
ask the server with a quick HTTP request.

If the bytes we examine do not match anything, they are left untouched
in the kernel socket buffer and control falls back to the normal
startup packet / authentication flow, so all connections are
unaffected even if expose_information is set.

A new TAP test, t/016_expose_information.pl, checks the output
of calls to the TCP socket for all the known endpoints, including
for servers in primary or replica mode. Also verify that normal
libpq connections still work.

Attachments:

t51653_14
0008-Allow-specific-information-to-be-output-directly-by-Postgres.patchapplication/octet-stream; name=0008-Allow-specific-information-to-be-output-directly-by-Postgres.patchDownload+599-1
#15Daniel Gustafsson
daniel@yesql.se
In reply to: Greg Sabino Mullane (#14)
Re: POC: Carefully exposing information without authentication

On 15 Sep 2026, at 19:57, Greg Sabino Mullane <htamfids@gmail.com> wrote:

..and a small sleep in the tests to try an overcome an issue with the WIN32 CI tests. Here's the new message:

What sort of issue? Sleeping in tests is generally never the right option but
can of course be useful in debugging.

Allow specific information to be output directly by Postgres

To be honest, I am absolutely terrified by any feature which does any level of
work based on user input before authentication or authorization. That being
said I've had this on my TODO to review, and while that is still left on the
TODO I threw some cursory looks while waiting for a test-run. Below are a few
comments from skimming.

	+	foreach(l, elemlist)
	+	{
	+		char	   *tok = (char *) lfirst(l);
	+
	+		if (pg_strcasecmp(tok, "role") == 0)
	+			newexpose |= EXPOSE_INFO_ROLE;
	+		else if (pg_strcasecmp(tok, "sysid") == 0)
	+			newexpose |= EXPOSE_INFO_SYSID;
	+		else if (pg_strcasecmp(tok, "version") == 0)
	+			newexpose |= EXPOSE_INFO_VERSION;
	+		else
	+		{
	+			GUC_check_errdetail("Unrecognized key word: \"%s\".", tok);
	+			pfree(rawstring);
	+			list_free(elemlist);
	+			return false;
	+		}
	+	}
	+
	+	pfree(rawstring);
	+	list_free(elemlist);

This will catch syntax errors and unrecognised keys, but not an empty list.
Should that be handled?

	+		if (
	+			(expose_information & endpoint_actions[i].require)
	+			&&
	+			strncmp(buf, endpoint_actions[i].endpoint, endpoint_len) == 0
	+			&&
	+			(buf[endpoint_len] == ' ' || buf[endpoint_len] == '\r' || buf[endpoint_len] == 	'\0')
	+			)
	+		{

While not overly complicated, it's also not particularly readable. Can it be
broken up into a series of conditionals to make it easier to follow?

+ pg_usleep(EXPOSE_SEND_RETRY_SLEEP_US);

pg_usleep is woken up and return before the expected sleep interval, and while
that might be a problem here it should at least be documented in a comment why
that's not a problem.

	+			/* The send() call failed in some way we cannot handle */
	+			elog(LOG, "failed to send information to client: %m");

Why elog instead of ereport, a syscall failing doesn't seem like an internal
error to me?

	+				case EXPOSE_TYPE_GET_VERSION:
	+					appendStringInfo(&content, "%d\r\n",
	+									 PG_VERSION_NUM);
	+					break;

Constructing static data for the response dynamically every time seems to
introduce quite asymmetrical cost relationships. The cost for the caller to
perform the request is very low compared to the work done serverside.

	+				case EXPOSE_TYPE_GET_PRIMARY:
	+					appendStringInfo(&content, "%d\r\n",
	+									 RecoveryInProgress() ? 0 : 1);

Maybe I'm daft and miss something obvious, but this is executed before shared
memory is available but RecoveryInProgress requires XLOGShmemInit to work?

--
Daniel Gustafsson

#16Tom Lane
tgl@sss.pgh.pa.us
In reply to: Daniel Gustafsson (#15)
Re: POC: Carefully exposing information without authentication

Daniel Gustafsson <daniel@yesql.se> writes:

To be honest, I am absolutely terrified by any feature which does any level of
work based on user input before authentication or authorization.

Yeah. TBH, I can't see this ever getting committed.

regards, tom lane

#17Greg Sabino Mullane
greg@turnstep.com
In reply to: Daniel Gustafsson (#15)
Re: POC: Carefully exposing information without authentication

Thanks for taking a look at this!

To be honest, I am absolutely terrified by any feature which does any

level of

work based on user input before authentication or authorization.

That's a very valid concern, and I completely understand your instinct.
I've been taking those concerns seriously, and hardening the patch over
time. While the initial version had a blocking peek, it now sets to
non-blocking and falls back to the usual startup packet handling. Client
input always runs through bounded strncmp calls, and is never parsed or
echoed back. The items that are output are not modifiable by the client,
and consist of low-impact, finite bits of information (version,sysid,role).
I'm happy to have people keep pushing back on this patch, so we can settle
any other concerns.

What sort of issue? Sleeping in tests is generally never the right

option but

can of course be useful in debugging.

The Win32 CI box was happily working until the test right after the first
reload via $node->reload().

https://github.com/postgres/postgres-cfbot/actions/runs/34537122766/job/103071572936

"No connection could be made because the target machine actively refused
it."

I don't have a working WIN32 to debug this, but perhaps something about the
reload causes connections to temporarily fail? Before I do a better
solution, just wanted to see if a sleep would clear things up in case
that's not the problem (if it is, will probably get a WIN32 system working,
then look into a bounded connection-retry loop in the tests).

This will catch syntax errors and unrecognised keys, but not an empty

list.

Should that be handled?

An empty list is fine to pass through.
This is modeled after log_destination in backend/utils/error/elog.c

While not overly complicated, it's also not particularly readable. Can

it be

broken up into a series of conditionals to make it easier to follow?

Sure, will do so in the next patch.

pg_usleep is woken up and return before the expected sleep interval, and

while

that might be a problem here it should at least be documented in a

comment why

that's not a problem.

Okay, will add a comment. It should not be a problem to leave early from a
signal - it just means more loops before we hit the timeout. I considered
WaitLatch but that seemed overkill as this is already handling a rare
condition and the sleep and timeout are both relatively small.

Why elog instead of ereport, a syscall failing doesn't seem like an

internal error to me?

It didn't seem like the right place for ereport to me - we don't have a
real SQL connection, so no need to worry about SQLSTATE and translations.
It seems to fall into the same sort of category as the "could not close
listen socket" failures in postmaster.c

Constructing static data for the response dynamically every time seems to
introduce quite asymmetrical cost relationships. The cost for the caller

to

perform the request is very low compared to the work done serverside.

I guess we could precompile this version string response, but it seems a
micro-optimization as the fork/exit of each connection dwarfs any string
handling costs.

Maybe I'm daft and miss something obvious, but this is executed before

shared

memory is available but RecoveryInProgress requires XLOGShmemInit to work?

It's always available, since we just forked off of postmaster, and we don't
need to do anything special to access XLogCtl. For Win32, the
PGSharedMemoryReAttach call happens before the code in this patch gets
invoked.

Cheers,
Greg

#18Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Tom Lane (#16)
Re: POC: Carefully exposing information without authentication

On Wed, Sep 16, 2026 at 7:21 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Daniel Gustafsson <daniel@yesql.se> writes:

To be honest, I am absolutely terrified by any feature which does any level of
work based on user input before authentication or authorization.

Yeah. TBH, I can't see this ever getting committed.

I agree.

Greg, I think any design that mixes HTTP and the Postgres protocol on
the wire is probably doomed, because then we all get to be forever
vigilant against cross-protocol attacks. I have many, many, more
issues with the design as presented, but IMNSHO the core feature is a
nonstarter, so it doesn't make much sense to get into those details.

It seems to me that there's nothing wrong with running a separate
service that provides that information, which can then be tailored to
whatever security requirements (or not) are required.

--Jacob