← all branches

feat/one-api

needs attentionviewing older commit
5ed593d · incrementalpre-PRreviewed 2026-08-03 21:32 UTC1H · 6M · 6L · 5I
The branch
Purpose
Extend database-enforced tenant isolation (RLS) to the asset-management public-v1 surface, completing the conversion of all org-scoped reads on the branch (42 total across bills, files, jobs, monitoring, payment-status, savings, and now asset-management).
Goal
Every public-v1 read that touches an org-scoped table runs inside a readInOrg transaction (two independent isolation controls: app filter + RLS); global catalog reads stay on service-role
Sub-goals
  • SG-1: Wrap 26 asset-management org-scoped reads in readInOrg
  • SG-2: Leave global catalog reads (makes, metric types, integrations) on service-role
  • SG-3: Add positive-path IDOR assertions for asset-management
  • SG-4: Tighten readInOrg callback type to Transaction
  • SG-5: Update one-api.md doc with 42-site count and SQL diagnostic
The changes (whole branch)
What
asset-management.handler.ts: 26 reads wrapped in readInOrg; large reformatting (single→double quotes); stale comments removed; enrichment reads left on bare db. public-v1-rls.ts: Transaction type import, simplified readInOrg callback type. public-v1-idor.test.ts: +39 lines positive-path assertions. .claude/rules/one-api.md: updated count and SQL.
Why
Machine callers (API keys) have no Supabase session so auth.uid() is null; before this branch org isolation relied solely on hand-written WHERE org_id = ? filters. The 0069 migration enabled batu.caller_org_id as machine-readable RLS claim for two independent controls.
Areas
apps/platform/src/api/handlers/public-v1+1172779apps/platform/src/__tests__/integration+390apps/platform/src/api/utils+140.claude/rules+184
Blast
4 files, +1243/−783 net; no schema/contract change. Affects public-v1 asset-management reads only.
RLS public-api security
ci· No GH auth availablecoderabbit· No .coderabbit.yamlcorrectnesssecurity· 2 medium findings on enrichment RLS bypassconventions· 1 high (stale comment), 2 medium (listMetricSources)tests· 2 medium: vacuous positive tests, coverage gapimprovement· 2 medium suggestions (batching, perf)

Findings · 18

correctness2

info

Redundant cross-org check after RLS — correct defense-in-depth

apps/platform/src/api/handlers/public-v1/asset-management.handler.ts:1063

RLS already returns null for cross-org rows; explicit orgId check is redundant but intentional per two-controls rule.

info

Bare database for metric_sources enrichment is safe under current schema invariants

apps/platform/src/api/handlers/public-v1/asset-management.handler.ts:766

IDs used in enrichment come from prior org-scoped reads; no attacker-controlled injection. Single-control, not zero.

security4

medium

metric_streams + assets enrichment reads bypass RLS (findByIds lines 1418-1419)

apps/platform/src/api/handlers/public-v1/asset-management.handler.ts:1418

Both are org-scoped tables (get_user_org_ids policy). No current IDOR path since IDs come from prior RLS-gated call, but bypasses the second control this PR establishes. Comment misclassifies as 'admin-required'.

medium

metric_sources enrichment bypass in asset-get and stream-get (lines 766, 1074)

apps/platform/src/api/handlers/public-v1/asset-management.handler.ts:766

listMetricSources(database, {orgId, assetId/metricStreamId}) uses bare db. list endpoint at 1394 correctly wraps same call. Inconsistent.

low

IDOR probe uses synthetic IDs for asset/stream/source get — no real cross-org probe

apps/platform/src/__tests__/integration/public-v1-idor.test.ts:432

tier: 'synthetic' proves invalid IDs return 404 (trivially true). Real cross-org probe absent for these 3 endpoints.

info

Double-check pattern after readInOrg is correct defense-in-depth

apps/platform/src/api/handlers/public-v1/asset-management.handler.ts:1063

RLS would already return null for cross-org rows; explicit check is belt-and-suspenders per the two-controls rule.

conventions4

high

Stale 'RLS is a no-op' comment contradicts readInOrg at line 695

apps/platform/src/api/handlers/public-v1/asset-management.handler.ts:693

Comment describes pre-fix state. Future reader will think RLS is absent. Should describe WHY readInOrg is used.

medium

listMetricSources on bare database at lines 766 and 1074 — org-scoped table without readInOrg

apps/platform/src/api/handlers/public-v1/asset-management.handler.ts:766

Per one-api.md: org-scoped tables (get_user_org_ids policy) must use readInOrg. The list endpoint wraps the same call; detail handlers don't. Comment taxonomy wrong.

low

'admin-required' label ambiguous for org-scoped tables post-migration

apps/platform/src/api/handlers/public-v1/asset-management.handler.ts:962

Post one-api.md: 'admin-required' = admin-gated (api_keys, webhooks, invitations). Org-scoped enrichment reads should use different label.

info

Quote style (single→double) aligns with Prettier defaults but diverges from sibling handlers

apps/platform/src/api/handlers/public-v1/asset-management.handler.ts:1

Cosmetic diff noise until sibling handlers normalised.

tests5

medium

Positive list tests accept empty 200 — no seeded assets/streams/sources

apps/platform/src/__tests__/integration/public-v1-idor.test.ts:661

Vacuous: a broken readInOrg returning empty would still pass. Only sitesList/siteGet have real assertions.

medium

IDOR probe uses site-filtered path; positive test uses unfiltered path — coverage gap

apps/platform/src/__tests__/integration/public-v1-idor.test.ts:669

Probe: {query: {site: orgB.sitePublicId}}. Positive: {query: {}}. Neither covers: positive with filter OR IDOR negative with no filter.

low

Site created by assetMgmtSiteCreate IDOR probe (org A) not tracked in cleanupSiteIds

apps/platform/src/__tests__/integration/public-v1-idor.test.ts:497

Accumulates in DB on repeated runs. afterAll only cleans orgB.sitePublicId.

low

sitesList assertion uses JSON.stringify toContain — fragile

apps/platform/src/__tests__/integration/public-v1-idor.test.ts:651

Passes if public ID appears anywhere in body string. Prefer checking result.body.data array.

info

Shared beforeAll/afterAll between describe blocks — no ordering risk

apps/platform/src/__tests__/integration/public-v1-idor.test.ts:125

Both IDOR and positive-path suites read shared fixture; no mutation between tests.

improvement3

medium

Two sequential readInOrg in stream-get/stream-update — 2 tx where 1 would do

apps/platform/src/api/handlers/public-v1/asset-management.handler.ts:1053

Stream + site could share one readInOrg with Promise.all, halving round-trips.

low

Streams-list loads all org sites into memory to build siteId filter

apps/platform/src/api/handlers/public-v1/asset-management.handler.ts:963

listStreams could accept orgId directly and join to sites internally.

low

Explore handler fetches sites one-at-a-time via bare database after org-scoped stream reads

apps/platform/src/api/handlers/public-v1/asset-management.handler.ts:605

N queries on service-role; should batch via findByIds inside readInOrg.

History · 47 commits

  1. 82bb5b9blockedincremental5H · 5M · 4L2026-08-12 01:48
  2. 90aa3d5needs attentionincremental1H · 5M · 3L2026-08-11 19:37
  3. 29d19a0needs attentionincremental1H · 5M · 9L2026-08-11 17:41
  4. 9bd8a0cneeds attentionfull0H · 5M · 9L2026-08-11 02:14
  5. 62ec3f7needs attentionincremental2H · 5M · 6L2026-08-10 22:51
  6. f93bca9needs attentionincremental2H · 5M · 8L2026-08-10 17:51
  7. 052db6fneeds attentionincremental1H · 3M · 4L2026-08-09 21:13
  8. 45699caneeds attentionincremental0H · 7M · 11L2026-08-09 17:44
  9. b843d8aneeds attentionincremental1H · 7M · 9L2026-08-09 04:05
  10. e1757b8needs attentionincremental0H · 3M · 6L2026-08-05 02:11
  11. 7a762faneeds attentionincremental2H · 5M · 5L2026-08-05 01:25
  12. 3300a60needs attentionincremental2H · 4M · 7L2026-08-04 19:06
  13. 0c8a7f5needs attentionincremental0H · 4M · 9L2026-08-04 18:15
  14. 345f42eneeds attentionincremental2H · 6M · 9L2026-08-04 17:28
  15. 8338a9aneeds attentionincremental5H · 14M · 14L2026-08-04 00:33
  16. 41be4c3needs attentionincremental0H · 5M · 7L2026-08-03 23:49
  17. 5ed593dneeds attentionincremental1H · 6M · 6L2026-08-03 21:32current
  18. b333e25needs attentionincremental4H · 9M · 8L2026-08-03 21:00
  19. 5642cccneeds attentionincremental2H · 3M · 2L2026-08-03 20:17
  20. 73b0b39needs attentionincremental3H · 10M · 13L2026-07-31 18:29
  21. b19852eneeds attentionincremental0H · 1M · 5L2026-07-29 05:04
  22. 3845205needs attentionincremental3H · 6M · 4L2026-07-29 04:47
  23. eb8eb50needs attentionincremental0H · 1M · 2L2026-07-29 03:03
  24. f4720a3needs attentionincremental6H · 8M · 7L2026-07-29 02:54
  25. f8d341ablockedincremental2H · 2M · 5L2026-07-29 00:00
  26. a7f1a64needs attentionincremental2H · 8M · 8L2026-07-28 18:41
  27. 738b60bblockedincremental3H · 6M · 5L2026-07-28 00:46
  28. 2c248b6needs attentionincremental8H · 12M · 8L2026-07-27 23:23
  29. 1346cc0needs attentionincremental2H · 8M · 6L2026-07-27 20:15
  30. 0716018needs attentionincremental2H · 11M · 12L2026-07-27 19:22
  31. 215cd2dneeds attentionincremental3H · 6M · 5L2026-07-27 17:04
  32. ec46958needs attentionincremental0H · 3M · 5L2026-07-27 16:51
  33. de7b337blockedincremental4H · 9M · 14L2026-07-27 06:36
  34. b1bb9c0needs attentionincremental1H · 2M · 4L2026-07-27 05:09
  35. 4701d11needs attentionincremental0H · 4M · 3L2026-07-27 04:44
  36. e1626c4needs attentionincremental3H · 9M · 10L2026-07-27 03:21
  37. 195f198needs attentionincremental3H · 3M · 3L2026-07-25 01:22
  38. 42c7358safeincremental0H · 0M · 0L2026-07-22 20:46
  39. 85b9018needs attentionincremental0H · 1M · 6L2026-07-21 23:51
  40. a7b2a9aneeds attentionincremental0H · 9M · 12L2026-07-21 18:49
  41. c2ee0daneeds attentionincremental4H · 7M · 7L2026-07-21 02:17
  42. e8ffa5eneeds attentionincremental4H · 7M · 5L2026-07-21 01:33
  43. a2d2a54needs attentionincremental2H · 7M · 3L2026-07-21 00:51
  44. 576fbd6needs attentionfull1H · 6M · 7L2026-07-21 00:35
  45. d3465e8needs attentionincremental1H · 7M · 10L2026-07-21 00:23
  46. dc794a7needs attentionincremental0H · 5M · 5L2026-07-20 23:46
  47. 9082773needs attentionfull1H · 3M · 3L2026-07-20 23:13