Repository navigation
Log masking fails closed: a DETAIL or CONTEXT is read as SQL only where PostgreSQL writes SQL, a value cut before its close is masked to the end, and get_store_query_stats masks a raw text pg_stat_statements kept (#3920 follow-up) - #3952
Merged
Merged
Conversation
…nclosed values masked, bounded time, raw pgss text masked (#3920 follow-up) A fourth security review of #3920 found five Medium issues and three Low: - M1 (round-3 regression): DETAIL/CONTEXT were split at any head-shaped line, even one inside a value. RedactDetail now reads SQL only in a deadlock report (a Process N: split needs one of the deadlock's own PIDs and a query that reads to its end), a crash report, or a logged EXECUTE's prepare: (L3). Everything else is prose. RedactContext reads an SQL frame only on line 0, after a closed SQL frame, or after a one-line frame with no value quote open. - M2 (round-3 regression): an identifier holding a non-ASCII non-letter (NBSP, full-width digits, zero-width marks) is masked. A quoted run after a noun is kept only when it reads as a name. - M3: fields over 64 KB are withheld, and a frame or split tests at most 32 boundaries. The pre-existing exclusion-pair key pattern was a ReDoS (7.2 s on 27 KB, minutes on 90 KB); its keys are now atomic (19 ms). Prose patterns carry a 500 ms timeout that withholds the text. - M4 (pre-existing): every prose value shape closes only where PostgreSQL's sentence closes it, and otherwise masks to the end. The key tuple matches [Kk]ey. - M5 (pre-existing, confirmed in PG 18 source and reproduced live): pg_stat_statements stores raw text when an entry is gone at executor end, so get_store_query_stats now masks every text it shows. The lexer keeps PG 18's IN-list marker. - L1: LOCATION's own line is kept only in PostgreSQL's function, file.c:N shape. The head rule and L2 (prose keeps unquoted values such as RAISE text) are a maintainer decision: #3944. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Z4T51J4E6L8fjriPHpsif
erikdarlingdata
enabled auto-merge (squash)
September 23, 2026 00:39
erikdarlingdata
added a commit
that referenced
this pull request
Sep 23, 2026
…3920, #3927, #3931, #3932, #3940, #3942, #3946, #3947, #3950, #3952, #3955, #3956, #3957, #3964, #3965, #3966, #3968, #3972, #3975, #3979, #3980, #3981, #3983, #3984, #3985) (#3989) The wave's fix PRs deliberately carried no CHANGELOG edits (parallel-agent hot-spot protocol); each agent reported its entry and this commit lands them together, byte-verified against origin/dev. Claude-Session: https://claude.ai/code/session_01GdmA4ND1wLSqA91ax1m4xv Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #3920 (merged as bbf3e73). A fourth security review of #3920's last round found no Critical or High issues, five Medium and three Low. Every Medium and two of the Lows are fixed here; the remaining design questions are in #3944.
CHANGELOG: this PR carries no CHANGELOG edit. Its entry is in the batched splice.
Why
Threat model: a viewer or mcp role user, or an LLM agent calling
get_store_log,get_pg_log_eventsorget_store_query_stats, reads literal values out of other sessions' SQL. The store log keeps them 400 days,pg_log_events30.Each finding below was reproduced as a failing test before it was fixed: all 7 new tests failed on dev.
What changes
M1: SQL is read only where PostgreSQL writes SQL (a regression from round 3).
Process N:orFailed process was running:, even inside a value. A literal holding numbered lines ("Process 1: call Jane Roe at jane@...") kept its middle as identifiers. A key tuple or failing row cut at such a line lost the close its prose shape needs, and kept the SSN.RedactDetailnow reads SQL only in three shapes:Process N:line starts the next query only when N is one of the deadlock's own processes AND the query before it reads to its end.Failed process was running:, one query to the end of the field.prepare:(L3), one query to the end of the field.RedactContext, an SQL frame is read as SQL only in three places, and never while an earlier value's quote is open:PL/pgSQL function ...,while ... in relation ...,JSON data, line N: ...).SQL statement "..."is its own content, not SQL.M2: a non-ASCII non-letter no longer keeps a value (a regression from round 3).
=<NBSP>4111...is one identifier, and full-width digits and zero-width marks behave the same. All three are how a value pasted from a web page or typed through an IME reads.café,名前) is still a name.column "4111..."is masked now.M3: masking a hostile field is bounded.
Key (a)=(and 3.7 minutes on 90 KB. Its key is now an atomic group: 19 ms. The key tuple and partition key got the same change.<text withheld: it could not be masked in time>.M4: prose value shapes fail closed (predates #3920).
: "…"/at or near "…"now closes only at a quote that ends the text, or that the log'sat character Nfollows. Before,json: "{"card": 4111closed at the key's quote and kept the number.already exists,is duplicated,is still referenced,is not present,conflicts with) or the end. It matches[Kk]ey, so the exclusion violation's second tuple is covered too.)that ends the text.M5:
get_store_query_statsmasks what it shows (predates #3920).pg_stat_statements.c: when a statement's entry is gone at executor end,pgss_storeruns with no jumble state and storesnorm_query ? norm_query : query, the raw text. That happens when the entry was evicted during a long execution, or reset between a protocol Parse and its Execute.pg_stat_statements_reset(), then Execute storesSELECT 'hunter2-m5b'::text AS pw, $1::int AS n. That passes the reader's allowlist.config.store_statement_stats(), the viewer or mcp role with its own credentials, still reads the raw text in that race. It already reads every collected table.L1: LOCATION. Its own line is kept as written only in PostgreSQL's shape (
function, file.c:N), and is masked as prose otherwise. The head rule for foreign lines is in #3944.L3:
prepare:is a query head (see M1).Not fixed here, needs a decision: #3944. Prose keeps values PostgreSQL didn't quote: RAISE text (P0001 / class P0), libxml DETAIL source lines, a typed value after a noun, and a foreign stderr line's head. Withholding RAISE text on monitored targets trades the application's own error messages for privacy.
Test plan
ADetailIsSqlOnlyInTheShapesPostgresWritesSqlIntoandAContextFrameIsSqlOnlyWhereAFrameCanStart.SQL statement "..."or a fake PL/pgSQL frame.prepare:and crash heads.ANonAsciiSpaceOrDigit_NeverKeepsAValue: NBSP, full-width digits and zero-width mark in statements;column "<NBSP>4111"andcolumn "4111"in prose. Non-ASCII letters are kept.MaskingAHostileField_StaysLinear, each under 2 s:Key (,Key (a)=(, partition keys, a JSON value andcolumn ".AValueShapeWithoutItsClose_IsMaskedToTheEnd(10 shapes, each idempotent), and through the store log's own capture inAnUnclosedValueAndAForeignLocation_AreMaskedWithTheEntry.OnTheProductsOwnConf_...: a prepared Npgsql command,pg_stat_statements_reset(), then Execute. The view holdsRawLiteral3920verbatim; the tool's full text and preview do not, and show'?'::text AS pgss_raw_marker.TheToolMasksEveryTextItShows_AndLeavesNormalizedTextAsItWas.Darling.Testsran on a pgss-preloaded rig withDARLING_TEST_PGandDARLING_TEST_PGRUNTIME; fullLite.Teststoo (numbers in the thread).🤖 Generated with Claude Code
https://claude.ai/code/session_018Z4T51J4E6L8fjriPHpsif