← all branches

feat/one-api

needs attentionviewing older commit
052db6f · incrementalpre-PRreviewed 2026-08-09 21:13 UTC1H · 3M · 4L · 6I
The branch
Purpose
Unified public API layer with machine-caller RLS isolation, telemetry pipeline, and webhook removal
Goal
Harden the public v1 surface: machine-claim one-way door, telemetry persistence, accessible-contracts optimization, x-real-ip, webhook cleanup
Sub-goals
  • SG-1: Machine-caller RLS isolation — get_user_org_ids identity gate (0075)
  • SG-2: Telemetry pipeline — recordApiRequest awaited, Tinybird sink tested, api_request_events index
  • SG-3: Accessible-contracts one-query optimization — two-hop eliminated
  • SG-4: Security hardening — x-real-ip, constantTimeEquals, TRUNCATE revoke
  • SG-5: Webhook subsystem removal — exports, schemas, test matrix
  • SG-6: Test coverage — wizard rollback, Tinybird sink, machine-claim, telemetry persistence
The changes (whole branch)
What
Sweep acting on PR #389 review round 2: 0075 migration (identity gate + TRUNCATE revoke), listContractPublicIdsByOrgId (one-query), recordApiRequest awaited in tests, Tinybird sink fully tested, wizard rollback asserted, x-real-ip preferred in OAuth route, constantTimeEquals for static key, webhook exports purged
Why
Seven medium findings from round-2 review closed: spoofable throttle key, machine-claim one-way door, telemetry deferred-write regression risk, Tinybird sink untested, rollback unasserted, timing-unsafe key comparison, two-hop site marshalling at scale
Areas
packages/database/drizzle+6035apps/platform/src/__tests__+11020apps/platform/src/api+4515domains+4020packages/api/src+3025.claude/rules + .github+1510
Blast
24 files, ~290 adds / ~120 dels across DB migrations, integration tests, API middleware, domain queries, package exports. No breaking schema changes.
security-hardening test-coverage db-migration webhook-removal
ci· no PR — push-triggered review, no CI rollup availablecoderabbit· no .coderabbit.yaml in repo

Findings · 16

correctness3

medium

listContractPublicIdsByOrgId returns soft-deleted utility contracts — no deletedAt IS NULL filter

domains/utility/src/site-utility-contract/site-utility-contract.queries.ts:231

Pre-existing gap carried from old two-hop path. Add isNull(utilityContracts.deletedAt) to the WHERE clause.

low

get_user_org_ids() evaluates current_setting('request.jwt.claims') twice — CTE would eliminate double parse

packages/database/drizzle/0075_machine_claim_hardening.sql:41

Functionally correct (STABLE); the duplicate expression risks drift if one occurrence is edited without the other. A WITH clause would make the value appear once.

info

Auth handler profile-insert deviation confirmed resolved — canonical-form note removal is accurate

apps/platform/src/api/handlers/auth.handler.ts:257

Handler now calls ProfileFCIS.ensureProfileForAuthShell(). Stale deviation note correctly retired.

security4

medium

Main v1 mount rate-limiter still uses spoofable x-forwarded-for[0] — fix applied only to OAuth token route

apps/platform/src/app/api/v1/[...ts-rest]/route.ts:91

x-real-ip preference correctly applied to oauth/token/route.ts. The main mount's rateLimitBucket() for unauthenticated requests still reads x-forwarded-for[0]. Apply the same x-real-ip ?? x-forwarded-for[0] precedence.

low

Non-empty invalid JSON in request.jwt.claims GUC raises unhandled exception in get_user_org_ids() (pre-existing, 0075 inherits gap)

packages/database/drizzle/0075_machine_claim_hardening.sql:38

nullif handles empty string → NULL, but any non-empty non-JSON value causes the ::json cast to raise, crashing all 88 RLS policies for that request. Not directly attacker-controllable from the public API. Inherited from 0069.

info

Machine-claim identity gate (0075) verified correct — auth.uid() IS NULL makes human and machine branches mutually exclusive

packages/database/drizzle/0075_machine_claim_hardening.sql:1

Fix is correct. CI ratchet on custom_access_token_hook definition text provides defense-in-depth.

info

No new IDOR risk in listContractPublicIdsByOrgId — orgId sourced from verified token, not caller input

packages/api/src/auth/accessible-contracts.ts:71

Permission boundary unchanged; query consolidation only.

conventions2

high

Orphaned docblock misattributes 'List contract public_ids for a set of sites' to listContractPublicIdsByOrgId

domains/utility/src/site-utility-contract/site-utility-contract.queries.ts:214

New function inserted between existing JSDoc for listContractPublicIdsBySiteIds and that function. listContractPublicIdsByOrgId now has two docblocks (wrong + right); listContractPublicIdsBySiteIds has zero. Move the first docblock down.

low

Second docblock on listContractPublicIdsByOrgId leads with SQL mechanics (WHAT) rather than WHY

domains/utility/src/site-utility-contract/site-utility-contract.queries.ts:218

Trim 'One-query org→contract resolution: SELECT DISTINCT…'; keep the performance reasoning and DISTINCT justification.

tests4

medium

shipToTinybird tests: vi.fn() call history accumulates across tests — not.toHaveBeenCalled() fails after test 1

apps/platform/src/api/utils/__tests__/public-v1-telemetry.test.ts:350

tinybird.appendEvents is a vi.hoisted() vi.fn(). vi.restoreAllMocks() does not clear call counts. Test 2 asserts not.toHaveBeenCalled() but call count is 1 after test 1. Add mockClear() in beforeEach or clearMocks:true in vitest config.

low

Auth hook 'cleanliness' check fragile — any comment mentioning batu_org_id in hook body fails CI as false alarm

apps/platform/src/__tests__/integration/rls-machine-claim.test.ts:162

pg_get_functiondef returns verbatim body including SQL comments. A future defensive comment would trigger a misleading security-alarm failure. Strip comment lines before the substring check.

low

Source-level after() boundary guard coupled to specific comment string '// Not in a request scope'

apps/platform/src/api/utils/__tests__/public-v1-telemetry.test.ts:267

Editing that comment silently breaks the slice boundary. Existing start>-1 + end>start guards prevent vacuous pass — risk is a confusing failure, not a silent miss.

info

fetchRow replacement correctly pins that recordApiRequest is synchronously awaited

apps/platform/src/__tests__/integration/api-telemetry-persistence.test.ts:49

Poll loop would have stayed green on deferred-write regression. Direct select is the right enforcement.

improvement3

low

0075: nullif(current_setting(...))::json evaluated twice — lateral or CTE would parse once

packages/database/drizzle/0075_machine_claim_hardening.sql:41

STABLE within a statement so functionally safe. Worth a cleanup on next migration touch.

info

withEnv helper doesn't cover TINYBIRD_BRANCH_TOKEN — env can be left deleted on test failure

apps/platform/src/api/utils/__tests__/public-v1-telemetry.test.ts:350

Extend withEnv to cover both tokens, or use vi.stubEnv for guaranteed cleanup.

info

setTimeout(r,0) microtask flush may not settle dynamic import() in all environments

apps/platform/src/api/utils/__tests__/public-v1-telemetry.test.ts:324

Works reliably in current Vitest+Node; worth noting if test environment changes.

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:13current
  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:32
  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