Skip to content

Tier 4 Onset T5-1 Infra Report — Phase 2 public exposure

Status: STOP — Ryan review required before T5-1-iOS
Date: 2026-08-30
Authorization: Ryan accepted Phase 1 verdict SAFE TO EXPOSE; authorized Phase 2 with gateway JWT hardening + scan/search auth fix folded in
Phase 1 reference: ONSET_DB_PUBLIC_EXPOSURE_RLS_SECURITY_PASS.md


Verdict

PHASE 2 COMPLETE — PUBLIC ENDPOINT LIVE (CF PROXIED)

Patient-facing Supabase is reachable at https://onset-api.theonset.app through Cloudflare (orange cloud) → onset-edge tunnel → Kong on VM 119. Public smoke passed, including cross-user RLS denial over the public URL.

This does NOT authorize iOS pointer flip, archive, or submit. That is T5-1-iOS, separately authorized.


Executive summary

Item Result
Public hostname onset-api.theonset.app (product domain; avoids db in URL)
CF proxy in path Yes — server: cloudflare, cf-ray present on all public probes
TLS Valid (Cloudflare edge cert; HTTPS 200 on /auth/v1/health)
Gateway JWT hardening 18 patient-facing functions → per-function JWT at edge runtime
Shared-secret / webhook functions 7 remain gateway JWT off; in-function auth documented below
scan/search-supplement 500-before-auth Fixed — auth before env; anon → 401 (not 500)
Anon PostgREST (public) 0 rows on all tested patient tables
Cross-user denial (public) PASS — hello 23 checkins; alex 0; no leak
Edge functions anon (public) 401 on all patient-facing probes
Storage (public) Private bucket empty/denied anon; public templates 200
iOS Production.xcconfig Not flipped (pairing documented for T5-1-iOS)

1. Public endpoint topology

Internet
  → Cloudflare (proxied, WAF/rate-limit layer)
  → onset-edge tunnel (UUID fe2fa2de-d776-485f-85ed-84415f0c3493)
  → onset-app VM 118 / 192.168.1.101 (cloudflared)
  → http://192.168.1.102:8000 (onset-db Kong / supabase-envoy)
Component Value
Public URL https://onset-api.theonset.app
DNS CNAME → fe2fa2de-d776-485f-85ed-84415f0c3493.cfargotunnel.com (proxied)
CF zone theonset.app (7607d8b8d46f14a6c903558e97eb555b)
Tunnel onset-edge on onset-app (192.168.1.101)
Origin onset-db Kong :8000 (192.168.1.102)
Stack public URL env API_EXTERNAL_URL + SUPABASE_PUBLIC_URL → https://onset-api.theonset.app

cloudflared ingress (/etc/cloudflared/config.yml on onset-app):

ingress:
  - hostname: onset-api.theonset.app
    service: http://192.168.1.102:8000
  - hostname: clinic.theonset.app
    service: http://192.168.1.101:3000
  - service: http_status:404

Untouched (per constraints): hosted Supabase rollback net, clinic sovereign stack internals, other product edges.

Automation scripts (repo):

Script Purpose
scripts/onset/t5-1-harden-functions.sh Per-function JWT + scan/search auth order + public URL env (VM 119)
scripts/onset/t5-1-add-onset-api-tunnel.sh Add onset-api ingress to cloudflared (VM 101)
scripts/onset/t5-1-dns-onset-api.sh Proxied CNAME via CF API
scripts/onset/t5-1-public-smoke.sh Public smoke + cross-user denial

2. Edge function gateway JWT decisions

Self-hosted Supabase keeps global FUNCTIONS_VERIFY_JWT=false in .env (Kong does not JWT-check all functions). Defense in depth is implemented in volumes/functions/main/index.ts via ONSET_FUNCTIONS_VERIFY_JWT — a comma-separated slug list that enables JWT verification at the edge runtime before the worker handler runs.

Env on VM 119:

ONSET_FUNCTIONS_VERIFY_JWT=daily-insight,client-event,scan-supplement-label,scan-supplement,search-supplement,resolve-branded-product,popular-branded-products,parse-lab,delete-account,apple-link,google-link,last1-link,last1-unlink,last1-connect-assertion,generate-protocol,lab-intelligence,scan-label,simulate-protocol-lab

2.1 verify_jwt = true (gateway JWT ON) — 18 patient-facing functions

Function Rationale
daily-insight Patient app; user JWT required
client-event Patient telemetry; user JWT required
scan-supplement-label Patient supplement scan; user JWT required
scan-supplement Patient supplement scan; user JWT required
search-supplement Patient supplement search; user JWT required
resolve-branded-product Patient catalog lookup; user JWT required
popular-branded-products Patient catalog; user JWT required
parse-lab Patient lab parse; user JWT required
delete-account Patient account deletion; user JWT required
apple-link Patient identity link; user JWT required
google-link Patient identity link; user JWT required
last1-link Patient Last1 link; user JWT required
last1-unlink Patient Last1 unlink; user JWT required
last1-connect-assertion Patient Last1 connect; user JWT required
generate-protocol Patient protocol generation; user JWT required
lab-intelligence Patient lab intelligence; user JWT required
scan-label Patient label scan; user JWT required
simulate-protocol-lab Patient protocol simulation; user JWT required

Public smoke (anon POST): all above return 401 Missing authorization header over https://onset-api.theonset.app.

2.2 verify_jwt = false (gateway JWT OFF) — 7 functions + WHY

Function Why gateway JWT must stay off In-function auth (confirmed)
admin-dashboard Clinic admin portal uses x-onset-admin shared secret, not user JWT Rejects when x-onset-admin ≠ ADMIN_DASHBOARD_SECRET
auth-send-email GoTrue webhook caller; verifies standardwebhooks signature on payload, not Bearer JWT Webhook.verify(body, headers) with hook secret
fitness-extract Clinic protocol pipeline; x-protocol-import-secret header auth Secret header checked before processing
protocol-extract Same clinic import pipeline x-protocol-import-secret
protocol-import Same clinic import pipeline x-protocol-import-secret
practitioner-notifications Clinic notification dispatch; x-notify-secret header Rejects 401 when secret mismatch
hello Dev/demo Supabase scaffold; uses publishable/secret apiKey modes, not patient JWT path Not on iOS patient path; requires apiKey

These functions are clinic-side, webhook, or internal — enabling gateway JWT would break legitimate callers that authenticate with shared secrets or webhook signatures instead of user Bearer tokens.


3. scan-supplement / search-supplement resolution

Decision: Fixed auth ordering (not excluded from public path). Both are patient-facing and remain in ONSET_FUNCTIONS_VERIFY_JWT.

Problem (Phase 1): Env-var guard (ANTHROPIC_API_KEY, etc.) ran before auth check → anon callers got 500 Server misconfigured instead of 401.

Fix: Moved Bearer JWT + getUser() validation before env check in both:

  • volumes/functions/scan-supplement/index.ts
  • volumes/functions/search-supplement/index.ts

Verification (LAN + public, anon POST):

Function Before After
scan-supplement 500 401
search-supplement 500 401

4. Public smoke results

Probe host: https://onset-api.theonset.app (real public path, not LAN IP)
Date: 2026-08-30 UTC

4.1 Cloudflare proxy confirmation

Response headers on /auth/v1/health:

server: cloudflare
cf-ray: a3300e5978e30b32-PHX
cf-cache-status: DYNAMIC

Direct-origin bypass was not used for smoke — all traffic through CF edge IPs (104.21.14.156, 172.67.203.205).

4.2 Auth health

Path HTTP Body
/auth/v1/health 200 {"version":"v2.189.0","name":"GoTrue",...}

4.3 PostgREST anon (no JWT)

Table Rows (anon)
practitioner_patients 0
shared_checkins 0
shared_lab_results 0
shared_adherence 0
data_share_events 0

4.4 Cross-user denial (public endpoint) — proof that matters

Re-run of Phase 1 test over public URL (not LAN Kong):

User Email shared_checkins rows Leak check
Patient A hello@unicornryda.com 23 All rows owned by hello's UID
Patient B alex.rivera@demo.theonset.app 0 No hello rows visible

Result: CROSS_USER_PUBLIC=PASS

4.5 Edge functions (anon POST)

Function HTTP Notes
daily-insight 401 Gateway JWT
client-event 401 Gateway JWT
scan-supplement 401 Fixed ordering + gateway JWT
search-supplement 401 Fixed ordering + gateway JWT
delete-account 401 Gateway JWT

4.6 Storage

Check HTTP Result
Public template templates/posts/1.png 200 Intentionally public
Private bucket list clinic-support-attachments (anon) 200 body [] No object leak

5. iOS pointer pairing (T5-1-iOS — NOT flipped)

When Ryan authorizes T5-1-iOS, flip Production.xcconfig only:

Key T5-1-iOS target value
SUPABASE_HOST onset-api.theonset.app
SUPABASE_URL https://onset-api.theonset.app
SUPABASE_ANON_KEY BWS ONSET_DB_PROD_SUPABASE_ANON_KEY (R940 JWT anon key from stack init; same value as VM 119 .env ANON_KEY)

Anon key source: Bitwarden Secrets Manager project Onset → ONSET_DB_PROD_SUPABASE_ANON_KEY. Public by design (ships in app binary); do not inline in git docs (GitGuardian false-alarm noise). No rotation required.

Pre-flip smoke (T5-1-iOS): curl https://onset-api.theonset.app/auth/v1/settings from public internet → apple: true (R940 GoTrue + TU6S85XABQ key).

Still on hosted (unchanged): Release Production.xcconfig → emxrmjysvxeboavhatry.supabase.co until T5-1-iOS.


6. Phase 2 vs Phase 1 delta

Gap (Phase 1) Phase 2 resolution
No public hostname onset-api.theonset.app live
CF proxy not in path Proxied CNAME + cf-ray confirmed
Gateway JWT off all functions 18 patient functions JWT at edge runtime
scan/search 500-before-auth Auth-first ordering
Cross-user test LAN-only Re-run over public URL — PASS

7. STOP — next authorized step

Step Status
T5-1 Phase 1 RLS pass Done — SAFE TO EXPOSE
T5-1 Phase 2 infra Done — Ryan accepted; public endpoint live
T5-1-iOS Step 1 In progress — Staging TestFlight public journey test (TIER_4_ONSET_T5-1_IOS_STEP1_PREP.md)
T5-1-iOS Step 2 (Production flip + archive) Blocked until Step 1 journey PASS
T5-1-iOS Step 3 (upload + submit) Blocked until Step 2 Ryan review

Do not: flip Production.xcconfig, production archive, or submit until Step 1 device journey passes.