| name | db-policy-apply |
| description | Install KISS or granular RLS policies on a table |
| agent | architect |
| subtask | false |
Apply RLS Policy Template
Install KISS or granular RLS policies on a table
1. YOLO Mode - Fast, Autonomous (0-1 prompts)
- Autonomous decision making with logging
- Minimal user interaction
- Best for:* Simple, deterministic tasks
2. Interactive Mode - Balanced, Educational (5-10 prompts) [DEFAULT]
- Explicit decision checkpoints
- Educational explanations
- Best for:* Learning, complex decisions
3. Pre-Flight Planning - Comprehensive Upfront Planning
- Task analysis phase (identify all ambiguities)
- Zero ambiguity execution
- Best for:* Ambiguous requirements, critical work
Parameter:* mode (optional, default: interactive)
Acceptance Criteria
Purpose:* Definitive pass/fail criteria for task completion
Checklist:*
acceptance-criteria:
- [ ] Data persisted correctly; constraints respected; no orphaned data
type: acceptance-criterion
blocker: true
validation: |
Assert data persisted correctly; constraints respected; no orphaned data
error_message: "Acceptance criterion not met: Data persisted correctly; constraints respected; no orphaned data"
Error Handling
Strategy:* abort
Common Errors:*
-
Error:* Connection Failed
- Cause:* Unable to connect to Neo4j database
- Resolution:* Check connection string, credentials, network
- Recovery:* Retry with exponential backoff (max 3 attempts)
-
Error:* Query Syntax Error
- Cause:* Invalid Cypher query syntax
- Resolution:* Validate query syntax before execution
- Recovery:* Return detailed syntax error, suggest fix
-
Error:* Transaction Rollback
- Cause:* Query violates constraints or timeout
- Resolution:* Review query logic and constraints
- Recovery:* Automatic rollback, preserve data integrity
🚀 NEW: Use Automated RLS Policy Installer (RECOMMENDED)
Token Savings: 89% | Time Savings: ~85%*
./Squads/super-agentes/scripts/database-operations/rls-policy-installer.sh {table} {mode}
./Squads/super-agentes/scripts/database-operations/rls-policy-installer.sh minds kiss
./Squads/super-agentes/scripts/database-operations/rls-policy-installer.sh sources read-only
./Squads/super-agentes/scripts/database-operations/rls-policy-installer.sh fragments private
OR continue with manual policy installation below:*
Inputs
table (string): Table name to apply policy to
mode (string): 'kiss' or 'granular' - policy type
1. Validate Inputs
Check table exists and mode is valid:
echo "Validating inputs..."
psql "$SUPABASE_DB_URL" -c \
"SELECT EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = '{table}'
);" | grep -q t || {
echo "❌ Table '{table}' not found"
exit 1
}
if [[ "{mode}" != "kiss" && "{mode}" != "granular" ]]; then
echo "❌ Invalid mode: {mode}"
echo " Use 'kiss' or 'granular'"
exit 1
fi
echo "✓ Table exists: {table}"
echo "✓ Mode: {mode}"
2. Check Existing Policies
Display current RLS status:
echo "Checking existing RLS policies..."
psql "$SUPABASE_DB_URL" << EOF
SELECT
schemaname,
tablename,
policyname,
permissive,
roles,
cmd,
qual,
with_check
FROM pg_policies
WHERE tablename = '{table}';
EOF
echo ""
echo "RLS enabled on {table}?"
psql "$SUPABASE_DB_URL" -c \
"SELECT relrowsecurity FROM pg_class WHERE relname = '{table}';" \
| grep -q t && echo "✓ Yes" || echo "⚠️ No (will be enabled)"
3. Ask User Confirmation
Present policy that will be applied based on mode:
If mode = 'kiss':*
Will apply KISS policy to {table}:
- Enable RLS
- Single policy: users can only access their own rows
- Uses: (select auth.uid()) = user_id [PERFORMANCE OPTIMIZED]
- Applies to: SELECT, INSERT, UPDATE, DELETE
⚠️ CRITICAL PERFORMANCE NOTE:
Wrapping auth.uid() in SELECT provides 99.99% performance improvement
by allowing PostgreSQL to cache the function result.
Continue? (yes/no)
If mode = 'granular':*
Will apply granular policies to {table}:
- Enable RLS
- Separate policies for each operation (SELECT, INSERT, UPDATE, DELETE)
- Fine-grained control
- Uses: auth.uid() = user_id
Continue? (yes/no)
Get confirmation before proceeding.
4. Generate Policy SQL
Based on mode, generate appropriate SQL:
KISS Mode:*
ALTER TABLE {table} ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "{table}_policy" ON {table};
CREATE POLICY "{table}_policy"
ON {table}
FOR ALL
TO authenticated
USING (
(select auth.uid()) IS NOT NULL AND
(select auth.uid()) = user_id
)
WITH CHECK (
(select auth.uid()) IS NOT NULL AND
(select auth.uid()) = user_id
);
COMMENT ON POLICY "{table}_policy" ON {table} IS
'KISS policy: users can only access their own rows (performance optimized with cached auth.uid())';
Granular Mode (PERFORMANCE OPTIMIZED):*
ALTER TABLE {table} ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "{table}_select" ON {table};
DROP POLICY IF EXISTS "{table}_insert" ON {table};
DROP POLICY IF EXISTS "{table}_update" ON {table};
DROP POLICY IF EXISTS "{table}_delete" ON {table};
CREATE POLICY "{table}_select"
ON {table}
FOR SELECT
TO authenticated
USING (
(select auth.uid()) IS NOT NULL AND
(select auth.uid()) = user_id
);
CREATE POLICY "{table}_insert"
ON {table}
FOR INSERT
TO authenticated
WITH CHECK (
(select auth.uid()) IS NOT NULL AND
(select auth.uid()) = user_id
);
CREATE POLICY "{table}_update"
ON {table}
FOR UPDATE
TO authenticated
USING (
(select auth.uid()) IS NOT NULL AND
(select auth.uid()) = user_id
)
WITH CHECK (
(select auth.uid()) IS NOT NULL AND
(select auth.uid()) = user_id
);
CREATE POLICY "{table}_delete"
ON {table}
FOR DELETE
TO authenticated
USING (
(select auth.uid()) IS NOT NULL AND
(select auth.uid()) = user_id
);
COMMENT ON POLICY "{table}_select" ON {table} IS 'Users can read own rows (cached auth.uid())';
COMMENT ON POLICY "{table}_insert" ON {table} IS 'Users can insert own rows (cached auth.uid())';
COMMENT ON POLICY "{table}_update" ON {table} IS 'Users can update own rows (cached auth.uid())';
COMMENT ON POLICY "{table}_delete" ON {table} IS 'Users can delete own rows (cached auth.uid())';
5. Create Migration File
Save policy SQL to migration file:
TS=$(date +%Y%m%d%H%M%S)
MIGRATION_FILE="supabase/migrations/${TS}_rls_${mode}__{table}.sql"
mkdir -p supabase/migrations
cat > "$MIGRATION_FILE" << 'EOF'
-- Migration: Apply {mode} RLS policy to {table}
-- Generated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
-- Table: {table}
-- Mode: {mode}
BEGIN;
[... SQL from step 4 ...]
COMMIT;
EOF
echo "✓ Migration created: $MIGRATION_FILE"
6. Apply Migration
Use existing db-apply-migration task:
echo "Applying migration..."
7. Test Policies
Verify policies work correctly:
echo "Testing RLS policies..."
psql "$SUPABASE_DB_URL" << EOF
SET ROLE anon;
SELECT COUNT(*) AS anon_count FROM {table};
RESET ROLE;
EOF
echo ""
echo "✓ Policy tests complete"
echo " ⚠️ Manual testing recommended:"
echo " - Use impersonate to test as specific user"
echo " - Verify each operation (SELECT, INSERT, UPDATE, DELETE)"
Output
Display summary:
✅ RLS POLICY APPLIED
Table: {table}
Mode: {mode}
Migration: supabase/migrations/{TS}_rls_{mode}__{table}.sql
Policies: [list created policies]
Next steps:
1. Test policies manually: impersonate {user_id}
2. Run RLS audit: rls-audit
3. Update documentation
4. Commit migration to git
KISS vs Granular
KISS* (Keep It Simple, Stupid):
- ✅ Single policy for all operations
- ✅ Easier to understand
- ✅ Less verbose
- ❌ Less flexible
Granular*:
- ✅ Separate policies per operation
- ✅ Fine-grained control
- ✅ Can have different logic per operation
- ❌ More verbose
Common Patterns
Public Read, Authenticated Write (Performance Optimized):*
CREATE POLICY "{table}_select" ON {table}
FOR SELECT TO public
USING (true);
CREATE POLICY "{table}_write" ON {table}
FOR ALL TO authenticated
USING (
(select auth.uid()) IS NOT NULL AND
(select auth.uid()) = user_id
)
WITH CHECK (
(select auth.uid()) IS NOT NULL AND
(select auth.uid()) = user_id
);
Tenant-Based (Performance Optimized):*
CREATE POLICY "{table}_tenant" ON {table}
FOR ALL TO authenticated
USING (
(select auth.uid()) IS NOT NULL AND
tenant_id IN (
SELECT tenant_id FROM user_tenants
WHERE user_id = (select auth.uid())
)
);
CRITICAL: Do NOT Use raw_user_meta_data in Policies
CREATE POLICY "bad_policy" ON {table}
USING (
(auth.jwt() -> 'user_metadata' ->> 'role') = 'admin'
);
Why dangerous:* raw_user_meta_data can be modified by the user through Supabase Auth client. An attacker can set { "role": "admin" } and bypass security!
Safe alternative:* Use raw_app_meta_data (server-only):
CREATE POLICY "safe_policy" ON {table}
USING (
(auth.jwt() -> 'app_metadata' ->> 'role') = 'admin'
);
Auth NULL Check
Always check if user is authenticated:
USING (auth.uid() = user_id)
USING (
(select auth.uid()) IS NOT NULL AND
(select auth.uid()) = user_id
)
Policy Debugging
Enable RLS policies in SQL Editor (dev only):
ALTER TABLE {table} DISABLE ROW LEVEL SECURITY;
ALTER TABLE {table} ENABLE ROW LEVEL SECURITY;
Prerequisites
Table must have:
user_id UUID column (for user-based policies)
- Or
tenant_id column (for tenant-based policies)
- Indexes on all policy filter columns* (critical for performance!)
CREATE INDEX idx_{table}_user_id ON {table}(user_id);
Error Handling
If policy application fails:
- Check table has required columns (user_id, etc.)
- Verify auth.uid() is available (Supabase)
- Check for existing policies with same names
- Rollback migration if needed:
rollback