UNPKG

@rip-user/rls-debugger-mcp

Version:

AI-powered MCP server for debugging Supabase Row Level Security policies with Claude structured outputs

274 lines (211 loc) 8.22 kB
--- name: RLS Policy Debugger description: Debug Supabase Row Level Security policies using AI-powered analysis tags: [supabase, rls, debugging, postgres, security] requires: mcp_servers: [rls-debugger] env_vars: [SUPABASE_URL, SUPABASE_SERVICE_KEY, ANTHROPIC_API_KEY] --- # RLS Policy Debugger Skill Systematically debug Supabase Row Level Security (RLS) policies using the rls-debugger MCP server. ## When to Use This Skill - User reports access denied errors - Policies not behaving as expected - Multiple policies creating complex interactions - Need to understand policy decision flow - Debugging multi-tenant access patterns ## Available MCP Tools 1. **analyze_rls_policies** - AI-powered policy analysis with structured output 2. **compile_policy_logic** - Generate decision trees showing policy combinations 3. **list_all_policies** - View all RLS policies in the database 4. **save_policy_knowledge** - Remember architectural patterns and decisions 5. **get_policy_knowledge** - Retrieve saved insights from previous sessions ## Debugging Workflow ### Step 1: Gather Context Start by understanding the scenario: - What is the user trying to do? - Which table are they accessing? - What operation (SELECT, INSERT, UPDATE, DELETE)? - User's role and permissions ### Step 2: List and Analyze Policies Use `list_all_policies` to see all RLS policies, then `analyze_rls_policies` with the specific scenario. ### Step 3: Review Decision Logic Use `compile_policy_logic` to understand how PERMISSIVE (OR) and RESTRICTIVE (AND) policies combine. ### Step 4: Identify Root Cause The analysis will show: - Which policies apply to the scenario - Why access is granted or denied - Missing relationships or conditions - Verification SQL queries to check data ### Step 5: Save Learnings Use `save_policy_knowledge` to remember architectural decisions and known patterns for future debugging. ## RLS Policy Fundamentals ### PERMISSIVE Policies (Default) - Use OR logic - ANY one policy grants access - Most common type - Example: "User owns record OR record is public" ### RESTRICTIVE Policies - Use AND logic - ALL policies must pass - Applied on top of PERMISSIVE - Example: "Record must be active AND user subscribed" ### How They Combine ``` FINAL ACCESS = (Any PERMISSIVE passes) AND (All RESTRICTIVE pass) ``` ## Example Debugging Session **Scenario:** User can't see their orders \`\`\` User: "User abc-123 can't access their orders" Step 1 - Analyze: "Using rls-debugger, analyze why user abc-123 can't access the orders table" Step 2 - Review logic: "Compile the policy logic for orders to see how policies combine" Step 3 - Check relationships: Review the verification queries to see if required relationships exist Step 4 - Save findings: "Save to memory: orders table uses tenant_id isolation pattern" \`\`\` ## Common RLS Patterns ### Pattern 1: Multi-tenant Isolation \`\`\`sql CREATE POLICY "tenant_isolation" ON orders FOR ALL USING (tenant_id = auth.uid()); \`\`\` Access granted if: user's ID matches the tenant_id column ### Pattern 2: Role-based Access \`\`\`sql CREATE POLICY "admin_access" ON orders FOR ALL USING ( EXISTS ( SELECT 1 FROM user_roles WHERE user_id = auth.uid() AND role = 'admin' ) ); \`\`\` Access granted if: user has admin role in user_roles table ### Pattern 3: Hierarchical Access \`\`\`sql CREATE POLICY "org_access" ON documents FOR SELECT USING ( org_id IN ( SELECT org_id FROM user_orgs WHERE user_id = auth.uid() ) ); \`\`\` Access granted if: user belongs to the organization ### Pattern 4: Ownership + Public \`\`\`sql -- Policy 1: Own records (PERMISSIVE) CREATE POLICY "own_posts" ON posts FOR SELECT USING (user_id = auth.uid()); -- Policy 2: Public records (PERMISSIVE) CREATE POLICY "public_posts" ON posts FOR SELECT USING (is_public = true); \`\`\` Access granted if: user owns it OR it's public (OR logic) ### Pattern 5: Base Access + Restrictions \`\`\`sql -- Base access (PERMISSIVE) CREATE POLICY "team_members" ON projects FOR SELECT USING ( EXISTS ( SELECT 1 FROM team_members WHERE user_id = auth.uid() AND team_id = projects.team_id ) ); -- Must be active (RESTRICTIVE) CREATE POLICY "active_only" ON projects FOR SELECT AS RESTRICTIVE USING (status = 'active'); \`\`\` Access granted if: (user is team member) AND (project is active) ## Best Practices 1. **Always check saved knowledge first** - Load context from previous sessions 2. **Verify relationships exist** - Run the provided SQL verification queries 3. **Consider performance** - Index foreign keys used in policy clauses 4. **Save architectural decisions** - Help future debugging sessions 5. **Test policy changes carefully** - Use `risk_level` in recommendations ## Common Issues Detected ### Issue 1: Missing Junction Table Records **Symptom:** Policy checks EXISTS but record doesn't exist **Fix:** Insert the missing relationship record **Example:** User exists but team_members entry is missing ### Issue 2: Policy Type Mismatch **Symptom:** Multiple conditions that should ALL pass, but defined as PERMISSIVE **Fix:** Change to RESTRICTIVE policy **Example:** "active AND subscribed" should be RESTRICTIVE, not PERMISSIVE ### Issue 3: Overly Complex Conditions **Symptom:** Slow queries, multiple JOINs in policy **Fix:** Use materialized views or simplify logic **Example:** Policy with 4-level deep JOIN ### Issue 4: Missing WITH CHECK Clause **Symptom:** INSERT/UPDATE allowed but creates inaccessible rows **Fix:** Add WITH CHECK clause matching USING clause **Example:** User can insert but can't read what they inserted ### Issue 5: Conflicting Policies **Symptom:** Multiple PERMISSIVE policies that never both pass **Fix:** Combine into single policy with OR conditions **Example:** Two policies checking different roles but should be one ## Performance Tips ### Index Foreign Keys Always index columns used in policy clauses: \`\`\`sql CREATE INDEX idx_orders_tenant_id ON orders(tenant_id); CREATE INDEX idx_team_members_user_id ON team_members(user_id); \`\`\` ### Avoid Complex JOINs Use materialized views for complex relationships: \`\`\`sql -- Instead of complex EXISTS in policy: CREATE MATERIALIZED VIEW user_accessible_orgs AS SELECT user_id, org_id FROM ...complex query...; -- Simple policy: CREATE POLICY "org_access" ON documents FOR SELECT USING ( org_id IN ( SELECT org_id FROM user_accessible_orgs WHERE user_id = auth.uid() ) ); \`\`\` ### Use Specific Operations Don't use `FOR ALL` if you only need SELECT: \`\`\`sql -- Good - specific operation CREATE POLICY "view_orders" ON orders FOR SELECT USING (...); -- Less optimal - applies to all operations CREATE POLICY "orders_access" ON orders FOR ALL USING (...); \`\`\` ## Verification Workflow After analyzing and fixing: 1. **Run verification queries** provided in the analysis 2. **Test the access** with the actual user 3. **Save the knowledge** for future reference 4. **Monitor performance** if policy is complex ## Example Complete Workflow \`\`\` 1. User: "User abc-123 with admin role can't see document xyz-456" 2. Analyze: "Using rls-debugger, analyze why user abc-123 can't see document xyz-456" 3. Results show: - Policy requires team_members record - User has admin role but no team_members entry - Verification query: SELECT * FROM team_members WHERE user_id = 'abc-123' 4. Fix: INSERT INTO team_members (user_id, org_id, role) VALUES ('abc-123', 'org-789', 'admin'); 5. Verify: SELECT * FROM documents WHERE id = 'xyz-456'; -- Now accessible! 6. Save: "Save to memory: documents table requires team_members record for access. Fixed by ensuring all admins have team_members entries." \`\`\` ## Tips for Effective Usage - **Be specific in scenarios** - Include user IDs, table names, and operations - **Start with policy logic** - Understand the structure before diving into analysis - **Use saved knowledge** - Build a knowledge base over time - **Verify relationships** - Always run the verification SQL queries - **Consider security** - Review `risk_level` before applying recommendations