fix(neon-auth): 020 — FORCE RLS + complete write policies (split from #245)#256
fix(neon-auth): 020 — FORCE RLS + complete write policies (split from #245)#256chitcommit wants to merge 2 commits into
Conversation
Prod regression from PR #241 (admin-merged, never applied to prod): all 9 neon_auth tables have rowsecurity=false in production on project restless-grass-40598426 (ChittyOS-Core). Split from PR #245 (hotfix/020-neon-auth-rls) which mixed this RLS hotfix with unrelated broker-primitives work. This branch contains ONLY migration 020 — no broker code, no schema extensions, no deps. Critical fix over PR #245's version — adds FORCE ROW LEVEL SECURITY: The runtime connection role (neondb_owner) owns the neon_auth tables. Postgres exempts table owners from RLS unless FORCE is set. PR #245's ALTER TABLE ... ENABLE ROW LEVEL SECURITY was inert at runtime — all 9 policies applied to a production owner-role connection were bypassed. Confirmed by: e2e test (scripts/e2e/neon-auth-rls.mjs:112-124) creates a shadow non-owner role to work around owner-bypass, implicitly acknowledging the bypass. Comment at neon-user-store.js:20-26 asserts "This module never bypasses RLS" — that comment is incorrect given the owner role; flagged in PR description. Scope corrections vs PR #245: - Removed RLS enable on neon_auth.jwks + neon_auth.project_config (ChittyAuth-owned; RLS enable with no policy = deny-all under FORCE; jwks-verify.js reads from remote HTTP JWKS, not the DB table, so no ChittyConnect path reads these; ChittyAuth must own policy DDL) - Added INSERT policy on neon_auth.user (POST /me upsert path) - Added INSERT policy on neon_auth.organization (POST /organizations) - Added INSERT policy on neon_auth.member (org-create adds first member) Total policies: 12 (was 9 in PR #245 — missing write-path coverage) Refs: #241 (prod regression), #245 (split source) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Warning Review limit reached
More reviews will be available in 33 minutes and 37 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
chittyconnect | 96909c9 | Jun 17 2026, 03:41 PM |
There was a problem hiding this comment.
Pull request overview
This PR adds a production hotfix migration (020) for the Neon Postgres neon_auth schema to ensure Row Level Security is actually enforced under the runtime owner-role connection by enabling and forcing RLS on the 7 ChittyConnect-owned tables, along with a complete policy set needed for current write paths.
Changes:
- Adds
ALTER TABLE ... ENABLE ROW LEVEL SECURITYand... FORCE ROW LEVEL SECURITYfor the 7 ChittyConnect-ownedneon_authtables. - Installs/refreshes 12 idempotent RLS policies, including missing INSERT policies for the current app write paths.
- Ensures
neon_auth."user".chittyDidand its partial unique index exist (to support policy predicates and upsert behavior).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| CREATE POLICY member_self_insert ON neon_auth.member | ||
| FOR INSERT | ||
| WITH CHECK ( | ||
| "userId" IN ( | ||
| SELECT "id" FROM neon_auth."user" | ||
| WHERE "chittyDid" = current_setting('request.jwt.claim.sub', true) | ||
| ) | ||
| ); |
Live Neon validation — migration is necessary but NOT sufficientValidated migration 020 on a throwaway Neon branch ( Structural — PASS
Behavioral — policies are CORRECT, but runtime role defeats them
Root cause (deeper than owner-bypass):
Required companion change (blocks effectiveness, not this PR's DDL): Test branch will be deleted after review. Validation run by ChittyConnect session (no mocks — real Neon branch). |
…uard Per the Neon Auth ownership-split authority, the user-store must connect as the non-BYPASSRLS role protected_app_rw — not neondb_owner (bypasses RLS even under FORCE) and not the neon_auth owner role (would grant ChittyConnect authority over ChittyAuth's jwks/project_config). - migration 020: add USAGE + least-privilege DML grants to protected_app_rw on the 7 ChittyConnect-owned neon_auth tables. - neon-user-store withRlsBinding: fail closed (rls_not_enforced) if the connected role has rolbypassrls — makes the inert-RLS regression impossible to reintroduce silently. Enforcement is verified at runtime, not assumed. - refresh the stale module comment (FORCE + protected_app_rw model). Validated live on Neon branch br-sparkling-cherry-aeh6918f: as protected_app_rw, SELECT isolates per-DID, self-INSERT allowed, foreign-INSERT raises RLS WITH CHECK violation; role rolbypassrls=false. Still required before merge: NEON_AUTH_DATABASE_URL secret pointing to protected_app_rw (concierge/operator) — without it the new guard fails closed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Coordinated fix applied + end-to-end validated as the canonical rolePer the Neon Auth ownership-split authority, the runtime role is Pushed in
Live validation (Neon branch
REMAINING GATING STEP before merge (ordering matters): |
Summary
Split from PR #245 (
hotfix/020-neon-auth-rls), which mixes this Neon RLS security fix with unrelated broker-primitives work. This PR contains only migration 020 — a clean cherry-pick of the RLS hotfix with critical corrections.Original regression: PR #241 was admin-merged on 2026-06-09 through a failing governance check. Migration 019 was never applied to production (
restless-grass-40598426/ ChittyOS-Core). All 9neon_authtables haverowsecurity=falsein prod today.Why PR #245's version is still inert (critical finding):
The runtime connection (
NEON_DATABASE_URL) connects asneondb_owner, which owns theneon_authtables. In Postgres, table owners bypass RLS unlessFORCE ROW LEVEL SECURITYis set on the table. PR #245 issued onlyENABLE ROW LEVEL SECURITY— neverFORCE. The 9 policies would be applied but never consulted at runtime. User data would still be cross-visible in production.Evidence:
scripts/e2e/neon-auth-rls.mjs:112-124explicitly creates a shadow non-owner role (SET LOCAL ROLE) to work around owner-bypass when testing — the test authors knew about the bypass.src/auth/neon-user-store.js:20-26asserts "This module never bypasses RLS" — that comment is factually incorrect for an owner-role connection and should be corrected in a follow-up.Scope corrections vs PR #245:
FORCE ROW LEVEL SECURITYneon_auth.jwksRLS enablejwks-verify.jsuses remote HTTP JWKS)neon_auth.project_configRLS enableuseruser_self_insert) — required forPOST /meupsertorganizationorganization_self_insert) — required forPOST /organizationsmembermember_self_insert) — required for org-create first-member rowPolicy inventory (12 total):
user:user_self_select(SELECT),user_self_update(UPDATE),user_self_insert(INSERT)account:account_self_all(ALL)session:session_self_all(ALL)verification:verification_self_all(ALL)organization:organization_member_select(SELECT),organization_self_insert(INSERT)member:member_self_org_select(SELECT),member_self_insert(INSERT)invitation:invitation_org_select(SELECT),invitation_self_email(SELECT)SQL parse validation
Verified via structural analysis (no local Postgres available):
ENABLE ROW LEVEL SECURITY(ChittyConnect-owned tables only)FORCE ROW LEVEL SECURITY(same 7 tables)DROP POLICY IF EXISTS/ 12CREATE POLICY(idempotent)neon-user-store.jsFull Neon-branch validation (
scripts/e2e/neon-auth-rls.mjs) is blocked on credential provisioning for a branch clone — per task hand-off to chittyconnect-concierge. The e2e test's shadow-role approach still validates FORCE enforcement correctly sinceSET LOCAL ROLEto a non-owner exercises the policy path.Post-apply verification
What stays in #245
The broker-primitives surface (
src/api/routes/broker-primitives.js,src/schemas/v1/,migrations/019_policy_resolve_extensions.sql,tests/api/v1/broker-primitives.test.js, package changes) remains untouched onhotfix/020-neon-auth-rlsfor separate review.Fixes: prod regression from #241
Splits: #245 (broker-primitives work remains there)
Refs:
restless-grass-40598426(ChittyOS-Core Neon project)🤖 Generated with Claude Code