Skip to content

fix(api): scope receipt access to owning tenant; RLS remediation#177

Open
chrismaz11 wants to merge 2 commits into
masterfrom
security/rls-remediation-tenant-scoping
Open

fix(api): scope receipt access to owning tenant; RLS remediation#177
chrismaz11 wants to merge 2 commits into
masterfrom
security/rls-remediation-tenant-scoping

Conversation

@chrismaz11

Copy link
Copy Markdown
Collaborator

Summary

Application-code half of the RLS remediation workstream on the 10 previously RLS-disabled tables. Enforces tenant ownership on receipt routes and corrects a docs note. Backend-only RLS policies were applied separately via Supabase migrations (not in this diff).

Cross-tenant read-exposure fix

Previously these routes resolved a receipt by id alone, exposing any tenant's receipt (and the large pool of legacy null-owner rows) to any authenticated key. Now gated by callerOwnsReceipt(authUserId, ownerUserId) (matches authContext.userIdapi_keys.user_id against Receipt.userId; null/undefined either side = no match), with owner loaded via raw SQL since Receipt.userId isn't on the Prisma model. Not-found and not-owned both return 404 so existence isn't leaked across tenants:

  • GET /api/v1/receipt/:id
  • GET /api/v1/receipt/:id/pdf
  • POST /api/v1/receipt/:id/verify
  • POST /api/v1/anchor/:id

GET /api/v1/receipts rewritten to list only the caller's own receipts (empty list for a null identity). POST /receipt/:id/revoke is intentionally unchanged — it already requires an issuer signature under its own trust model.

Tests

apps/api/src/receiptAccess.test.ts — 7 unit tests over callerOwnsReceipt: own-read, cross-tenant, null-owner, null/undefined/empty-string callers.

Docs

Corrected the CLAUDE.md hashing note: the receipt hash and inputs commitment are keccak256 (ethers), not SHA-256; SHA-256 is used only for zkp/zkml digests and ATTOM normalization.

Test plan

  • npm -w apps/api exec -- vitest run src/receiptAccess.test.ts (7 pass)
  • CI: lint + typecheck + full test + build

🤖 Generated with Claude Code

Enforce tenant ownership on receipt routes so an API key can only access
its own tenant's receipts. Previously these routes resolved a receipt by
id alone, exposing any receipt (and the large pool of legacy null-owner
rows) to any authenticated key.

Add `callerOwnsReceipt(authUserId, ownerUserId)` — a pure decision that
matches a caller's resolved tenant identity (authContext.userId, from
api_keys.user_id) against Receipt.userId, treating null/undefined on
either side as no-match — and `loadReceiptOwner()`, a raw-SQL owner lookup
(Receipt.userId is not on the Prisma model). Gate these routes; both
not-found and not-owned return 404 so existence is not leaked across
tenants:
  - GET  /api/v1/receipt/:id
  - GET  /api/v1/receipt/:id/pdf
  - POST /api/v1/receipt/:id/verify
  - POST /api/v1/anchor/:id
Rewrite GET /api/v1/receipts to list only the caller's own receipts
(empty list for a null identity). POST /receipt/:id/revoke is unchanged:
it already requires an issuer signature under its own trust model.

Add receiptAccess.test.ts: 7 unit tests over callerOwnsReceipt covering
own-read, cross-tenant, null-owner, null/undefined/empty-string callers.

Docs: correct the CLAUDE.md hashing note — receipt hash and inputs
commitment are keccak256 (ethers), not SHA-256; SHA-256 is used only for
zkp/zkml digests and ATTOM normalization.

This is the application-code half of the RLS remediation; the backend-only
RLS policies on the 10 previously RLS-disabled tables (tenants + 9 dormant)
were applied separately via Supabase migrations.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@vercel

vercel Bot commented Jun 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
api Ready Ready Preview, Comment Jun 22, 2026 4:01pm
trustsignal Ready Ready Preview, Comment Jun 22, 2026 4:01pm

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Replace additive bucket accumulation with probabilistic OR scoring
(1 − Π(1 − wᵢ)) so multiple weak signals accumulate without a single
moderate signal saturating the score. Extracts the scoring logic into
scoring.ts and refactors types/forensics/layout/patterns to carry
per-signal weights. Adds documentExtraction.ts in apps/api to parse PDF
buffers (text, geometry, structural tokens) into the ExtractedDocument
the core RiskEngine consumes. Updates CLAUDE.md with accurate stub/prod
caveats for risk, ZK, and ZKML modules.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
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