Skip to content
Open
Show file tree
Hide file tree
Changes from 78 commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
abb93dc
docs: design spec for native PostgreSQL backend protocol (replace lib…
renecannao Jun 10, 2026
e2448f8
docs: Phase 0+1 implementation plan for native PostgreSQL backend pro…
renecannao Jun 10, 2026
0572b03
docs: fix build commands in plan (plain make, never bare make -j)
renecannao Jun 10, 2026
eef410e
feat(pgsql): add pgsql-use_native_backend_protocol runtime variable (…
renecannao Jun 10, 2026
d539351
feat(pgsql): add native_mode + backend protocol member to PgSQL_Conne…
renecannao Jun 10, 2026
e5e99e3
feat(pgsql): native_mode dispatch skeleton with libpq fallback (Phase…
renecannao Jun 11, 2026
5447992
feat(pgsql): native backend message framer with partial-message handl…
renecannao Jun 11, 2026
6874b5a
fix(pgsql): harden backend framer (msg-length cap, realloc/overflow c…
renecannao Jun 11, 2026
6ed54c5
feat(pgsql): native startup + SSLRequest encoders with unit tests
renecannao Jun 11, 2026
ad31ded
feat(pgsql): native md5 password auth builder with known-vector unit …
renecannao Jun 11, 2026
4e90372
feat(pgsql): native SCRAM-SHA-256 client exchange over libscram with …
renecannao Jun 11, 2026
630c076
docs: defer SCRAM channel binding (-PLUS) to Phase 1b (libscram lacks…
renecannao Jun 11, 2026
b7bedf0
feat(pgsql): native PostgreSQL connect + auth (trust/md5/SCRAM, no TL…
renecannao Jun 11, 2026
b027c8c
fix(pgsql): native connect entry guards (handler first-call flag, asy…
renecannao Jun 11, 2026
f953a3f
feat(pgsql): native backend TLS (SSLRequest + client handshake + cert…
renecannao Jun 11, 2026
f9ac560
test(pgsql): differential native-vs-libpq auth test (compiles; run pe…
renecannao Jun 11, 2026
6e344f0
feat(pgsql): native simple-query + simple-command execution (stream-t…
renecannao Jun 12, 2026
21600ac
fix(pgsql): native Query message must have exactly one NUL terminator
renecannao Jun 13, 2026
741ce1e
fix(pgsql): native query error must not be misclassified as broken co…
renecannao Jun 13, 2026
02db8fe
docs: design spec for SCRAM-SHA-256-PLUS / channel binding (Phase 1b)
renecannao Jun 14, 2026
e0ce545
docs: implementation plan for SCRAM-SHA-256-PLUS (Phase 1b-A)
renecannao Jun 14, 2026
b9583d0
test(pgsql): add unit tests for libscram cbind patch (failing until p…
renecannao Jun 14, 2026
5937ba4
feat(pgsql): libscram scaffolding for SCRAM-PLUS cbind (field, init, …
renecannao Jun 14, 2026
f8e0534
feat(pgsql): libscram build_client_first_message emits p=tls-server-e…
renecannao Jun 14, 2026
89161a0
feat(pgsql): libscram build_client_final_message uses cbind c=; libsc…
renecannao Jun 14, 2026
320ae67
feat(pgsql): pg_tls_server_end_point + RFC 5929 MD5/SHA-1 upgrade (te…
renecannao Jun 14, 2026
fc42ba1
feat(pgsql): pg_scram_build_cbind_input_tls_server_end_point (tests 1…
renecannao Jun 14, 2026
1f4709d
feat(pgsql): pg_scram_set_cbind wrapper over scram_state_set_cbind_input
renecannao Jun 14, 2026
5328403
feat(pgsql): native_drive_auth mechanism selection prefers -PLUS when…
renecannao Jun 14, 2026
3ef8875
test(pgsql): broader query differential (15 queries) + 10k-row stream…
renecannao Jun 14, 2026
d0a53ae
spec(pgsql): native protocol coverage for transactions, COPY, prepare…
renecannao Jun 14, 2026
6b4c7c5
spec(pgsql): record user decisions on byte-equal, wire format, COPY t…
renecannao Jun 15, 2026
e076212
plan(pgsql): transactions/copy/prepared coverage tests (PR 1, tests o…
renecannao Jun 15, 2026
a407f17
test(pgsql): add CoverageRecorder helper for native-vs-libpq tracking
renecannao Jun 15, 2026
392a199
fix+test(pgsql): native-mode async_reset_session/async_ping/copy_star…
renecannao Jun 15, 2026
0b7efc3
test(pgsql): COPY differential (14 cases) + coverage summary
renecannao Jun 15, 2026
9f08de8
test(pgsql): prepared statements differential (21 cases) + coverage s…
renecannao Jun 15, 2026
e9428cb
fix(pgsql): native-mode session tx-state sync (handle_transaction_sta…
renecannao Jun 15, 2026
f9a95c6
test(pgsql): loosen T13 state check (Postgres auto-commit after DEALL…
renecannao Jun 15, 2026
c3518cd
fix+test(pgsql): graceful FEATURE_NOT_SUPPORTED on native+extended-qu…
renecannao Jun 15, 2026
d4a11c5
fix+feat(pgsql): stats refresh native-guard; native extq scaffolding …
renecannao Jun 16, 2026
4b77532
test(pgsql): LISTEN/NOTIFY differential (3 cases) + multiplex-gap det…
renecannao Jun 16, 2026
86caf12
test(pgsql): stress test (200x PREPARE/SELECT/txn) + 5-test 61/61 green
renecannao Jun 16, 2026
ed0a1fb
fix(pgsql): stats SQL3_Free_Connections crash on native connections; …
renecannao Jul 7, 2026
cc2aa79
feat(pgsql): native wire builders for CopyFail and extended-query fra…
renecannao Jul 7, 2026
54b3633
feat(pgsql): native-drive COPY hardening — CopyFail safety net for 'G…
renecannao Jul 7, 2026
f7f80ff
fix(pgsql): native COPY CopyFail partial-send hang — return on PG_EVE…
renecannao Jul 7, 2026
3dba6ed
test(pgsql): truthful COPY coverage — classify fast_forward-routed ca…
renecannao Jul 7, 2026
051dd25
feat(pgsql): capture raw extended-query client bytes at session intak…
renecannao Jul 7, 2026
5cfa4f3
fix(pgsql): extq raw capture — snapshot pkt before parse (move_pkt ze…
renecannao Jul 7, 2026
c9c0073
fix(pgsql): free extq raw frame in async Execute-completion epilogue …
renecannao Jul 7, 2026
a254976
feat(pgsql): native extended-query pass-through wired through the asy…
renecannao Jul 7, 2026
683d385
refactor(pgsql): remove native extq raw pass-through (superseded by s…
renecannao Jul 7, 2026
6ca9fc5
feat(pgsql): native frontend-message builders for extended query (Par…
renecannao Jul 7, 2026
6645312
feat(pgsql): native extended-query wire drives through the prepared-s…
renecannao Jul 7, 2026
dc6309d
fix(pgsql): native extq bare-ack step misread as empty result (assert…
renecannao Jul 7, 2026
f1ba189
test(pgsql): prepared differential strict — byte-equality for all EXT…
renecannao Jul 7, 2026
295072c
fix(pgsql): injected-Sync recovery warning once per connection; posit…
renecannao Jul 7, 2026
0eb57a8
feat(pgsql): statement-level Describe metadata cache on PgSQL_STMT_Gl…
renecannao Jul 7, 2026
6f8edef
fix(pgsql): describe-cache hit evidence at debug level (was per-hit I…
renecannao Jul 7, 2026
62f4e3c
docs(pgsql): spec statuses for the stmt-pipeline pivot; drop stale pa…
renecannao Jul 7, 2026
c4d6c70
build: prefer OpenSSL 3.x shared libs when multiple versions coexist …
renecannao Jul 7, 2026
91f82e9
docs(pgsql): named-portals implementation plan (registry, BIND phase,…
renecannao Jul 7, 2026
dfb0490
feat(pgsql): named-portal registry + immediate native Bind dispatch (…
renecannao Jul 7, 2026
81f2b23
fix(pgsql): native-mode locked_on_hostgroup epilogue — replace libpq-…
renecannao Jul 8, 2026
200f75a
feat(pgsql): named-portal Execute/Describe/Close routing, max_rows + …
renecannao Jul 8, 2026
1becd76
test(pgsql): raw-wire named-portal differential vs direct PostgreSQL …
renecannao Jul 8, 2026
aeef97b
fix(pgsql): clear named-portal registry when a SIMPLE query ends the …
renecannao Jul 8, 2026
c274c39
test(pgsql): portals test — real pin-release assertion, BAIL_OUT-safe…
renecannao Jul 8, 2026
5c306c2
docs(pgsql): mark named portals implemented in spec; clear closing_po…
renecannao Jul 8, 2026
41df3ca
feat(pgsql): native backend query cancellation via raw CancelRequest …
renecannao Jul 8, 2026
ad4312c
test(pgsql): cancellation differential (libpq bar vs native)
renecannao Jul 8, 2026
d561b76
fix(pgsql): named-portal teardown UAF vs event logger (ASAN); BIO own…
renecannao Jul 8, 2026
7a9671c
fix(pgsql): bounded connect in native cancel; positive native-path as…
renecannao Jul 8, 2026
b53ae6c
test(pgsql): portals corpus — statement-closed-while-portal-open (las…
renecannao Jul 8, 2026
8018060
fix(pgsql): drop duplicate native-mode explicit-txn registration (COM…
renecannao Jul 8, 2026
4e8f025
test(pgsql): native extended-protocol txn cases + zero-warning assertion
renecannao Jul 8, 2026
181e87c
docs(agents): TAP-infra build/deploy and provisioning hazard catalog …
renecannao Jul 8, 2026
89ed5b6
Merge remote-tracking branch 'origin/v3.0' into feature/pgsql-native-…
renecannao Jul 10, 2026
f603e83
fix(infra): provision admin-debug=true for pgsql16 so proxy_debug() e…
renecannao Jul 10, 2026
836cc4b
Merge remote-tracking branch 'origin/v3.0' into feature/pgsql-native-…
renecannao Aug 18, 2026
3e43b35
Merge remote-tracking branch 'origin/v3.0' into feature/pgsql-native-…
renecannao Aug 19, 2026
5cece87
test(tap): sort groups.json entries
renecannao Aug 19, 2026
ea60cba
test(tap): register PgSQL unit tests
renecannao Aug 19, 2026
bafe054
Merge branch 'v3.0' into feature/pgsql-native-backend-protocol
renecannao Aug 19, 2026
810eaba
Merge remote-tracking branch 'origin/v3.0' into feature/pgsql-native-…
renecannao Aug 30, 2026
c516670
Merge remote-tracking branch 'origin/v3.0' into feature/pgsql-native-…
renecannao Aug 30, 2026
1534652
Merge branch 'v3.0_pgsql-auth-5863' into feature/pgsql-native-backend…
rahim-kanji Sep 3, 2026
c5ce37e
Merge branch 'v3.0' into feature/pgsql-native-backend-protocol
renecannao Sep 6, 2026
b9feac1
fix(pgsql): hand the backend TLS transport back when fast forward ends
rahim-kanji Sep 9, 2026
03d1f8a
test(pgsql): cover COPY over a TLS-encrypted backend connection
rahim-kanji Sep 9, 2026
0280699
fix(pgsql): address review on the backend TLS handover
rahim-kanji Sep 9, 2026
9b6a01c
test(pgsql): cover the backend TLS handover and its refusal paths
rahim-kanji Sep 9, 2026
b18da3a
Fixed groups.json sorting
rahim-kanji Sep 9, 2026
782532a
Added more tests
rahim-kanji Sep 10, 2026
4a5e67d
Merge remote-tracking branch 'v3.0' into feature/pgsql-native-backend…
rahim-kanji Sep 10, 2026
f058436
Merge remote-tracking branch 'v3.0_pgsql-copy-tls-handover-6191' into…
rahim-kanji Sep 10, 2026
6888955
Merge remote-tracking branch 'feature/pgsql-native-backend-protocol' …
rahim-kanji Sep 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions common_mk/openssl_flags.mk
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,20 @@ ifeq ($(UNAME_S),Darwin)
LIB_CRYPTO_PATH := $(shell find $(SSL_LDIR) -maxdepth 1 -name "libcrypto.a" 2>/dev/null | head -n 1)
endif
else
LIB_SSL_PATH := $(shell find $(SSL_LDIR) -maxdepth 1 -name "libssl.so*" 2>/dev/null | head -n 1)
# Prefer OpenSSL 3.x when multiple versions coexist in SSL_LDIR
# (a bare libssl.so* first-match can pick a stale 1.1 library);
# fall back to any shared lib, then static.
LIB_SSL_PATH := $(shell find $(SSL_LDIR) -maxdepth 1 -name "libssl.so*3*" 2>/dev/null | head -n 1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This file is explicitly documented as a local build workaround that must not be committed. Remove the added OpenSSL-selection changes from the PR so shared builds do not inherit this machine-dependent library selection.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At common_mk/openssl_flags.mk, line 43:

<comment>This file is explicitly documented as a local build workaround that must not be committed. Remove the added OpenSSL-selection changes from the PR so shared builds do not inherit this machine-dependent library selection.</comment>

<file context>
@@ -37,11 +37,20 @@ ifeq ($(UNAME_S),Darwin)
+        # Prefer OpenSSL 3.x when multiple versions coexist in SSL_LDIR
+        # (a bare libssl.so* first-match can pick a stale 1.1 library);
+        # fall back to any shared lib, then static.
+        LIB_SSL_PATH := $(shell find $(SSL_LDIR) -maxdepth 1 -name "libssl.so*3*" 2>/dev/null | head -n 1)
+        ifeq ($(LIB_SSL_PATH),)
+            LIB_SSL_PATH := $(shell find $(SSL_LDIR) -maxdepth 1 -name "libssl.so*" 2>/dev/null | head -n 1)
</file context>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This file is explicitly documented as a local build workaround that must not be committed. Remove the added OpenSSL-selection changes from the PR so shared builds do not inherit this machine-dependent library selection.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At common_mk/openssl_flags.mk, line 43:

<comment>This file is explicitly documented as a local build workaround that must not be committed. Remove the added OpenSSL-selection changes from the PR so shared builds do not inherit this machine-dependent library selection.</comment>

<file context>
@@ -37,11 +37,20 @@ ifeq ($(UNAME_S),Darwin)
+        # Prefer OpenSSL 3.x when multiple versions coexist in SSL_LDIR
+        # (a bare libssl.so* first-match can pick a stale 1.1 library);
+        # fall back to any shared lib, then static.
+        LIB_SSL_PATH := $(shell find $(SSL_LDIR) -maxdepth 1 -name "libssl.so*3*" 2>/dev/null | head -n 1)
+        ifeq ($(LIB_SSL_PATH),)
+            LIB_SSL_PATH := $(shell find $(SSL_LDIR) -maxdepth 1 -name "libssl.so*" 2>/dev/null | head -n 1)
</file context>

ifeq ($(LIB_SSL_PATH),)
LIB_SSL_PATH := $(shell find $(SSL_LDIR) -maxdepth 1 -name "libssl.so*" 2>/dev/null | head -n 1)
endif
ifeq ($(LIB_SSL_PATH),)
LIB_SSL_PATH := $(shell find $(SSL_LDIR) -maxdepth 1 -name "libssl.a" 2>/dev/null | head -n 1)
endif
LIB_CRYPTO_PATH := $(shell find $(SSL_LDIR) -maxdepth 1 -name "libcrypto.so*" 2>/dev/null | head -n 1)
LIB_CRYPTO_PATH := $(shell find $(SSL_LDIR) -maxdepth 1 -name "libcrypto.so*3*" 2>/dev/null | head -n 1)
ifeq ($(LIB_CRYPTO_PATH),)
LIB_CRYPTO_PATH := $(shell find $(SSL_LDIR) -maxdepth 1 -name "libcrypto.so*" 2>/dev/null | head -n 1)
endif
ifeq ($(LIB_CRYPTO_PATH),)
LIB_CRYPTO_PATH := $(shell find $(SSL_LDIR) -maxdepth 1 -name "libcrypto.a" 2>/dev/null | head -n 1)
endif
Expand Down
16 changes: 16 additions & 0 deletions deps/libscram/include/scram.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ struct ScramState {
uint8_t ClientKey[32]; /* SHA256_DIGEST_LENGTH */
uint8_t StoredKey[32];
uint8_t ServerKey[32];
/* Channel-binding input (SCRAM-SHA-256-PLUS). NULL = plain SCRAM; non-NULL
* means the client-final's c= field is base64(cbind_input) and the gs2
* header in the client-first is "p=tls-server-end-point,,". The state
* takes ownership of a copy allocated in scram_state_set_cbind_input. */
char* client_cbind_input;
int client_cbind_input_len;
};

struct PgCredentials {
Expand Down Expand Up @@ -99,6 +105,16 @@ extern "C" {

bool verify_server_signature(ScramState *scram_state, const PgCredentials *credentials, const char *ServerSignature);

/*
* Sets the channel-binding input that will be used by build_client_first_message
* (gs2 header) and build_client_final_message (c= field). cbind_input must be
* the full "gs2-header || cbind-data" (e.g. "p=tls-server-end-point,," || digest).
* Passing NULL/0 reverts to plain SCRAM (cbind_flag='n', cbind_input freed).
* The state takes ownership of a copy of the input.
*/
void scram_state_set_cbind_input(ScramState* state,
const char* cbind_input, int cbind_input_len);


/*
* Functions for communicating as a server to the client
Expand Down
58 changes: 55 additions & 3 deletions deps/libscram/src/scram.c
Original file line number Diff line number Diff line change
Expand Up @@ -215,13 +215,15 @@ ScramState* scram_state_init() {
scram_state->server_nonce = NULL;
scram_state->server_first_message = NULL;
scram_state->SaltedPassword = NULL;
scram_state->cbind_flag = '\0';
scram_state->cbind_flag = 'n';
scram_state->adhoc = false;
scram_state->iterations = 0;
scram_state->salt = NULL;
memset(scram_state->ClientKey, 0, sizeof(scram_state->ClientKey));
memset(scram_state->StoredKey, 0, sizeof(scram_state->StoredKey));
memset(scram_state->ServerKey, 0, sizeof(scram_state->ServerKey));
scram_state->client_cbind_input = NULL;
scram_state->client_cbind_input_len = 0;
}
return scram_state;
}
Expand All @@ -237,6 +239,7 @@ void free_scram_state(ScramState *scram_state)
free(scram_state->client_final_message_without_proof);
free(scram_state->server_nonce);
free(scram_state->server_first_message);
free(scram_state->client_cbind_input);
free(scram_state->SaltedPassword);
free(scram_state->salt);
memset(scram_state, 0, sizeof(*scram_state));
Expand Down Expand Up @@ -503,7 +506,15 @@ char *build_client_first_message(ScramState *scram_state)
result = malloc(len);
if (result == NULL)
goto failed;
snprintf(result, len, "n,,n=,r=%s", scram_state->client_nonce);
if (scram_state->client_cbind_input != NULL) {
/* Channel-bound client: gs2 cbind flag 'p' with tls-server-end-point
* type. The PostgreSQL convention is an empty SCRAM username (the
* real username travels in the StartupMessage), so the header is
* "p=tls-server-end-point,,". */
snprintf(result, len, "p=tls-server-end-point,,n=,r=%s", scram_state->client_nonce);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When channel binding is enabled, this call writes into a buffer sized for plain SCRAM and truncates the client-first nonce. Size the result from the complete channel-bound format before calling snprintf.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At deps/libscram/src/scram.c, line 514:

<comment>When channel binding is enabled, this call writes into a buffer sized for plain SCRAM and truncates the client-first nonce. Size the result from the complete channel-bound format before calling `snprintf`.</comment>

<file context>
@@ -503,7 +506,15 @@ char *build_client_first_message(ScramState *scram_state)
+		 * type. The PostgreSQL convention is an empty SCRAM username (the
+		 * real username travels in the StartupMessage), so the header is
+		 * "p=tls-server-end-point,,". */
+		snprintf(result, len, "p=tls-server-end-point,,n=,r=%s", scram_state->client_nonce);
+	} else {
+		snprintf(result, len, "n,,n=,r=%s", scram_state->client_nonce);
</file context>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When channel binding is enabled, this call writes into a buffer sized for plain SCRAM and truncates the client-first nonce. Size the result from the complete channel-bound format before calling snprintf.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At deps/libscram/src/scram.c, line 514:

<comment>When channel binding is enabled, this call writes into a buffer sized for plain SCRAM and truncates the client-first nonce. Size the result from the complete channel-bound format before calling `snprintf`.</comment>

<file context>
@@ -503,7 +506,15 @@ char *build_client_first_message(ScramState *scram_state)
+		 * type. The PostgreSQL convention is an empty SCRAM username (the
+		 * real username travels in the StartupMessage), so the header is
+		 * "p=tls-server-end-point,,". */
+		snprintf(result, len, "p=tls-server-end-point,,n=,r=%s", scram_state->client_nonce);
+	} else {
+		snprintf(result, len, "n,,n=,r=%s", scram_state->client_nonce);
</file context>

} else {
snprintf(result, len, "n,,n=,r=%s", scram_state->client_nonce);
}

scram_state->client_first_message_bare = strdup(result + 3);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

When channel binding is enabled (client_cbind_input != NULL), the GS2 header prepended to the client-first message is "p=tls-server-end-point,," which has a length of 24 bytes, unlike the plain SCRAM header "n,," which is 3 bytes. Hardcoding result + 3 here results in an incorrect client_first_message_bare (e.g., "ls-server-end-point,,n=,r=<nonce>" instead of "n=,r=<nonce>"), which will cause the SCRAM signature/proof verification to fail during authentication. We should dynamically adjust the offset based on whether channel binding is active.

\tscram_state->client_first_message_bare = strdup(result + (scram_state->client_cbind_input != NULL ? 24 : 3));

if (scram_state->client_first_message_bare == NULL)
Expand Down Expand Up @@ -532,7 +543,22 @@ char *build_client_final_message(ScramState *scram_state,
uint8_t client_proof[SCRAM_KEY_LEN];
int enclen;

snprintf(buf, sizeof(buf), "c=biws,r=%s", server_nonce);
if (scram_state->client_cbind_input != NULL) {
/* Channel-bound client: c=base64(gs2-header || cbind-data).
* 86 bytes buffer = 22 (header) + 64 (max digest we accept) = 86;
* base64-encoded = 116 chars max. The full prefix
* "c=<b64>,r=<server_nonce>" easily fits in 512. */
char b64[128];
int blen = pg_b64_encode(scram_state->client_cbind_input,
scram_state->client_cbind_input_len,
b64, sizeof(b64));
if (blen < 0)
goto failed;
b64[blen] = '\0';
snprintf(buf, sizeof(buf), "c=%s,r=%s", b64, server_nonce);
} else {
snprintf(buf, sizeof(buf), "c=biws,r=%s", server_nonce);
}

scram_state->client_final_message_without_proof = strdup(buf);
if (scram_state->client_final_message_without_proof == NULL)
Expand Down Expand Up @@ -1418,3 +1444,29 @@ bool scram_verify_plain_password(const char *username, const char *password,
free(prep_password);
return false;
}

/*
* Set the channel-binding input that will be used by build_client_first_message
* (gs2 header selection) and build_client_final_message (c= field composition).
* cbind_input must be the full "gs2-header || cbind-data" blob, e.g.
* "p=tls-server-end-point,," || digest. Passing NULL/0 reverts to plain SCRAM
* (the existing gs2 header "n,," / c="biws" path) and frees any prior input.
* The state owns a private copy allocated with malloc; the caller may free its
* own buffer after the call returns.
*/
void scram_state_set_cbind_input(ScramState *state,
const char *cbind_input, int cbind_input_len)
{
if (state == NULL) return;
free(state->client_cbind_input);
state->client_cbind_input = NULL;
state->client_cbind_input_len = 0;
state->cbind_flag = 'n';
if (cbind_input == NULL || cbind_input_len <= 0) return;
state->client_cbind_input = (char *)malloc((size_t)cbind_input_len + 1);
if (state->client_cbind_input == NULL) return;
memcpy(state->client_cbind_input, cbind_input, (size_t)cbind_input_len);
state->client_cbind_input[cbind_input_len] = '\0';
state->client_cbind_input_len = cbind_input_len;
state->cbind_flag = 'p';
}
32 changes: 32 additions & 0 deletions doc/agents/common-mistakes.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,3 +272,35 @@ gh pr view <PR> --json commits --jq '.commits[].messageHeadline' | grep -i merge
# Unrelated files changed?
gh pr diff <PR> | grep "^+++ b/" | grep -v "<expected_files_pattern>"
```

---

## 16. TAP-Infra Build/Deploy Hazards (debug-only, bind-mounted binary, shared object dir)

**Symptoms** (each observed live, 2026-07): every TAP test fails at `LOAD DEBUG FROM DISK`; the proxysql binary fails to link with `undefined reference to ...run_tests()`; a mid-run test suite aborts with the admin connection dropping for no visible reason; a test run silently exercises last week's code.

**Root causes — four independent traps that compound:**

1. **The TAP harness requires a DEBUG build.** `admin-debug`, `debug_levels`, and `LOAD DEBUG` are `#ifdef DEBUG`-gated; a release binary fails every test at harness init. Build with `make debug -j$(nproc)` (the lib/src sub-makes do NOT inherit parallelism from plain `make debug`).
2. **`lib/obj/*.oo` is shared between release and debug flavors.** Switching flavors without `make clean` produces a mixed archive that fails at link (missing DEBUG-only symbols) or worse. One flavor per checkout; `make clean` when switching — and note `make clean` also deletes every TAP test binary (`make build_tap_test_debug` to restore).
3. **The proxysql container FILE-bind-mounts `$WORKSPACE/src/proxysql`.** Two consequences: (a) after any rebuild you must `docker restart proxysql.<INFRA_ID>` or tests exercise the stale in-memory binary; (b) NEVER rebuild while a test run is live — the linker truncates the mapped inode in place and corrupts the running server's text pages (manifests as inexplicable mid-run connection loss).
4. **Header-touching rebuilds have no dependency tracking.** After editing a widely-included header (enum/struct changes), stale objects can misbehave at runtime rather than fail to build (e.g. a renumbered enum crashing via a stale lookup table). If symptoms look impossible, `make clean` and rebuild before debugging further.

**Prevention:** one build flavor per checkout (use a separate worktree for the other flavor); rebuild → `docker restart` → then test, never overlapping; treat "impossible" runtime behavior after header edits as a stale-object signal first.

---

## 17. Shared/Misprovisioned Test Infra (INFRA_ID collisions, INFRA type-vs-id, stale containers)

**Symptoms:** tests fail with `Unknown global variable` for a variable your branch definitely has; `FATAL: User not found` on every pgsql connection; backend hostname `pgsql1.<something>` unresolvable; another session's binary appears in your container.

**Root causes:**

1. **`INFRA_ID` is a shared namespace.** The conventional `INFRA_ID=dev-$USER` collides across worktrees/sessions of the same user: `ensure-infras.bash` reuses a running `proxysql.<INFRA_ID>` container even if it is bind-mounted from a *different worktree*. Use a per-effort id (e.g. `dev-$USER-<feature>`).
2. **`INFRA` is the infra TYPE, not the infra id.** Scripts like `docker-pgsql16-single/bin/docker-proxy-post.bash` build the backend hostname as `pgsql1.${INFRA}` — the resolvable compose alias is `pgsql1.docker-pgsql16-single`. Hand-exporting `INFRA=<your INFRA_ID>` writes an unresolvable hostname into the persisted admin DB, which then survives container recreation.
3. **`docker start` on an exited proxysql container skips provisioning.** The users/servers config is applied by the post scripts, not baked into the container; recreate via `ensure-infras.bash` / `start-proxysql-isolated.bash`, never bare `docker start`.
4. Hand-running post scripts needs the full env: `COMPOSE_PROJECT`, `INFRA` (type), `ROOT_PASSWORD=$(echo -n "$INFRA_ID" | sha256sum | head -c 10)`, run from the infra directory (they source `./constants`).

**Detection:** `docker inspect proxysql.<INFRA_ID> --format '{{range .Mounts}}{{.Source}}{{"\n"}}{{end}}'` shows whose worktree the binary comes from; `SELECT hostname FROM pgsql_servers` via admin shows a poisoned backend hostname.

**Useful:** `TEST_PY_TAP_INCL=<regex>` runs a subset through `run-tests-isolated.bash` without the full group.
Loading
Loading