Skip to content

docs(db): side_effects_at IS readable by clients — the last migration said otherwise - #825

Merged
catomean merged 1 commit into
mainfrom
fix/correct-side-effects-grant-claim
Aug 28, 2026
Merged

docs(db): side_effects_at IS readable by clients — the last migration said otherwise#825
catomean merged 1 commit into
mainfrom
fix/correct-side-effects-grant-claim

Conversation

@catomean

Copy link
Copy Markdown
Collaborator

#823 added side_effects_at and its COMMENT asserted "Server-side only: no client SELECT grant". That is false, and production proves it: anon holds SELECT on the column.

What I got wrong

I read payment_intents as having the same grant shape as wallets. It doesn't, and the difference is exactly what the drift guard from #821 exists for:

table grant shape a column added later is…
wallets explicit column-list (GRANT SELECT (cols)) unreadable until granted
payment_intents table-level readable automatically

What misled me: counting 21 rows in information_schema.column_privileges. That view expands a table-level grant per column, so it looks identical to an explicit column list — and has_table_privilege doesn't distinguish them either. The honest test is whether the grant was written as GRANT SELECT (cols).

The exposure is fine — now deliberately, not accidentally

A settlement-completion timestamp is not a secret, and RLS still decides which intents a caller may see at all. Withholding it would mean rebuilding the entire table's grants as a column list — the surgery the wallets lockdown performed, for a reason that doesn't apply to a timestamp.

Why a new migration rather than an edit

20260828220000 has already run, so its COMMENT would never be re-issued and production would keep the false text. Never edit an applied migration — the repo's own rule, and here it's also the only thing that actually fixes the database.

Migration versions unique.

… said otherwise

20260828220000 added the column and its COMMENT asserted "Server-side only: no
client SELECT grant". False, and production proves it: anon holds SELECT.

I read payment_intents as having the same grant shape as wallets. It does not,
and the difference is exactly the thing the wallets drift guard exists for:

  * wallets carries an explicit COLUMN-LIST grant, rebuilt that way so
    nwc_connection_uri could be excluded — so a column added later is
    unreadable until someone grants it.
  * payment_intents holds TABLE-level grants, which automatically cover every
    column added afterwards.

What misled me was counting 21 rows in information_schema.column_privileges:
that view expands a table-level grant per column, so it looks identical to an
explicit column list, and has_table_privilege does not distinguish them either.
The honest test is whether the grant was written as `GRANT SELECT (cols)`.

The exposure is acceptable and now says so deliberately rather than by
accident: a settlement-completion timestamp is not a secret, and RLS still
decides which intents a caller sees. Withholding it would mean rebuilding the
whole table's grants as a column list — the surgery the wallets lockdown did,
for a reason that does not apply to a timestamp.

A new forward migration rather than an edit to the applied one: 20260828220000
has already run, so its COMMENT would never be re-issued and production would
keep the false text.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018waGt1ieA9TjpscqrbrnGb
@catomean
catomean merged commit c6ea3d3 into main Aug 28, 2026
8 checks passed
@catomean
catomean deleted the fix/correct-side-effects-grant-claim branch August 28, 2026 21:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant