From 9eec791d666bebf3735ccb286e6f044f391f85fd Mon Sep 17 00:00:00 2001
From: Zsolt Parragi <zsolt.parragi@percona.com>
Date: Thu, 11 Dec 2025 23:56:08 +0000
Subject: [PATCH 1/2] Split PGOAUTHDEBUG=UNSAFE into multiple options

---
 doc/src/sgml/libpq.sgml                      | 144 ++++++++++++++----
 src/interfaces/libpq-oauth/Makefile          |  12 +-
 src/interfaces/libpq-oauth/meson.build       |   6 +-
 src/interfaces/libpq-oauth/oauth-curl.c      |  18 +--
 src/interfaces/libpq-oauth/oauth-utils.c     |  11 --
 src/interfaces/libpq-oauth/oauth-utils.h     |   2 +-
 src/interfaces/libpq-oauth/test-oauth-curl.c |   8 +-
 src/interfaces/libpq/Makefile                |   3 +-
 src/interfaces/libpq/fe-auth-oauth-debug.c   | 147 +++++++++++++++++++
 src/interfaces/libpq/fe-auth-oauth.c         |  16 +-
 src/interfaces/libpq/fe-auth-oauth.h         |  19 ++-
 src/interfaces/libpq/meson.build             |   1 +
 12 files changed, 317 insertions(+), 70 deletions(-)
 create mode 100644 src/interfaces/libpq/fe-auth-oauth-debug.c

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 21e1ba34a4e..5d70cc2b261 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -10508,41 +10508,123 @@ typedef struct PGoauthBearerRequest
    <title>Debugging and Developer Settings</title>
 
    <para>
-    A "dangerous debugging mode" may be enabled by setting the environment
-    variable <envar>PGOAUTHDEBUG=UNSAFE</envar>. This functionality is provided
-    for ease of local development and testing only. It does several things that
-    you will not want a production system to do:
+    Debug features may be enabled by setting the <envar>PGOAUTHDEBUG</envar>
+    environment variable. This functionality is provided for ease of local
+    development and testing. The variable accepts a comma-separated list of
+    debug options:
+
+    <programlisting>
+PGOAUTHDEBUG=option1,option2,...    <lineannotation>for safe options only</lineannotation>
+PGOAUTHDEBUG=UNSAFE:option1,option2,...    <lineannotation>when using unsafe options</lineannotation>
+PGOAUTHDEBUG=UNSAFE    <lineannotation>legacy format; enables all options</lineannotation>
+    </programlisting>
+   </para>
 
-    <itemizedlist spacing="compact">
-     <listitem>
-      <para>
-       permits the use of unencrypted HTTP during the OAuth provider exchange
-      </para>
-     </listitem>
-     <listitem>
-      <para>
-       allows the system's trusted CA list to be completely replaced using the
-       <envar>PGOAUTHCAFILE</envar> environment variable
-      </para>
-     </listitem>
-     <listitem>
-      <para>
-       prints HTTP traffic (containing several critical secrets) to standard
-       error during the OAuth flow
-      </para>
-     </listitem>
-     <listitem>
-      <para>
-       permits the use of zero-second retry intervals, which can cause the
-       client to busy-loop and pointlessly consume CPU
-      </para>
-     </listitem>
-    </itemizedlist>
+   <para>
+    Available debug options:
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>http</literal> (unsafe)</term>
+      <listitem>
+       <para>
+        Permits the use of unencrypted HTTP during the OAuth provider exchange.
+        This allows OAuth credentials to be transmitted over unencrypted
+        connections, which is extremely dangerous and should only be used for
+        local testing.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>trace</literal> (unsafe)</term>
+      <listitem>
+       <para>
+        Prints HTTP traffic to standard error during the OAuth flow. This output
+        contains critical secrets including bearer tokens, client secrets, access
+        tokens, and authorization codes. Never share this output with third
+        parties.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>custom-ca</literal> (unsafe)</term>
+      <listitem>
+       <para>
+        Allows the system's trusted CA list to be completely replaced using the
+        <envar>PGOAUTHCAFILE</envar> environment variable. This can facilitate
+        man-in-the-middle attacks when testing with self-signed certificates.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>fast-retry</literal> (safe)</term>
+      <listitem>
+       <para>
+        Permits the use of zero-second retry intervals instead of the normal
+        minimum of one second. This can speed up tests but may cause the client
+        to busy-loop and consume CPU unnecessarily.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>poll-counts</literal> (safe)</term>
+      <listitem>
+       <para>
+        Prints the total number of poll() calls to standard error when the
+        OAuth flow completes. This helps developers debug the async multiplexer
+        behavior.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>print-plugin-errors</literal> (safe)</term>
+      <listitem>
+       <para>
+        Prints plugin loading errors to standard error. This helps developers
+        and package maintainers debug issues when the OAuth plugin fails to load.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+   </para>
+
+   <para>
+    Unsafe options (<literal>http</literal>, <literal>trace</literal>,
+    <literal>custom-ca</literal>) require the <literal>UNSAFE:</literal> prefix.
+    If unsafe options are specified without this prefix, a warning is printed
+    to standard error and that option is ignored. Other valid options in the
+    list continue to work. Safe options (<literal>fast-retry</literal>,
+    <literal>poll-counts</literal>, <literal>print-plugin-errors</literal>) can
+    be used without the prefix.
+   </para>
+
+   <para>
+    Unrecognized option names will also trigger a warning and be ignored, while
+    valid options continue to work. This helps catch typos in the environment
+    variable configuration without breaking the debugging of valid options.
    </para>
+
+   <para>
+    Examples:
+    <programlisting>
+PGOAUTHDEBUG=fast-retry,poll-counts    <lineannotation>safe options only</lineannotation>
+PGOAUTHDEBUG=UNSAFE:http,trace    <lineannotation>enable HTTP and traffic logging</lineannotation>
+PGOAUTHDEBUG=UNSAFE:http,custom-ca,poll-counts    <lineannotation>mix of unsafe and safe</lineannotation>
+PGOAUTHDEBUG=UNSAFE    <lineannotation>legacy; enables all options</lineannotation>
+    </programlisting>
+   </para>
+
    <warning>
     <para>
-     Do not share the output of the OAuth flow traffic with third parties. It
-     contains secrets that can be used to attack your clients and servers.
+     Never use unsafe debug options in production environments. The
+     <literal>trace</literal> option in particular exposes secrets that can be
+     used to attack your clients and servers. Do not share the output with third
+     parties.
     </para>
    </warning>
   </sect2>
diff --git a/src/interfaces/libpq-oauth/Makefile b/src/interfaces/libpq-oauth/Makefile
index a5f2d65fcad..60ce0ad8c58 100644
--- a/src/interfaces/libpq-oauth/Makefile
+++ b/src/interfaces/libpq-oauth/Makefile
@@ -30,15 +30,25 @@ override CFLAGS += $(PTHREAD_CFLAGS)
 OBJS = \
 	$(WIN32RES)
 
-OBJS_STATIC = oauth-curl.o
+OBJS_STATIC = \
+	oauth-curl.o \
+	fe-auth-oauth-debug.o
 
 # The shared library needs additional glue symbols.
 OBJS_SHLIB = \
 	oauth-curl_shlib.o \
 	oauth-utils.o \
+	fe-auth-oauth-debug_shlib.o
 
 oauth-utils.o: override CPPFLAGS += -DUSE_DYNAMIC_OAUTH
 oauth-curl_shlib.o: override CPPFLAGS_SHLIB += -DUSE_DYNAMIC_OAUTH
+fe-auth-oauth-debug_shlib.o: override CPPFLAGS_SHLIB += -DUSE_DYNAMIC_OAUTH
+
+fe-auth-oauth-debug.o: $(libpq_srcdir)/fe-auth-oauth-debug.c
+	$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
+
+fe-auth-oauth-debug_shlib.o: $(libpq_srcdir)/fe-auth-oauth-debug.c
+	$(CC) $(CFLAGS) $(CFLAGS_SL) $(CPPFLAGS) $(CPPFLAGS_SHLIB) -c $< -o $@
 
 # Add shlib-/stlib-specific objects.
 $(shlib): override OBJS += $(OBJS_SHLIB)
diff --git a/src/interfaces/libpq-oauth/meson.build b/src/interfaces/libpq-oauth/meson.build
index d8a0c04095a..86a10ccca27 100644
--- a/src/interfaces/libpq-oauth/meson.build
+++ b/src/interfaces/libpq-oauth/meson.build
@@ -6,6 +6,7 @@ endif
 
 libpq_oauth_sources = files(
   'oauth-curl.c',
+  '../libpq/fe-auth-oauth-debug.c',
 )
 
 # The shared library needs additional glue symbols.
@@ -50,7 +51,10 @@ libpq_oauth_so = shared_module(libpq_oauth_name,
 
 libpq_oauth_test_deps = []
 
-oauth_test_sources = files('test-oauth-curl.c') + libpq_oauth_so_sources
+oauth_test_sources = files(
+  'test-oauth-curl.c',
+  '../libpq/fe-auth-oauth-debug.c',
+) + libpq_oauth_so_sources
 
 if host_system == 'windows'
   oauth_test_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
diff --git a/src/interfaces/libpq-oauth/oauth-curl.c b/src/interfaces/libpq-oauth/oauth-curl.c
index 691e7ec1d9f..ac8b4631d53 100644
--- a/src/interfaces/libpq-oauth/oauth-curl.c
+++ b/src/interfaces/libpq-oauth/oauth-curl.c
@@ -278,7 +278,7 @@ struct async_ctx
 	int			running;		/* is asynchronous work in progress? */
 	bool		user_prompted;	/* have we already sent the authz prompt? */
 	bool		used_basic_auth;	/* did we send a client secret? */
-	bool		debugging;		/* can we give unsafe developer assistance? */
+	oauth_debug_flags debug_flags;	/* can we give developer assistance */
 	int			dbg_num_calls;	/* (debug mode) how many times were we called? */
 };
 
@@ -985,7 +985,7 @@ parse_interval(struct async_ctx *actx, const char *interval_str)
 	parsed = ceil(parsed);
 
 	if (parsed < 1)
-		return actx->debugging ? 0 : 1;
+		return actx->debug_flags.fast_retry ? 0 : 1;
 
 	else if (parsed >= INT_MAX)
 		return INT_MAX;
@@ -1759,7 +1759,7 @@ setup_curl_handles(struct async_ctx *actx)
 	 */
 	CHECK_SETOPT(actx, CURLOPT_NOSIGNAL, 1L, return false);
 
-	if (actx->debugging)
+	if (actx->debug_flags.trace)
 	{
 		/*
 		 * Set a callback for retrieving error information from libcurl, the
@@ -1791,7 +1791,7 @@ setup_curl_handles(struct async_ctx *actx)
 		const long	unsafe = CURLPROTO_HTTPS | CURLPROTO_HTTP;
 #endif
 
-		if (actx->debugging)
+		if (actx->debug_flags.http)
 			protos = unsafe;
 
 		CHECK_SETOPT(actx, popt, protos, return false);
@@ -1805,7 +1805,7 @@ setup_curl_handles(struct async_ctx *actx)
 	 * the flow to work at all, so any changes to the roots are likely to be
 	 * done system-wide.
 	 */
-	if (actx->debugging)
+	if (actx->debug_flags.custom_ca)
 	{
 		const char *env;
 
@@ -2271,7 +2271,7 @@ check_for_device_flow(struct async_ctx *actx)
 	 * decent time to bail out if we're not using HTTPS for the endpoints
 	 * we'll use for the flow.
 	 */
-	if (!actx->debugging)
+	if (!actx->debug_flags.http)
 	{
 		if (pg_strncasecmp(provider->device_authorization_endpoint,
 						   HTTPS_SCHEME, strlen(HTTPS_SCHEME)) != 0)
@@ -2793,8 +2793,8 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
 		actx->mux = PGINVALID_SOCKET;
 		actx->timerfd = -1;
 
-		/* Should we enable unsafe features? */
-		actx->debugging = oauth_unsafe_debugging_enabled();
+		/* Parse debug flags from environment */
+		actx->debug_flags = oauth_get_debug_flags();
 
 		state->async_ctx = actx;
 
@@ -3074,7 +3074,7 @@ pg_fe_run_oauth_flow(PGconn *conn)
 	actx = state->async_ctx;
 	Assert(actx || result == PGRES_POLLING_FAILED);
 
-	if (actx && actx->debugging)
+	if (actx && actx->debug_flags.poll_counts)
 	{
 		actx->dbg_num_calls++;
 		if (result == PGRES_POLLING_OK || result == PGRES_POLLING_FAILED)
diff --git a/src/interfaces/libpq-oauth/oauth-utils.c b/src/interfaces/libpq-oauth/oauth-utils.c
index 4ebe7d0948c..fab8990b746 100644
--- a/src/interfaces/libpq-oauth/oauth-utils.c
+++ b/src/interfaces/libpq-oauth/oauth-utils.c
@@ -142,17 +142,6 @@ libpq_gettext(const char *msgid)
 
 #endif							/* ENABLE_NLS */
 
-/*
- * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
- */
-bool
-oauth_unsafe_debugging_enabled(void)
-{
-	const char *env = getenv("PGOAUTHDEBUG");
-
-	return (env && strcmp(env, "UNSAFE") == 0);
-}
-
 /*
  * Duplicate SOCK_ERRNO* definitions from libpq-int.h, for use by
  * pq_block/reset_sigpipe().
diff --git a/src/interfaces/libpq-oauth/oauth-utils.h b/src/interfaces/libpq-oauth/oauth-utils.h
index 9f4d5b692d2..4d986fcb358 100644
--- a/src/interfaces/libpq-oauth/oauth-utils.h
+++ b/src/interfaces/libpq-oauth/oauth-utils.h
@@ -76,7 +76,7 @@ typedef enum
 } PGTernaryBool;
 
 extern void libpq_append_conn_error(PGconn *conn, const char *fmt,...) pg_attribute_printf(2, 3);
-extern bool oauth_unsafe_debugging_enabled(void);
+extern oauth_debug_flags oauth_get_debug_flags(void);
 extern int	pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending);
 extern void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe);
 
diff --git a/src/interfaces/libpq-oauth/test-oauth-curl.c b/src/interfaces/libpq-oauth/test-oauth-curl.c
index 4328a332738..d9971797b5c 100644
--- a/src/interfaces/libpq-oauth/test-oauth-curl.c
+++ b/src/interfaces/libpq-oauth/test-oauth-curl.c
@@ -89,7 +89,13 @@ init_test_actx(void)
 
 	actx->mux = PGINVALID_SOCKET;
 	actx->timerfd = -1;
-	actx->debugging = true;
+	actx->debug_flags.http = true;
+	actx->debug_flags.trace = true;
+	actx->debug_flags.custom_ca = true;
+	actx->debug_flags.issuer_mismatch = true;
+	actx->debug_flags.fast_retry = true;
+	actx->debug_flags.poll_counts = true;
+	actx->debug_flags.print_plugin_errors = true;
 
 	initPQExpBuffer(&actx->errbuf);
 
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index bf4baa92917..1165859859c 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -44,7 +44,8 @@ OBJS = \
 	legacy-pqsignal.o \
 	libpq-events.o \
 	pqexpbuffer.o \
-	fe-auth.o
+	fe-auth.o \
+	fe-auth-oauth-debug.o
 
 # File shared across all SSL implementations supported.
 ifneq ($(with_ssl),no)
diff --git a/src/interfaces/libpq/fe-auth-oauth-debug.c b/src/interfaces/libpq/fe-auth-oauth-debug.c
new file mode 100644
index 00000000000..f65f069fed8
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-oauth-debug.c
@@ -0,0 +1,147 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-oauth-debug.c
+ *	  Parsing logic for PGOAUTHDEBUG environment variable
+ *
+ * This file contains pure string parsing logic with no dependencies on
+ * libpq or libpq-oauth implementation details. It's compiled into both
+ * libraries to avoid code duplication.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * 	src/interfaces/libpq/fe-auth-oauth-debug.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "fe-auth-oauth.h"
+
+/*
+ * Parse a single debug option from PGOAUTHDEBUG.
+ * Returns true if the option is recognized, false otherwise.
+ * Sets *is_unsafe to indicate if this option requires the UNSAFE: prefix.
+ */
+static bool
+parse_debug_option(const char *option, oauth_debug_flags *flags, bool *is_unsafe)
+{
+	*is_unsafe = false;
+
+	/* Unsafe options */
+	if (strcmp(option, "http") == 0)
+	{
+		flags->http = true;
+		*is_unsafe = true;
+		return true;
+	}
+	else if (strcmp(option, "trace") == 0)
+	{
+		flags->trace = true;
+		*is_unsafe = true;
+		return true;
+	}
+	else if (strcmp(option, "custom-ca") == 0)
+	{
+		flags->custom_ca = true;
+		*is_unsafe = true;
+		return true;
+	}
+	/* Safe options */
+	else if (strcmp(option, "fast-retry") == 0)
+	{
+		flags->fast_retry = true;
+		return true;
+	}
+	else if (strcmp(option, "poll-counts") == 0)
+	{
+		flags->poll_counts = true;
+		return true;
+	}
+	else if (strcmp(option, "print-plugin-errors") == 0)
+	{
+		flags->print_plugin_errors = true;
+		return true;
+	}
+
+	return false;
+}
+
+/*
+ * Parses the PGOAUTHDEBUG environment variable and returns debug flags.
+ *
+ * Supported formats:
+ *   PGOAUTHDEBUG=UNSAFE              - legacy format, enables all features
+ *   PGOAUTHDEBUG=option1,option2     - enable safe features only
+ *   PGOAUTHDEBUG=UNSAFE:opt1,opt2    - enable unsafe and/or safe features
+ *
+ * Prints a warning and skips the invalid option if:
+ * - An unrecognized option is specified
+ * - An unsafe option is specified without the UNSAFE: prefix
+ */
+oauth_debug_flags
+oauth_get_debug_flags(void)
+{
+	oauth_debug_flags flags = {0};
+	const char *env = getenv("PGOAUTHDEBUG");
+	char	   *options_str;
+	char	   *option;
+	char	   *saveptr = NULL;
+	bool		unsafe_prefix = false;
+
+	if (!env || env[0] == '\0')
+		return flags;
+
+	if (strcmp(env, "UNSAFE") == 0)
+	{
+		flags.http = true;
+		flags.trace = true;
+		flags.custom_ca = true;
+		flags.fast_retry = true;
+		flags.poll_counts = true;
+		flags.print_plugin_errors = true;
+		return flags;
+	}
+
+	if (strncmp(env, "UNSAFE:", 7) == 0)
+	{
+		unsafe_prefix = true;
+		env += 7;
+	}
+
+	options_str = strdup(env);
+	if (!options_str)
+		return flags;
+
+	option = strtok_r(options_str, ",", &saveptr);
+	while (option != NULL)
+	{
+		bool		is_unsafe;
+
+		if (!parse_debug_option(option, &flags, &is_unsafe))
+		{
+			fprintf(stderr,
+					"WARNING: PGOAUTHDEBUG: unrecognized debug option \"%s\" (ignored)\n",
+					option);
+		}
+		else if (is_unsafe && !unsafe_prefix)
+		{
+			fprintf(stderr,
+					"WARNING: PGOAUTHDEBUG: unsafe option \"%s\" requires UNSAFE: prefix (ignored)\n"
+					"Use: PGOAUTHDEBUG=UNSAFE:%s\n",
+					option, option);
+		}
+
+		option = strtok_r(NULL, ",", &saveptr);
+	}
+
+	free(options_str);
+
+	return flags;
+}
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
index 67879d64b39..5dff354c19b 100644
--- a/src/interfaces/libpq/fe-auth-oauth.c
+++ b/src/interfaces/libpq/fe-auth-oauth.c
@@ -383,7 +383,7 @@ issuer_from_well_known_uri(PGconn *conn, const char *wkuri)
 		authority_start = wkuri + strlen(HTTPS_SCHEME);
 
 	if (!authority_start
-		&& oauth_unsafe_debugging_enabled()
+		&& oauth_get_debug_flags().http
 		&& pg_strncasecmp(wkuri, HTTP_SCHEME, strlen(HTTP_SCHEME)) == 0)
 	{
 		/* Allow http:// for testing only. */
@@ -877,7 +877,7 @@ use_builtin_flow(PGconn *conn, fe_oauth_state *state)
 		 *
 		 * Note that POSIX dlerror() isn't guaranteed to be threadsafe.
 		 */
-		if (oauth_unsafe_debugging_enabled())
+		if (oauth_get_debug_flags().print_plugin_errors)
 			fprintf(stderr, "failed dlopen for libpq-oauth: %s\n", dlerror());
 
 		return false;
@@ -891,7 +891,7 @@ use_builtin_flow(PGconn *conn, fe_oauth_state *state)
 		 * This is more of an error condition than the one above, but due to
 		 * the dlerror() threadsafety issue, lock it behind PGOAUTHDEBUG too.
 		 */
-		if (oauth_unsafe_debugging_enabled())
+		if (oauth_get_debug_flags().print_plugin_errors)
 			fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
 
 		dlclose(state->builtin_flow);
@@ -1392,13 +1392,3 @@ pqClearOAuthToken(PGconn *conn)
 	conn->oauth_token = NULL;
 }
 
-/*
- * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
- */
-bool
-oauth_unsafe_debugging_enabled(void)
-{
-	const char *env = getenv("PGOAUTHDEBUG");
-
-	return (env && strcmp(env, "UNSAFE") == 0);
-}
diff --git a/src/interfaces/libpq/fe-auth-oauth.h b/src/interfaces/libpq/fe-auth-oauth.h
index 5c8a24b76fa..272638ea359 100644
--- a/src/interfaces/libpq/fe-auth-oauth.h
+++ b/src/interfaces/libpq/fe-auth-oauth.h
@@ -42,8 +42,25 @@ typedef struct
 	void	   *builtin_flow;
 } fe_oauth_state;
 
+/*
+ * Debug flags for PGOAUTHDEBUG environment variable.
+ * Each flag controls a specific debug feature.
+ */
+typedef struct oauth_debug_flags
+{
+	/* UNSAFE features - require UNSAFE: prefix */
+	bool		http;			/* allow HTTP (unencrypted) connections */
+	bool		trace;			/* log HTTP traffic (exposes secrets) */
+	bool		custom_ca;		/* allow custom CA certificate file */
+
+	/* SAFE features - allowed without UNSAFE: prefix */
+	bool		fast_retry;		/* allow zero-second retry intervals */
+	bool		poll_counts;	/* print poll() statistics */
+	bool		print_plugin_errors;	/* print plugin loading errors */
+} oauth_debug_flags;
+
 extern void pqClearOAuthToken(PGconn *conn);
-extern bool oauth_unsafe_debugging_enabled(void);
+extern oauth_debug_flags oauth_get_debug_flags(void);
 extern bool use_builtin_flow(PGconn *conn, fe_oauth_state *state);
 
 /* Mechanisms in fe-auth-oauth.c */
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index c5ecd9c3a87..7f2999aebb3 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -2,6 +2,7 @@
 
 libpq_sources = files(
   'fe-auth-oauth.c',
+  'fe-auth-oauth-debug.c',
   'fe-auth-scram.c',
   'fe-auth.c',
   'fe-cancel.c',
-- 
2.43.0

