Skip to content

test(wallets): the column list and the GRANT that makes it readable can drift - #821

Merged
catomean merged 1 commit into
mainfrom
fix/wallet-column-grant-drift
Aug 28, 2026
Merged

test(wallets): the column list and the GRANT that makes it readable can drift#821
catomean merged 1 commit into
mainfrom
fix/wallet-column-grant-drift

Conversation

@catomean

Copy link
Copy Markdown
Collaborator

Closes finding 5 of #563.

Migration 20260802120000 revoked table-level SELECT on wallets and replaced it with an explicit column grant, so nwc_connection_uri — an encrypted wallet-spending credential — is write-only for client roles. Every non-service client therefore selects WALLET_CLIENT_COLUMNS, the code-side mirror of that grant.

Two sources of truth for one fact, and nothing enforced the match.

Why it bites harder than it looks

Postgres does not auto-grant a column added later. So: add a column to wallets, add it to WALLET_CLIENT_COLUMNS because your query needs it, and every wallet read on the site starts failing with 42501 permission-denied for anon/authenticated — not just the new feature.

It presents as a broken page rather than as a missing grant, which is how #561 was spent finding it once already.

What it asserts

  1. the GRANT list and the TS constant name the same columns;
  2. neither ever names a secret column;
  3. any column a later migration adds to wallets is granted somewhere, or declared secret.

Verified against production, not assumed

anon currently holds SELECT on exactly the 25 columns in the constant; the table has 26; the one withheld is nwc_connection_uri.

Proven to fail in both directions

mutation result
add a column to the TS constant only reported under codeOnly
a later migration adds a column, no grant names sweep_key
restored 8/8 pass

Assertion 3 currently scans a set with nothing in it — no migration adds a wallets column yet — so it logs how many it scanned and how many it found. A silent pass over an empty set reads exactly like a pass over a checked one. Five fixture tests prove the parsers detect rather than quietly matching nothing, including a GRANT on a different table and an ADD COLUMN on a different table.

type-check green; 8 tests pass.

Also checked while here

Finding 6 is already fixed — and better than the audit proposed. redactExtendedKeys (added 2026-08-24, after the audit) classifies by value rather than the user-set wallet_type label, so a key pasted into a row typed onchain is still redacted. Verified live: an anonymous request for a profile whose wallet genuinely holds an extended key returns address_or_xpub: null.

Finding 3 is substantially closed by #819. notifyRecipientOfClaim fires only on the transition into buyer_confirmed, never on idempotent re-claims — so the notification count is bounded by intent count, and #819 capped intent creation at 20 per 5 minutes per recipient. The unbounded primitive is gone.

…an drift

Migration 20260802120000 revoked table-level SELECT on wallets and replaced it
with an explicit COLUMN grant, so nwc_connection_uri — an encrypted
wallet-spending credential — is write-only for client roles. Every non-service
client therefore selects WALLET_CLIENT_COLUMNS, the code-side mirror of that
grant. Two sources of truth for one fact, and nothing enforced the match.

Postgres does not auto-grant a column added later. So: add a column to wallets,
add it to WALLET_CLIENT_COLUMNS because your query needs it, and every wallet
read on the site starts failing with 42501 permission-denied for
anon/authenticated — not just the new feature. It presents as a broken page
rather than as a missing grant, which is how #561 was spent finding it once
already. #563 finding 5.

Three assertions: the GRANT list and the TS constant name the same columns;
neither ever names a secret column; and any column a LATER migration adds to
wallets is granted somewhere or declared secret.

Verified against production rather than assumed — anon currently holds SELECT on
exactly the 25 columns in the constant, the table has 26, and the one withheld
is nwc_connection_uri.

Proven to fail in both directions before shipping. Adding a column to the TS
constant alone reports it under codeOnly; a later migration adding a column with
no grant names it. Restored, all 8 pass.

The third assertion currently scans a set with nothing in it — no migration adds
a wallets column yet — so it logs how many it scanned and how many it found. A
silent pass over an empty set reads exactly like a pass over a checked one, and
five fixture tests prove the parsers detect rather than quietly matching
nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018waGt1ieA9TjpscqrbrnGb
@catomean
catomean merged commit 4576d9f into main Aug 28, 2026
6 checks passed
@catomean
catomean deleted the fix/wallet-column-grant-drift branch August 28, 2026 20:28
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