@luanpdd/kit-mcp 1.30.2 → 1.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +168 -168
- package/gates/agent-no-recursive-dispatch.md +84 -82
- package/kit/COMANDOS.md +138 -138
- package/kit/README.md +76 -76
- package/kit/agents/advisor-researcher.md +107 -106
- package/kit/agents/ai-mutation-tester.md +1 -0
- package/kit/agents/assumptions-analyzer.md +108 -107
- package/kit/agents/audit-log-implementer.md +314 -313
- package/kit/agents/auditor-consistencia-isolamento.md +414 -413
- package/kit/agents/b2b-saas-architect.md +157 -156
- package/kit/agents/burn-rate-forecaster.md +1 -0
- package/kit/agents/cascading-failures-auditor.md +299 -298
- package/kit/agents/codebase-mapper.md +769 -768
- package/kit/agents/crm-pipeline-implementer.md +257 -256
- package/kit/agents/debugger.md +814 -813
- package/kit/agents/detector-tenant-quente.md +338 -337
- package/kit/agents/evolution-go-integrator.md +201 -200
- package/kit/agents/example-reviewer.md +22 -21
- package/kit/agents/executor.md +565 -564
- package/kit/agents/golden-signals-instrumenter.md +1 -0
- package/kit/agents/incident-investigator.md +1 -0
- package/kit/agents/integration-checker.md +201 -200
- package/kit/agents/invite-flow-implementer.md +190 -189
- package/kit/agents/legacy-characterizer.md +369 -368
- package/kit/agents/lgpd-compliance-auditor.md +296 -295
- package/kit/agents/load-shedding-instrumenter.md +1 -0
- package/kit/agents/multi-tenant-isolation-auditor.md +254 -253
- package/kit/agents/multi-tenant-rls-writer.md +341 -340
- package/kit/agents/nyquist-auditor.md +179 -178
- package/kit/agents/observability-coverage-auditor.md +316 -315
- package/kit/agents/observability-instrumenter.md +1 -0
- package/kit/agents/omm-auditor.md +1 -0
- package/kit/agents/org-onboarding-implementer.md +224 -223
- package/kit/agents/payload-capture-instrumenter.md +274 -273
- package/kit/agents/phase-researcher.md +697 -696
- package/kit/agents/plan-checker.md +273 -272
- package/kit/agents/planner.md +923 -922
- package/kit/agents/postmortem-writer.md +1 -0
- package/kit/agents/project-researcher.md +653 -652
- package/kit/agents/prr-conductor.md +1 -0
- package/kit/agents/refactor-safety-auditor.md +405 -404
- package/kit/agents/release-pipeline-auditor.md +1 -0
- package/kit/agents/research-synthesizer.md +246 -245
- package/kit/agents/roadmapper.md +678 -677
- package/kit/agents/schema-checker.md +1 -0
- package/kit/agents/seam-finder.md +360 -359
- package/kit/agents/shotgun-surgery-detector.md +350 -349
- package/kit/agents/slo-engineer.md +1 -0
- package/kit/agents/storytelling-analyst.md +1 -0
- package/kit/agents/supabase-architect.md +1 -0
- package/kit/agents/supabase-auth-bootstrapper.md +1 -0
- package/kit/agents/supabase-branching-architect.md +563 -562
- package/kit/agents/supabase-cicd-pipeline-implementer.md +778 -777
- package/kit/agents/supabase-column-privileges-writer.md +400 -399
- package/kit/agents/supabase-edge-fn-tester.md +2 -1
- package/kit/agents/supabase-edge-fn-writer.md +2 -1
- package/kit/agents/supabase-migration-writer.md +386 -385
- package/kit/agents/supabase-rbac-implementer.md +393 -392
- package/kit/agents/supabase-realtime-implementer.md +364 -363
- package/kit/agents/supabase-rls-hardener.md +522 -521
- package/kit/agents/supabase-rls-writer.md +324 -323
- package/kit/agents/supabase-roles-implementer.md +356 -355
- package/kit/agents/supabase-storage-implementer.md +1 -0
- package/kit/agents/super-admin-implementer.md +282 -281
- package/kit/agents/toil-auditor.md +1 -0
- package/kit/agents/ui-auditor.md +438 -437
- package/kit/agents/ui-checker.md +303 -302
- package/kit/agents/ui-researcher.md +356 -355
- package/kit/agents/user-profiler.md +176 -175
- package/kit/agents/validador-evolucao-schema.md +336 -335
- package/kit/agents/verifier.md +729 -728
- package/kit/commands/adicionar-backlog.md +75 -75
- package/kit/commands/adicionar-fase.md +42 -42
- package/kit/commands/adicionar-tarefa.md +45 -45
- package/kit/commands/adicionar-testes.md +41 -41
- package/kit/commands/ajuda.md +21 -21
- package/kit/commands/atualizar.md +37 -37
- package/kit/commands/auditar-cascading.md +111 -111
- package/kit/commands/auditar-marco.md +179 -179
- package/kit/commands/auditar-observabilidade-cobertura.md +183 -183
- package/kit/commands/auditar-refactor.md +219 -219
- package/kit/commands/auditar-release.md +109 -109
- package/kit/commands/auditar-uat.md +23 -23
- package/kit/commands/autonomo.md +40 -40
- package/kit/commands/branch-pr.md +24 -24
- package/kit/commands/burn-rate-status.md +408 -408
- package/kit/commands/capturar-payloads.md +193 -193
- package/kit/commands/caracterizar.md +212 -212
- package/kit/commands/concluir-marco.md +247 -247
- package/kit/commands/configuracoes.md +36 -36
- package/kit/commands/dados-distribuidos.md +188 -188
- package/kit/commands/definir-perfil.md +10 -10
- package/kit/commands/depurar.md +190 -190
- package/kit/commands/detectar-duplicacao.md +197 -197
- package/kit/commands/discutir-fase.md +131 -131
- package/kit/commands/encontrar-seams.md +136 -136
- package/kit/commands/entrar-discord.md +17 -17
- package/kit/commands/estatisticas.md +18 -18
- package/kit/commands/example-greeting.md +33 -33
- package/kit/commands/executar-fase.md +58 -58
- package/kit/commands/expresso.md +56 -56
- package/kit/commands/fase-ui.md +34 -34
- package/kit/commands/fazer.md +57 -57
- package/kit/commands/fio.md +125 -125
- package/kit/commands/fluxos-trabalho.md +64 -64
- package/kit/commands/forense.md +176 -176
- package/kit/commands/gerenciador.md +38 -38
- package/kit/commands/inserir-fase.md +31 -31
- package/kit/commands/legacy.md +263 -263
- package/kit/commands/limpeza.md +17 -17
- package/kit/commands/listar-hipoteses-fase.md +45 -45
- package/kit/commands/listar-workspaces.md +18 -18
- package/kit/commands/load-shedding.md +117 -117
- package/kit/commands/mapear-codebase.md +70 -70
- package/kit/commands/multi-tenant.md +163 -163
- package/kit/commands/nota.md +33 -33
- package/kit/commands/novo-marco.md +43 -43
- package/kit/commands/novo-projeto.md +41 -41
- package/kit/commands/novo-workspace.md +43 -43
- package/kit/commands/pausar-trabalho.md +37 -37
- package/kit/commands/perfil-usuario.md +45 -45
- package/kit/commands/pesquisar-fase.md +195 -195
- package/kit/commands/planejar-fase.md +67 -67
- package/kit/commands/planejar-lacunas.md +33 -33
- package/kit/commands/plantar-ideia.md +25 -25
- package/kit/commands/progresso.md +24 -24
- package/kit/commands/proximo.md +30 -30
- package/kit/commands/publicar.md +490 -490
- package/kit/commands/rapido.md +35 -35
- package/kit/commands/reaplicar-patches.md +124 -124
- package/kit/commands/refactor-seguro.md +321 -321
- package/kit/commands/relatorio-sessao.md +19 -19
- package/kit/commands/remover-fase.md +31 -31
- package/kit/commands/remover-workspace.md +26 -26
- package/kit/commands/resumo-marco.md +50 -50
- package/kit/commands/retomar-trabalho.md +40 -40
- package/kit/commands/revisar-backlog.md +60 -60
- package/kit/commands/revisar-ui.md +32 -32
- package/kit/commands/revisar.md +37 -37
- package/kit/commands/saude.md +21 -21
- package/kit/commands/setup-notion.md +93 -93
- package/kit/commands/storytelling.md +179 -179
- package/kit/commands/sync-main.md +68 -68
- package/kit/commands/validar-fase.md +35 -35
- package/kit/commands/verificar-tarefas.md +44 -44
- package/kit/commands/verificar-trabalho.md +64 -64
- package/kit/file-manifest.json +82 -81
- package/kit/framework/bin/lib/commands.cjs +959 -959
- package/kit/framework/bin/lib/config.cjs +442 -442
- package/kit/framework/bin/lib/core.cjs +1230 -1230
- package/kit/framework/bin/lib/frontmatter.cjs +336 -336
- package/kit/framework/bin/lib/init.cjs +1442 -1442
- package/kit/framework/bin/lib/milestone.cjs +252 -252
- package/kit/framework/bin/lib/model-profiles.cjs +68 -68
- package/kit/framework/bin/lib/phase.cjs +888 -888
- package/kit/framework/bin/lib/profile-output.cjs +952 -952
- package/kit/framework/bin/lib/profile-pipeline.cjs +539 -539
- package/kit/framework/bin/lib/roadmap.cjs +329 -329
- package/kit/framework/bin/lib/security.cjs +382 -382
- package/kit/framework/bin/lib/state.cjs +1031 -1031
- package/kit/framework/bin/lib/template.cjs +222 -222
- package/kit/framework/bin/lib/uat.cjs +282 -282
- package/kit/framework/bin/lib/verify.cjs +888 -888
- package/kit/framework/bin/lib/workstream.cjs +491 -491
- package/kit/framework/bin/tools.cjs +918 -918
- package/kit/framework/commands/workstreams.md +63 -63
- package/kit/framework/references/checkpoints.md +778 -778
- package/kit/framework/references/continuation-format.md +249 -249
- package/kit/framework/references/decimal-phase-calculation.md +64 -64
- package/kit/framework/references/git-integration.md +295 -295
- package/kit/framework/references/git-planning-commit.md +38 -38
- package/kit/framework/references/model-profile-resolution.md +36 -36
- package/kit/framework/references/model-profiles.md +139 -139
- package/kit/framework/references/phase-argument-parsing.md +61 -61
- package/kit/framework/references/planning-config.md +202 -202
- package/kit/framework/references/questioning.md +162 -162
- package/kit/framework/references/tdd.md +263 -263
- package/kit/framework/references/ui-brand.md +160 -160
- package/kit/framework/references/user-profiling.md +657 -657
- package/kit/framework/references/verification-patterns.md +612 -612
- package/kit/framework/references/workstream-flag.md +58 -58
- package/kit/framework/templates/DEBUG.md +164 -164
- package/kit/framework/templates/UAT.md +265 -265
- package/kit/framework/templates/UI-SPEC.md +100 -100
- package/kit/framework/templates/VALIDATION.md +76 -76
- package/kit/framework/templates/claude-md.md +122 -122
- package/kit/framework/templates/codebase/architecture.md +185 -185
- package/kit/framework/templates/codebase/concerns.md +205 -205
- package/kit/framework/templates/codebase/conventions.md +204 -204
- package/kit/framework/templates/codebase/integrations.md +192 -192
- package/kit/framework/templates/codebase/stack.md +158 -158
- package/kit/framework/templates/codebase/structure.md +199 -199
- package/kit/framework/templates/codebase/testing.md +301 -301
- package/kit/framework/templates/config.json +44 -44
- package/kit/framework/templates/context.md +352 -352
- package/kit/framework/templates/continue-here.md +78 -78
- package/kit/framework/templates/copilot-instructions.md +7 -7
- package/kit/framework/templates/debug-subagent-prompt.md +91 -91
- package/kit/framework/templates/dev-preferences.md +20 -20
- package/kit/framework/templates/discovery.md +146 -146
- package/kit/framework/templates/discussion-log.md +63 -63
- package/kit/framework/templates/milestone-archive.md +123 -123
- package/kit/framework/templates/milestone.md +115 -115
- package/kit/framework/templates/phase-prompt.md +610 -610
- package/kit/framework/templates/planner-subagent-prompt.md +117 -117
- package/kit/framework/templates/project.md +186 -186
- package/kit/framework/templates/requirements.md +231 -231
- package/kit/framework/templates/research-project/ARCHITECTURE.md +204 -204
- package/kit/framework/templates/research-project/FEATURES.md +147 -147
- package/kit/framework/templates/research-project/PITFALLS.md +200 -200
- package/kit/framework/templates/research-project/STACK.md +120 -120
- package/kit/framework/templates/research-project/SUMMARY.md +170 -170
- package/kit/framework/templates/research.md +419 -419
- package/kit/framework/templates/retrospective.md +54 -54
- package/kit/framework/templates/roadmap.md +202 -202
- package/kit/framework/templates/state.md +176 -176
- package/kit/framework/templates/summary-complex.md +59 -59
- package/kit/framework/templates/summary-minimal.md +41 -41
- package/kit/framework/templates/summary-standard.md +48 -48
- package/kit/framework/templates/summary.md +209 -209
- package/kit/framework/templates/user-profile.md +146 -146
- package/kit/framework/templates/user-setup.md +256 -256
- package/kit/framework/templates/verification-report.md +258 -258
- package/kit/framework/workflows/add-phase.md +112 -112
- package/kit/framework/workflows/add-tests.md +351 -351
- package/kit/framework/workflows/add-todo.md +158 -158
- package/kit/framework/workflows/audit-milestone.md +340 -340
- package/kit/framework/workflows/audit-uat.md +109 -109
- package/kit/framework/workflows/autonomous.md +891 -891
- package/kit/framework/workflows/check-todos.md +177 -177
- package/kit/framework/workflows/cleanup.md +152 -152
- package/kit/framework/workflows/complete-milestone.md +696 -696
- package/kit/framework/workflows/diagnose-issues.md +231 -231
- package/kit/framework/workflows/discovery-phase.md +289 -289
- package/kit/framework/workflows/discuss-phase-assumptions.md +653 -653
- package/kit/framework/workflows/discuss-phase.md +784 -784
- package/kit/framework/workflows/do.md +104 -104
- package/kit/framework/workflows/execute-phase.md +838 -838
- package/kit/framework/workflows/execute-plan.md +510 -510
- package/kit/framework/workflows/fast.md +102 -102
- package/kit/framework/workflows/forensics.md +265 -265
- package/kit/framework/workflows/health.md +181 -181
- package/kit/framework/workflows/help.md +619 -619
- package/kit/framework/workflows/insert-phase.md +130 -130
- package/kit/framework/workflows/list-phase-assumptions.md +178 -178
- package/kit/framework/workflows/list-workspaces.md +56 -56
- package/kit/framework/workflows/manager.md +362 -362
- package/kit/framework/workflows/map-codebase.md +377 -377
- package/kit/framework/workflows/milestone-summary.md +223 -223
- package/kit/framework/workflows/new-milestone.md +486 -486
- package/kit/framework/workflows/new-project.md +1159 -1159
- package/kit/framework/workflows/new-workspace.md +237 -237
- package/kit/framework/workflows/next.md +97 -97
- package/kit/framework/workflows/node-repair.md +92 -92
- package/kit/framework/workflows/note.md +156 -156
- package/kit/framework/workflows/pause-work.md +176 -176
- package/kit/framework/workflows/plan-milestone-gaps.md +273 -273
- package/kit/framework/workflows/plan-phase.md +765 -765
- package/kit/framework/workflows/plant-seed.md +169 -169
- package/kit/framework/workflows/pr-branch.md +129 -129
- package/kit/framework/workflows/profile-user.md +450 -450
- package/kit/framework/workflows/progress.md +507 -507
- package/kit/framework/workflows/quick.md +757 -757
- package/kit/framework/workflows/remove-phase.md +155 -155
- package/kit/framework/workflows/remove-workspace.md +90 -90
- package/kit/framework/workflows/research-phase.md +82 -82
- package/kit/framework/workflows/resume-project.md +326 -326
- package/kit/framework/workflows/review.md +228 -228
- package/kit/framework/workflows/session-report.md +146 -146
- package/kit/framework/workflows/settings.md +283 -283
- package/kit/framework/workflows/ship.md +228 -228
- package/kit/framework/workflows/stats.md +60 -60
- package/kit/framework/workflows/transition.md +671 -671
- package/kit/framework/workflows/ui-phase.md +302 -302
- package/kit/framework/workflows/ui-review.md +165 -165
- package/kit/framework/workflows/update.md +323 -323
- package/kit/framework/workflows/validate-phase.md +174 -174
- package/kit/framework/workflows/verify-phase.md +252 -252
- package/kit/framework/workflows/verify-work.md +637 -637
- package/kit/hooks/check-update.js +118 -118
- package/kit/hooks/context-monitor.js +163 -163
- package/kit/hooks/kit-attribution-reminder.cjs +29 -50
- package/kit/hooks/kit-router.cjs +137 -0
- package/kit/hooks/prompt-guard.js +103 -103
- package/kit/hooks/statusline.js +125 -125
- package/kit/hooks/workflow-guard.js +101 -101
- package/kit/settings.json +45 -45
- package/kit/skills/ai-prompt-characterization/SKILL.md +335 -335
- package/kit/skills/armadilhas-sistemas-distribuidos/SKILL.md +447 -447
- package/kit/skills/audit-log-multi-tenant/SKILL.md +340 -340
- package/kit/skills/b2b-saas-architecture/SKILL.md +300 -300
- package/kit/skills/consistencia-leitura-replica/SKILL.md +385 -385
- package/kit/skills/crm-lead-pipeline-patterns/SKILL.md +343 -343
- package/kit/skills/escolha-modelo-consistencia/SKILL.md +494 -494
- package/kit/skills/evolucao-schema-compativel/SKILL.md +448 -448
- package/kit/skills/evolution-go-whatsapp-integration/SKILL.md +322 -322
- package/kit/skills/example-skill/SKILL.md +42 -42
- package/kit/skills/legacy-api-only-applications/SKILL.md +358 -358
- package/kit/skills/legacy-characterization-tests/SKILL.md +330 -330
- package/kit/skills/legacy-effect-analysis/SKILL.md +331 -331
- package/kit/skills/legacy-extract-class/SKILL.md +203 -203
- package/kit/skills/legacy-programming-by-difference/SKILL.md +252 -252
- package/kit/skills/legacy-seams-and-test-harness/SKILL.md +460 -460
- package/kit/skills/legacy-shotgun-surgery/SKILL.md +286 -286
- package/kit/skills/legacy-sprout-wrap-techniques/SKILL.md +434 -434
- package/kit/skills/legacy-storytelling-naked-crc/SKILL.md +270 -270
- package/kit/skills/lgpd-multi-tenant-compliance/SKILL.md +340 -340
- package/kit/skills/member-invite-flow/SKILL.md +305 -305
- package/kit/skills/member-management-react-shadcn/SKILL.md +328 -328
- package/kit/skills/multi-tenant-performance-scaling/SKILL.md +316 -316
- package/kit/skills/multi-tenant-rls-hierarchy/SKILL.md +342 -342
- package/kit/skills/org-onboarding-flow/SKILL.md +257 -257
- package/kit/skills/org-switcher-react-pattern/SKILL.md +349 -349
- package/kit/skills/permission-gate-react-pattern/SKILL.md +271 -271
- package/kit/skills/postgres-isolamento-concorrencia/SKILL.md +552 -552
- package/kit/skills/pre-refactor-characterization/SKILL.md +421 -421
- package/kit/skills/rbac-permissions-matrix-supabase/SKILL.md +338 -338
- package/kit/skills/streams-eventos-cdc/SKILL.md +711 -711
- package/kit/skills/supabase-branching-workflow/SKILL.md +544 -544
- package/kit/skills/supabase-ci-cd-github-actions/SKILL.md +880 -880
- package/kit/skills/supabase-column-level-security/SKILL.md +426 -426
- package/kit/skills/supabase-config-toml-remotes/SKILL.md +807 -807
- package/kit/skills/supabase-custom-claims-rbac/SKILL.md +472 -472
- package/kit/skills/supabase-edge-functions/SKILL.md +1 -1
- package/kit/skills/supabase-edge-functions-auth/SKILL.md +1 -1
- package/kit/skills/supabase-edge-functions-limits/SKILL.md +1 -1
- package/kit/skills/supabase-edge-functions-mcp-server/SKILL.md +1 -1
- package/kit/skills/supabase-edge-functions-testing/SKILL.md +1 -1
- package/kit/skills/supabase-edge-runtime-builtins/SKILL.md +1 -1
- package/kit/skills/supabase-migration-repair/SKILL.md +823 -823
- package/kit/skills/supabase-migrations/SKILL.md +297 -297
- package/kit/skills/supabase-pgtap-testing/SKILL.md +1053 -1053
- package/kit/skills/supabase-postgres-roles/SKILL.md +392 -392
- package/kit/skills/supabase-realtime/SKILL.md +460 -460
- package/kit/skills/supabase-rls-defense-in-depth/SKILL.md +418 -418
- package/kit/skills/supabase-rls-policies/SKILL.md +635 -635
- package/kit/skills/super-admin-platform-pattern/SKILL.md +326 -326
- package/kit/skills/tenant-quente-mitigacao/SKILL.md +605 -605
- package/kit/skills/whatsapp-conversation-state-machine/SKILL.md +287 -287
- package/package.json +1 -1
- package/src/core/kit.js +216 -216
- package/src/core/reflect.js +247 -247
- package/src/core/reverse-sync.js +372 -372
- package/src/core/sync.js +437 -418
- package/src/core/watch.js +121 -121
- package/src/mcp-server/index.js +794 -746
|
@@ -1,418 +1,418 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: supabase-rls-defense-in-depth
|
|
3
|
-
description: Use ao desenhar defense-in-depth RLS em Supabase — event trigger rls_auto_enable (default projetos novos), BYPASSRLS role privilege, service_role caveat, security definer functions como bypass co…
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Supabase — RLS Defense in Depth
|
|
7
|
-
|
|
8
|
-
## Quando usar
|
|
9
|
-
|
|
10
|
-
LLM carrega esta skill quando precisar desenhar **camadas de defesa** RLS além das policies básicas. Trigger phrases:
|
|
11
|
-
|
|
12
|
-
- "defense in depth RLS", "camadas de defesa Postgres"
|
|
13
|
-
- "auto-enable RLS em todas tabelas novas", "event trigger ensure RLS"
|
|
14
|
-
- "BYPASSRLS role privilege", "alter role bypassrls"
|
|
15
|
-
- "security definer function bypassa RLS"
|
|
16
|
-
- "service_role bypassa RLS", "service_role caveat"
|
|
17
|
-
- "view security_invoker", "view bypass RLS"
|
|
18
|
-
- "como proteger contra third-party tools acessando DB direto"
|
|
19
|
-
|
|
20
|
-
## Princípio canônico
|
|
21
|
-
|
|
22
|
-
**Defense in depth** = múltiplas camadas independentes de proteção. Para RLS em Supabase:
|
|
23
|
-
|
|
24
|
-
1. **Camada 1 — Policy explícita por tabela** (skill `supabase-rls-policies`) — sempre granular, sempre `(select auth.uid())`, sempre `IS NOT NULL`, sempre indices.
|
|
25
|
-
2. **Camada 2 — Auto-enable RLS via event trigger** (DEFENSE-01) — garante que toda tabela nova nasce com RLS habilitado, mesmo se developer esquecer.
|
|
26
|
-
3. **Camada 3 — GRANT explícito** (skill `supabase-rls-policies`) — sem `grant select to authenticated`, queries falham com "permission denied" antes mesmo de chegar nas policies.
|
|
27
|
-
4. **Camada 4 — Bypass controlado** — `BYPASSRLS` role, `security definer` functions em schema `private` (DEFENSE-02, DEFENSE-04). Nunca em schema exposto.
|
|
28
|
-
5. **Camada 5 — Views com `security_invoker=true`** (DEFENSE-05, Postgres 15+) — views respeitam RLS do role chamador, não do criador.
|
|
29
|
-
6. **Camada 6 — Service role caveat** (DEFENSE-03) — entender que service_role bypassa RLS mas só no servidor; nunca expor ao cliente.
|
|
30
|
-
7. **Camada 7 — Cooperative handoff via supabase-rls-hardener** (v1.23) — todo SQL gerado pelo kit passa pelo hardener canonical antes do output final. Verdicts GO/STRENGTHEN/REWRITE-com-confirmação. Princípio canônico: agents externos pensam/planejam; agents Supabase materializam/hardenam.
|
|
31
|
-
8. **Camada 8 — Column-Level Privileges** (v1.24) — `GRANT/REVOKE (col1, col2) ON TABLE` para restringir colunas sensíveis (PII, audit payload, billing, tokens). Feature AVANÇADA — usar apenas quando RLS + dedicated role table não cobrem o caso. Cross-ref skill [`supabase-column-level-security`](../supabase-column-level-security/SKILL.md).
|
|
32
|
-
9. **Camada 9 — Auth Hooks - Custom Claims** (v1.25) — `Custom Access Token Auth Hook` (função PG `custom_access_token_hook(event jsonb)`) injeta `user_role` no JWT durante geração do token. RLS policies consultam o claim direto via `auth.jwt() ->> 'user_role'` ou via `authorize()` function — zero-JOIN, type-safe via enum, composable. Alternativa moderna a dedicated role table com JOIN custoso em policies. Caveat JWT freshness (eventually consistent). Cross-ref skill [`supabase-custom-claims-rbac`](../supabase-custom-claims-rbac/SKILL.md).
|
|
33
|
-
10. **Camada 10 — Postgres Roles Hierarchy** (v1.26) — Postgres roles dedicados para **system access** (service accounts, cron jobs, BI tools, ETL, admin scripts) em vez de service_role API key sempre. Auditabilidade superior (queries logam por role no `pg_stat_statements`), Role hierarchy via INHERIT/NOINHERIT, custom roles com BYPASSRLS específicos (`security_admin`, `dpo_role`, `lead_manager`, `platform_admin`). Distinção canônica vs application access (RLS + Custom Claims). Cross-ref skill [`supabase-postgres-roles`](../supabase-postgres-roles/SKILL.md).
|
|
34
|
-
|
|
35
|
-
Razão: RLS é a primeira linha, mas humanos esquecem. Third-party tooling (Metabase, dbt, ferramentas BI conectadas via JDBC, scripts) bypassam toda a lógica da camada de aplicação — só RLS no banco protege. Defense in depth aplica princípio de **proteção sobreposta** para resiliência.
|
|
36
|
-
|
|
37
|
-
## DEFENSE-01: Event trigger `rls_auto_enable()` como default
|
|
38
|
-
|
|
39
|
-
Em projetos novos, instale event trigger que ativa RLS automaticamente quando uma tabela é criada em schema exposto. Protege contra esquecimento humano.
|
|
40
|
-
|
|
41
|
-
### Função PLpgSQL + Event Trigger
|
|
42
|
-
|
|
43
|
-
```sql
|
|
44
|
-
create or replace function rls_auto_enable()
|
|
45
|
-
returns event_trigger
|
|
46
|
-
language plpgsql
|
|
47
|
-
security definer
|
|
48
|
-
set search_path = pg_catalog
|
|
49
|
-
as $$
|
|
50
|
-
declare
|
|
51
|
-
cmd record;
|
|
52
|
-
begin
|
|
53
|
-
for cmd in
|
|
54
|
-
select *
|
|
55
|
-
from pg_event_trigger_ddl_commands()
|
|
56
|
-
where command_tag in ('CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO')
|
|
57
|
-
and object_type in ('table','partitioned table')
|
|
58
|
-
loop
|
|
59
|
-
-- só atua em schemas configurados; pula sistema
|
|
60
|
-
if cmd.schema_name is not null
|
|
61
|
-
and cmd.schema_name in ('public')
|
|
62
|
-
and cmd.schema_name not in ('pg_catalog','information_schema')
|
|
63
|
-
and cmd.schema_name not like 'pg_toast%'
|
|
64
|
-
and cmd.schema_name not like 'pg_temp%' then
|
|
65
|
-
begin
|
|
66
|
-
execute format('alter table if exists %s enable row level security', cmd.object_identity);
|
|
67
|
-
raise log 'rls_auto_enable: enabled RLS on %', cmd.object_identity;
|
|
68
|
-
exception
|
|
69
|
-
when others then
|
|
70
|
-
raise log 'rls_auto_enable: failed to enable RLS on %', cmd.object_identity;
|
|
71
|
-
end;
|
|
72
|
-
else
|
|
73
|
-
raise log 'rls_auto_enable: skip % (system schema or not in enforced list)', cmd.object_identity;
|
|
74
|
-
end if;
|
|
75
|
-
end loop;
|
|
76
|
-
end;
|
|
77
|
-
$$;
|
|
78
|
-
|
|
79
|
-
drop event trigger if exists ensure_rls;
|
|
80
|
-
create event trigger ensure_rls
|
|
81
|
-
on ddl_command_end
|
|
82
|
-
when tag in ('CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO')
|
|
83
|
-
execute function rls_auto_enable();
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### Caveats e limitações
|
|
87
|
-
|
|
88
|
-
- **Aplica-se a tabelas criadas após o trigger ser instalado** — tabelas pré-existentes precisam de `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` manual ou script de migração.
|
|
89
|
-
- **Schemas filtrados** — só atua em schemas listados explicitamente (`public` por default). Para incluir mais schemas, edite a lista. Para projetos multi-tenant em schemas separados por org, adicione `('public', 'org_*')` ou similar.
|
|
90
|
-
- **Não cria policies** — apenas habilita RLS. Sem policies, tabela fica bloqueada para roles não-bypass — comportamento desejado (failsafe).
|
|
91
|
-
- **Não dispara em `CREATE TABLE IF NOT EXISTS` quando tabela já existe** — comportamento padrão Postgres event triggers.
|
|
92
|
-
- **Trigger é `SECURITY DEFINER`** — roda como o owner do trigger (geralmente `postgres`). Garante permissão para `ALTER TABLE`.
|
|
93
|
-
|
|
94
|
-
### Auditoria — listar tabelas sem RLS
|
|
95
|
-
|
|
96
|
-
Para validar que defense-in-depth está aplicado:
|
|
97
|
-
|
|
98
|
-
```sql
|
|
99
|
-
select schemaname, tablename
|
|
100
|
-
from pg_tables
|
|
101
|
-
where schemaname = 'public'
|
|
102
|
-
and not exists (
|
|
103
|
-
select 1 from pg_class c
|
|
104
|
-
join pg_namespace n on n.oid = c.relnamespace
|
|
105
|
-
where n.nspname = pg_tables.schemaname
|
|
106
|
-
and c.relname = pg_tables.tablename
|
|
107
|
-
and c.relrowsecurity = true
|
|
108
|
-
);
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
Se retorna ≥ 1 row, há tabelas sem RLS — gap de defense-in-depth.
|
|
112
|
-
|
|
113
|
-
## DEFENSE-02: `BYPASSRLS` role privilege para tarefas admin
|
|
114
|
-
|
|
115
|
-
Para tarefas administrativas internas (jobs noturnos, migrations, scripts de manutenção), crie role com privilégio `BYPASSRLS` ao invés de usar `service_role`:
|
|
116
|
-
|
|
117
|
-
```sql
|
|
118
|
-
-- criar role interno
|
|
119
|
-
create role admin_internal with login password '<strong_pw>';
|
|
120
|
-
alter role admin_internal with bypassrls;
|
|
121
|
-
|
|
122
|
-
-- conceder permissões necessárias
|
|
123
|
-
grant all on all tables in schema public to admin_internal;
|
|
124
|
-
grant all on all sequences in schema public to admin_internal;
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
**Quando usar:**
|
|
128
|
-
|
|
129
|
-
- Scripts de manutenção que rodam dentro do banco (não via API)
|
|
130
|
-
- Jobs `pg_cron` que precisam acessar dados cross-tenant
|
|
131
|
-
- Migrations que populam dados em massa ignorando policies
|
|
132
|
-
- DBAs investigando incidents em production
|
|
133
|
-
|
|
134
|
-
**Quando NÃO usar:**
|
|
135
|
-
|
|
136
|
-
- Edge Functions — use `SUPABASE_SERVICE_ROLE_KEY` direto (já tem BYPASSRLS implícito)
|
|
137
|
-
- Backend customizado conectado via JDBC — use service_role role do Supabase
|
|
138
|
-
- Qualquer role que receba requisição de cliente
|
|
139
|
-
|
|
140
|
-
**Comparação com service_role:**
|
|
141
|
-
|
|
142
|
-
| | `service_role` (Supabase API key) | Custom role `BYPASSRLS` |
|
|
143
|
-
|---|---|---|
|
|
144
|
-
| Escopo | API key — usado em Authorization header | Postgres role — login direto |
|
|
145
|
-
| Auditoria | Logs do Supabase API | Postgres audit log (pg_audit) |
|
|
146
|
-
| Revoke | Rotacionar key via dashboard | `revoke bypassrls from <role>` |
|
|
147
|
-
| Granularidade | Tudo-ou-nada | Pode escopar GRANT a tabelas específicas |
|
|
148
|
-
|
|
149
|
-
## DEFENSE-03: `service_role` caveat — não bypassa RLS do user logged-in
|
|
150
|
-
|
|
151
|
-
**Caveat crítico:** quando você usa o SDK Supabase com `SUPABASE_SERVICE_ROLE_KEY`, mas a sessão ainda tem `Authorization: Bearer <user_jwt>` set, **o RLS do user é aplicado** — o service_role bypass é **overridden**.
|
|
152
|
-
|
|
153
|
-
```js
|
|
154
|
-
// errado — service_role mas com sessão user ainda ativa
|
|
155
|
-
const supabase = createClient(URL, SERVICE_ROLE_KEY)
|
|
156
|
-
supabase.auth.setSession({ access_token: userJwt, refresh_token: userRefresh })
|
|
157
|
-
// agora .from('tasks').select() aplica RLS do user, não bypass
|
|
158
|
-
|
|
159
|
-
// certo — service_role limpo
|
|
160
|
-
const supabase = createClient(URL, SERVICE_ROLE_KEY, {
|
|
161
|
-
auth: { persistSession: false, autoRefreshToken: false }
|
|
162
|
-
})
|
|
163
|
-
// agora .from('tasks').select() bypassa RLS
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
**Como Supabase decide:** se a request tem JWT válido em Authorization header, Supabase deriva o `role` (anon/authenticated) e aplica RLS. Service_role só bypassa se NÃO há JWT user OU se você usa o pattern admin client com `persistSession: false`.
|
|
167
|
-
|
|
168
|
-
**Aplicação:** Edge Functions que precisam fazer trabalho cross-tenant **devem** usar admin client separado (não o client da sessão do user que disparou a função):
|
|
169
|
-
|
|
170
|
-
```ts
|
|
171
|
-
// edge function
|
|
172
|
-
import { createClient } from 'npm:@supabase/supabase-js'
|
|
173
|
-
|
|
174
|
-
Deno.serve(async (req) => {
|
|
175
|
-
// admin client — bypassa RLS porque sem session
|
|
176
|
-
const adminDb = createClient(
|
|
177
|
-
Deno.env.get('SUPABASE_URL')!,
|
|
178
|
-
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!,
|
|
179
|
-
{ auth: { persistSession: false, autoRefreshToken: false } }
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
// user client — respeita RLS (para audit log do que user PODE fazer)
|
|
183
|
-
const userDb = createClient(
|
|
184
|
-
Deno.env.get('SUPABASE_URL')!,
|
|
185
|
-
Deno.env.get('SUPABASE_ANON_KEY')!,
|
|
186
|
-
{ global: { headers: { Authorization: req.headers.get('Authorization')! } } }
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
// exemplo: validar com userDb (RLS aplicado) que user pode acessar org
|
|
190
|
-
const { data: orgCheck } = await userDb.from('organizations').select('id').eq('id', orgId).single()
|
|
191
|
-
if (!orgCheck) return new Response('Forbidden', { status: 403 })
|
|
192
|
-
|
|
193
|
-
// executar mutation cross-tenant com adminDb (bypass RLS)
|
|
194
|
-
const { data } = await adminDb.from('audit_log').insert({ ... })
|
|
195
|
-
return new Response(JSON.stringify(data))
|
|
196
|
-
})
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
## DEFENSE-04: `SECURITY DEFINER` functions como bypass controlado
|
|
200
|
-
|
|
201
|
-
Funções `SECURITY DEFINER` rodam com permissões do owner (geralmente `postgres`, que tem `BYPASSRLS`). Use para encapsular lógica admin/cross-tenant que precisa bypassar RLS.
|
|
202
|
-
|
|
203
|
-
### Regras absolutas
|
|
204
|
-
|
|
205
|
-
1. **NUNCA em schema exposto** (`public`) — atacante poderia chamar a função com input arbitrário via REST API. Sempre em schema `private`, `internal`, ou similar **não-exposto** em API settings.
|
|
206
|
-
2. **Sempre `SET search_path = ''`** — evita schema injection (atacante criando função homônima em outro schema).
|
|
207
|
-
3. **Validar inputs** — função `SECURITY DEFINER` é alta autoridade; valide tudo (uuid format, range, ownership) antes de fazer trabalho.
|
|
208
|
-
4. **Auditar invocações** — log quem chamou + o quê + quando.
|
|
209
|
-
|
|
210
|
-
### Example: cross-tenant analytics aggregation
|
|
211
|
-
|
|
212
|
-
```sql
|
|
213
|
-
-- schema private (NÃO exposto via API)
|
|
214
|
-
create schema if not exists private;
|
|
215
|
-
|
|
216
|
-
create or replace function private.org_analytics_summary(org_id_arg uuid)
|
|
217
|
-
returns table(metric text, value bigint)
|
|
218
|
-
language plpgsql
|
|
219
|
-
security definer
|
|
220
|
-
set search_path = ''
|
|
221
|
-
as $$
|
|
222
|
-
begin
|
|
223
|
-
-- validar input (uuid format, ownership do caller, etc.)
|
|
224
|
-
if org_id_arg is null then
|
|
225
|
-
raise exception 'org_id_arg required';
|
|
226
|
-
end if;
|
|
227
|
-
|
|
228
|
-
-- validar que caller pode acessar essa org
|
|
229
|
-
-- (mesmo bypassando RLS, regras de negócio aplicam)
|
|
230
|
-
if not exists (
|
|
231
|
-
select 1 from public.organization_members
|
|
232
|
-
where org_id = org_id_arg and user_id = auth.uid()
|
|
233
|
-
) then
|
|
234
|
-
raise exception 'access denied to org %', org_id_arg;
|
|
235
|
-
end if;
|
|
236
|
-
|
|
237
|
-
-- audit log
|
|
238
|
-
insert into public.audit_log (event, user_id, org_id, payload)
|
|
239
|
-
values ('analytics_summary_query', auth.uid(), org_id_arg, jsonb_build_object('ts', now()));
|
|
240
|
-
|
|
241
|
-
-- query bypass RLS
|
|
242
|
-
return query
|
|
243
|
-
select 'total_tasks' as metric, count(*)::bigint as value
|
|
244
|
-
from public.tasks
|
|
245
|
-
where org_id = org_id_arg
|
|
246
|
-
union all
|
|
247
|
-
select 'total_members', count(*)::bigint
|
|
248
|
-
from public.organization_members
|
|
249
|
-
where org_id = org_id_arg;
|
|
250
|
-
end;
|
|
251
|
-
$$;
|
|
252
|
-
|
|
253
|
-
-- expor via RPC (RLS check feito DENTRO da função, não via policy)
|
|
254
|
-
grant execute on function private.org_analytics_summary(uuid) to authenticated;
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
**Padrão de uso em policy:** funções `SECURITY DEFINER` podem ser chamadas em policies para fazer JOIN performant sem aplicar RLS recursivamente:
|
|
258
|
-
|
|
259
|
-
```sql
|
|
260
|
-
-- ver supabase-rls-policies Performance section #5
|
|
261
|
-
create function private.has_good_role()
|
|
262
|
-
returns boolean
|
|
263
|
-
language plpgsql
|
|
264
|
-
security definer
|
|
265
|
-
set search_path = ''
|
|
266
|
-
as $$
|
|
267
|
-
begin
|
|
268
|
-
return exists (
|
|
269
|
-
select 1 from public.roles_table
|
|
270
|
-
where (select auth.uid()) = user_id and role = 'good_role'
|
|
271
|
-
);
|
|
272
|
-
end;
|
|
273
|
-
$$;
|
|
274
|
-
|
|
275
|
-
create policy "rls_test_select"
|
|
276
|
-
on public.test_table for select
|
|
277
|
-
to authenticated
|
|
278
|
-
using ((select private.has_good_role()));
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
## DEFENSE-05: Views com `security_invoker=true` (Postgres 15+)
|
|
282
|
-
|
|
283
|
-
Por padrão, views são criadas como `SECURITY DEFINER` — rodam com permissões do criador (geralmente `postgres`). Resultado: **views bypassam RLS** das tabelas subjacentes. Atacante pode contornar policies acessando a view ao invés da tabela.
|
|
284
|
-
|
|
285
|
-
### Postgres 15+: `security_invoker=true`
|
|
286
|
-
|
|
287
|
-
```sql
|
|
288
|
-
-- view respeita RLS do role chamador (anon ou authenticated)
|
|
289
|
-
create view public.user_active_tasks
|
|
290
|
-
with (security_invoker = true)
|
|
291
|
-
as
|
|
292
|
-
select id, title, status, created_at
|
|
293
|
-
from public.tasks
|
|
294
|
-
where status = 'active';
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
Agora `select * from user_active_tasks` aplica policies de `public.tasks` baseadas no `auth.uid()` do caller.
|
|
298
|
-
|
|
299
|
-
### Postgres < 15: revoke ou schema privado
|
|
300
|
-
|
|
301
|
-
Em versões anteriores, `security_invoker` não está disponível. Mitigação:
|
|
302
|
-
|
|
303
|
-
```sql
|
|
304
|
-
-- alternativa 1: revoke acesso de roles expostos
|
|
305
|
-
revoke select on public.legacy_view from anon, authenticated;
|
|
306
|
-
grant select on public.legacy_view to service_role; -- apenas backend
|
|
307
|
-
|
|
308
|
-
-- alternativa 2: mover view para schema privado (não exposto via API)
|
|
309
|
-
drop view if exists public.legacy_view;
|
|
310
|
-
create view private.legacy_view as ...;
|
|
311
|
-
-- não conceder a anon/authenticated; expor via RPC se necessário
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
### Auditoria — encontrar views vulneráveis
|
|
315
|
-
|
|
316
|
-
```sql
|
|
317
|
-
-- views sem security_invoker em schemas expostos (Postgres 15+)
|
|
318
|
-
select schemaname, viewname
|
|
319
|
-
from pg_views v
|
|
320
|
-
where schemaname = 'public'
|
|
321
|
-
and not exists (
|
|
322
|
-
select 1 from pg_class c
|
|
323
|
-
join pg_namespace n on n.oid = c.relnamespace
|
|
324
|
-
where n.nspname = v.schemaname
|
|
325
|
-
and c.relname = v.viewname
|
|
326
|
-
and c.reloptions::text like '%security_invoker=true%'
|
|
327
|
-
);
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
## DEFENSE-06 (v1.24): Column-Level Privileges para PII/audit/billing/tokens
|
|
331
|
-
|
|
332
|
-
Em tabelas com colunas sensíveis (PII em compliance LGPD/GDPR, audit log payload, billing data, tokens raw), aplique column-level privileges como **Camada 8** de defense-in-depth.
|
|
333
|
-
|
|
334
|
-
### Quando aplicar
|
|
335
|
-
|
|
336
|
-
Use o checklist da skill [`supabase-column-level-security`](../supabase-column-level-security/SKILL.md):
|
|
337
|
-
|
|
338
|
-
- **PII compliance:** SSN, CPF, salary, medical info
|
|
339
|
-
- **Audit log sanitization:** `audit_log.payload` jsonb (legível só por security_admin role)
|
|
340
|
-
- **Billing data:** `credit_card_token`, `bank_account`
|
|
341
|
-
- **Tokens raw:** `org_invites.token_raw` (apenas service_role pós-create)
|
|
342
|
-
|
|
343
|
-
### Pattern canônico
|
|
344
|
-
|
|
345
|
-
```sql
|
|
346
|
-
-- 1. REVOKE table-level (perde acesso a TODAS colunas)
|
|
347
|
-
revoke select on table public.audit_log from authenticated;
|
|
348
|
-
|
|
349
|
-
-- 2. GRANT column-level apenas em colunas não-sensíveis
|
|
350
|
-
grant select (id, event_type, user_id, org_id, occurred_at)
|
|
351
|
-
on table public.audit_log to authenticated;
|
|
352
|
-
|
|
353
|
-
-- 3. service_role ou security_admin role mantém acesso total
|
|
354
|
-
grant select on table public.audit_log to service_role;
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
### Caveat crítico — Wildcard `*` restriction
|
|
358
|
-
|
|
359
|
-
Com column privileges, **`SELECT *` falha** — clientes devem listar colunas explicitamente:
|
|
360
|
-
|
|
361
|
-
```js
|
|
362
|
-
// ❌ FALHA — wildcard expansion bate em colunas sem permission
|
|
363
|
-
const { data } = supabase.from('audit_log').select()
|
|
364
|
-
|
|
365
|
-
// ✅ OK — colunas explicitamente listadas
|
|
366
|
-
const { data } = supabase.from('audit_log').select('id, event_type, user_id, org_id, occurred_at')
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
### Auditoria — detectar tabelas com PII sem column-level
|
|
370
|
-
|
|
371
|
-
```sql
|
|
372
|
-
-- detectar colunas potencialmente sensíveis sem column-level GRANT/REVOKE
|
|
373
|
-
select c.table_schema, c.table_name, c.column_name, c.data_type
|
|
374
|
-
from information_schema.columns c
|
|
375
|
-
where c.table_schema = 'public'
|
|
376
|
-
and (
|
|
377
|
-
c.column_name ilike any (array[
|
|
378
|
-
'%email%', '%phone%', '%ssn%', '%cpf%', '%token%',
|
|
379
|
-
'%password%', '%credit_card%', '%bank_account%', '%salary%'
|
|
380
|
-
])
|
|
381
|
-
)
|
|
382
|
-
and not exists (
|
|
383
|
-
select 1 from information_schema.column_privileges p
|
|
384
|
-
where p.table_schema = c.table_schema
|
|
385
|
-
and p.table_name = c.table_name
|
|
386
|
-
and p.column_name = c.column_name
|
|
387
|
-
);
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
Cross-ref auditoria sistemática em agent [`supabase-rls-hardener`](../../agents/supabase-rls-hardener.md) Detector 8 (v1.24) — invoca [`supabase-column-privileges-writer`](../../agents/supabase-column-privileges-writer.md) cooperativamente quando detecta gap.
|
|
391
|
-
|
|
392
|
-
## Resumo — checklist defense-in-depth (8 itens, v1.24)
|
|
393
|
-
|
|
394
|
-
Use este checklist ao validar projetos Supabase em produção:
|
|
395
|
-
|
|
396
|
-
- [ ] **DEFENSE-01:** Event trigger `rls_auto_enable` instalado em `ddl_command_end` cobrindo `CREATE TABLE` em schema `public`.
|
|
397
|
-
- [ ] **DEFENSE-02:** Roles admin internos usam `BYPASSRLS` privilege ao invés de service_role API key em scripts/cron jobs.
|
|
398
|
-
- [ ] **DEFENSE-03:** Edge Functions cross-tenant usam admin client separado (`persistSession: false`) ao invés de service_role com sessão user ativa.
|
|
399
|
-
- [ ] **DEFENSE-04:** Lógica admin/cross-tenant encapsulada em funções `SECURITY DEFINER` em schema `private` (não-exposto), com `SET search_path = ''` + input validation + audit log.
|
|
400
|
-
- [ ] **DEFENSE-05:** Views em Postgres 15+ usam `with (security_invoker = true)`; em versões anteriores, revoke acesso de `anon`/`authenticated` ou mover para schema privado.
|
|
401
|
-
- [ ] **GRANT explícito** antes de ENABLE RLS em todas tabelas — sem `grant select to authenticated`, queries falham antes mesmo da policy.
|
|
402
|
-
- [ ] **Cooperative handoff** — qualquer agent/skill/command produzindo SQL passa pelo `supabase-rls-hardener` (v1.23) antes do output final.
|
|
403
|
-
- [ ] **DEFENSE-06 (v1.24):** Column-Level Privileges em tabelas com PII/audit payload/billing/tokens — REVOKE table-level + GRANT column-level granular; clientes listam colunas explicitamente (não `select *`).
|
|
404
|
-
- [ ] **DEFENSE-07 (v1.25):** RBAC via Custom Access Token Auth Hook — `user_role` injetado no JWT durante geração do token; RLS policies consultam claim via `authorize()` function ao invés de JOIN em user_roles; `supabase_auth_admin` tem GRANT EXECUTE no hook + GRANT ALL em user_roles; revogação de role força logout via `auth.admin.signOut()` para invalidação imediata.
|
|
405
|
-
- [ ] **DEFENSE-08 (v1.26):** Postgres Roles Hierarchy — service accounts internos (cron jobs, BI tools, ETL, admin scripts) usam Postgres roles dedicados em vez de service_role API key; custom roles com BYPASSRLS específicos (`security_admin`, `dpo_role`, `lead_manager`, `platform_admin`); role hierarchy via INHERIT/NOINHERIT para multi-level permissions; pg_stat_statements audit por role; cross-ref skill `supabase-postgres-roles` v1.26 para 10 predefined Supabase roles documentados.
|
|
406
|
-
|
|
407
|
-
## Cross-suite handoff cooperativo (v1.23)
|
|
408
|
-
|
|
409
|
-
Esta skill é base para o agent `supabase-rls-hardener` (criado em Phase 126 do v1.23) — que recebe draft SQL via `Task()` upstream context, valida os 6 itens do checklist defense-in-depth acima, e devolve verdict GO/STRENGTHEN/REWRITE-com-confirmação.
|
|
410
|
-
|
|
411
|
-
Princípio canônico v1.23: **agents não-Supabase pensam/planejam; agents Supabase materializam/hardenam; ninguém descarta upstream**. Esta skill consolida o conhecimento que o hardener aplica.
|
|
412
|
-
|
|
413
|
-
## Ver também
|
|
414
|
-
|
|
415
|
-
- [supabase-rls-policies](../supabase-rls-policies/SKILL.md) — Camada 1 (policies granulares + IS NOT NULL + GRANT) + Performance recommendations
|
|
416
|
-
- [supabase-migrations](../supabase-migrations/SKILL.md) — Template canônico v1.23 com 5 blocos obrigatórios (incluindo GRANT + ENABLE RLS + policies + index)
|
|
417
|
-
- [supabase-database-functions](../supabase-database-functions/SKILL.md) — funções `SECURITY INVOKER` (default seguro) vs `SECURITY DEFINER` (com justificativa)
|
|
418
|
-
- [glossário compartilhado](../_shared-supabase/glossary.md) — termos defense-in-depth, hardener, cooperative-handoff, event-trigger-rls-auto-enable, bypassrls, security_invoker
|
|
1
|
+
---
|
|
2
|
+
name: supabase-rls-defense-in-depth
|
|
3
|
+
description: Use ao desenhar defense-in-depth RLS em Supabase — event trigger rls_auto_enable (default projetos novos), BYPASSRLS role privilege, service_role caveat, security definer functions como bypass co…
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Supabase — RLS Defense in Depth
|
|
7
|
+
|
|
8
|
+
## Quando usar
|
|
9
|
+
|
|
10
|
+
LLM carrega esta skill quando precisar desenhar **camadas de defesa** RLS além das policies básicas. Trigger phrases:
|
|
11
|
+
|
|
12
|
+
- "defense in depth RLS", "camadas de defesa Postgres"
|
|
13
|
+
- "auto-enable RLS em todas tabelas novas", "event trigger ensure RLS"
|
|
14
|
+
- "BYPASSRLS role privilege", "alter role bypassrls"
|
|
15
|
+
- "security definer function bypassa RLS"
|
|
16
|
+
- "service_role bypassa RLS", "service_role caveat"
|
|
17
|
+
- "view security_invoker", "view bypass RLS"
|
|
18
|
+
- "como proteger contra third-party tools acessando DB direto"
|
|
19
|
+
|
|
20
|
+
## Princípio canônico
|
|
21
|
+
|
|
22
|
+
**Defense in depth** = múltiplas camadas independentes de proteção. Para RLS em Supabase:
|
|
23
|
+
|
|
24
|
+
1. **Camada 1 — Policy explícita por tabela** (skill `supabase-rls-policies`) — sempre granular, sempre `(select auth.uid())`, sempre `IS NOT NULL`, sempre indices.
|
|
25
|
+
2. **Camada 2 — Auto-enable RLS via event trigger** (DEFENSE-01) — garante que toda tabela nova nasce com RLS habilitado, mesmo se developer esquecer.
|
|
26
|
+
3. **Camada 3 — GRANT explícito** (skill `supabase-rls-policies`) — sem `grant select to authenticated`, queries falham com "permission denied" antes mesmo de chegar nas policies.
|
|
27
|
+
4. **Camada 4 — Bypass controlado** — `BYPASSRLS` role, `security definer` functions em schema `private` (DEFENSE-02, DEFENSE-04). Nunca em schema exposto.
|
|
28
|
+
5. **Camada 5 — Views com `security_invoker=true`** (DEFENSE-05, Postgres 15+) — views respeitam RLS do role chamador, não do criador.
|
|
29
|
+
6. **Camada 6 — Service role caveat** (DEFENSE-03) — entender que service_role bypassa RLS mas só no servidor; nunca expor ao cliente.
|
|
30
|
+
7. **Camada 7 — Cooperative handoff via supabase-rls-hardener** (v1.23) — todo SQL gerado pelo kit passa pelo hardener canonical antes do output final. Verdicts GO/STRENGTHEN/REWRITE-com-confirmação. Princípio canônico: agents externos pensam/planejam; agents Supabase materializam/hardenam.
|
|
31
|
+
8. **Camada 8 — Column-Level Privileges** (v1.24) — `GRANT/REVOKE (col1, col2) ON TABLE` para restringir colunas sensíveis (PII, audit payload, billing, tokens). Feature AVANÇADA — usar apenas quando RLS + dedicated role table não cobrem o caso. Cross-ref skill [`supabase-column-level-security`](../supabase-column-level-security/SKILL.md).
|
|
32
|
+
9. **Camada 9 — Auth Hooks - Custom Claims** (v1.25) — `Custom Access Token Auth Hook` (função PG `custom_access_token_hook(event jsonb)`) injeta `user_role` no JWT durante geração do token. RLS policies consultam o claim direto via `auth.jwt() ->> 'user_role'` ou via `authorize()` function — zero-JOIN, type-safe via enum, composable. Alternativa moderna a dedicated role table com JOIN custoso em policies. Caveat JWT freshness (eventually consistent). Cross-ref skill [`supabase-custom-claims-rbac`](../supabase-custom-claims-rbac/SKILL.md).
|
|
33
|
+
10. **Camada 10 — Postgres Roles Hierarchy** (v1.26) — Postgres roles dedicados para **system access** (service accounts, cron jobs, BI tools, ETL, admin scripts) em vez de service_role API key sempre. Auditabilidade superior (queries logam por role no `pg_stat_statements`), Role hierarchy via INHERIT/NOINHERIT, custom roles com BYPASSRLS específicos (`security_admin`, `dpo_role`, `lead_manager`, `platform_admin`). Distinção canônica vs application access (RLS + Custom Claims). Cross-ref skill [`supabase-postgres-roles`](../supabase-postgres-roles/SKILL.md).
|
|
34
|
+
|
|
35
|
+
Razão: RLS é a primeira linha, mas humanos esquecem. Third-party tooling (Metabase, dbt, ferramentas BI conectadas via JDBC, scripts) bypassam toda a lógica da camada de aplicação — só RLS no banco protege. Defense in depth aplica princípio de **proteção sobreposta** para resiliência.
|
|
36
|
+
|
|
37
|
+
## DEFENSE-01: Event trigger `rls_auto_enable()` como default
|
|
38
|
+
|
|
39
|
+
Em projetos novos, instale event trigger que ativa RLS automaticamente quando uma tabela é criada em schema exposto. Protege contra esquecimento humano.
|
|
40
|
+
|
|
41
|
+
### Função PLpgSQL + Event Trigger
|
|
42
|
+
|
|
43
|
+
```sql
|
|
44
|
+
create or replace function rls_auto_enable()
|
|
45
|
+
returns event_trigger
|
|
46
|
+
language plpgsql
|
|
47
|
+
security definer
|
|
48
|
+
set search_path = pg_catalog
|
|
49
|
+
as $$
|
|
50
|
+
declare
|
|
51
|
+
cmd record;
|
|
52
|
+
begin
|
|
53
|
+
for cmd in
|
|
54
|
+
select *
|
|
55
|
+
from pg_event_trigger_ddl_commands()
|
|
56
|
+
where command_tag in ('CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO')
|
|
57
|
+
and object_type in ('table','partitioned table')
|
|
58
|
+
loop
|
|
59
|
+
-- só atua em schemas configurados; pula sistema
|
|
60
|
+
if cmd.schema_name is not null
|
|
61
|
+
and cmd.schema_name in ('public')
|
|
62
|
+
and cmd.schema_name not in ('pg_catalog','information_schema')
|
|
63
|
+
and cmd.schema_name not like 'pg_toast%'
|
|
64
|
+
and cmd.schema_name not like 'pg_temp%' then
|
|
65
|
+
begin
|
|
66
|
+
execute format('alter table if exists %s enable row level security', cmd.object_identity);
|
|
67
|
+
raise log 'rls_auto_enable: enabled RLS on %', cmd.object_identity;
|
|
68
|
+
exception
|
|
69
|
+
when others then
|
|
70
|
+
raise log 'rls_auto_enable: failed to enable RLS on %', cmd.object_identity;
|
|
71
|
+
end;
|
|
72
|
+
else
|
|
73
|
+
raise log 'rls_auto_enable: skip % (system schema or not in enforced list)', cmd.object_identity;
|
|
74
|
+
end if;
|
|
75
|
+
end loop;
|
|
76
|
+
end;
|
|
77
|
+
$$;
|
|
78
|
+
|
|
79
|
+
drop event trigger if exists ensure_rls;
|
|
80
|
+
create event trigger ensure_rls
|
|
81
|
+
on ddl_command_end
|
|
82
|
+
when tag in ('CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO')
|
|
83
|
+
execute function rls_auto_enable();
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Caveats e limitações
|
|
87
|
+
|
|
88
|
+
- **Aplica-se a tabelas criadas após o trigger ser instalado** — tabelas pré-existentes precisam de `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` manual ou script de migração.
|
|
89
|
+
- **Schemas filtrados** — só atua em schemas listados explicitamente (`public` por default). Para incluir mais schemas, edite a lista. Para projetos multi-tenant em schemas separados por org, adicione `('public', 'org_*')` ou similar.
|
|
90
|
+
- **Não cria policies** — apenas habilita RLS. Sem policies, tabela fica bloqueada para roles não-bypass — comportamento desejado (failsafe).
|
|
91
|
+
- **Não dispara em `CREATE TABLE IF NOT EXISTS` quando tabela já existe** — comportamento padrão Postgres event triggers.
|
|
92
|
+
- **Trigger é `SECURITY DEFINER`** — roda como o owner do trigger (geralmente `postgres`). Garante permissão para `ALTER TABLE`.
|
|
93
|
+
|
|
94
|
+
### Auditoria — listar tabelas sem RLS
|
|
95
|
+
|
|
96
|
+
Para validar que defense-in-depth está aplicado:
|
|
97
|
+
|
|
98
|
+
```sql
|
|
99
|
+
select schemaname, tablename
|
|
100
|
+
from pg_tables
|
|
101
|
+
where schemaname = 'public'
|
|
102
|
+
and not exists (
|
|
103
|
+
select 1 from pg_class c
|
|
104
|
+
join pg_namespace n on n.oid = c.relnamespace
|
|
105
|
+
where n.nspname = pg_tables.schemaname
|
|
106
|
+
and c.relname = pg_tables.tablename
|
|
107
|
+
and c.relrowsecurity = true
|
|
108
|
+
);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Se retorna ≥ 1 row, há tabelas sem RLS — gap de defense-in-depth.
|
|
112
|
+
|
|
113
|
+
## DEFENSE-02: `BYPASSRLS` role privilege para tarefas admin
|
|
114
|
+
|
|
115
|
+
Para tarefas administrativas internas (jobs noturnos, migrations, scripts de manutenção), crie role com privilégio `BYPASSRLS` ao invés de usar `service_role`:
|
|
116
|
+
|
|
117
|
+
```sql
|
|
118
|
+
-- criar role interno
|
|
119
|
+
create role admin_internal with login password '<strong_pw>';
|
|
120
|
+
alter role admin_internal with bypassrls;
|
|
121
|
+
|
|
122
|
+
-- conceder permissões necessárias
|
|
123
|
+
grant all on all tables in schema public to admin_internal;
|
|
124
|
+
grant all on all sequences in schema public to admin_internal;
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Quando usar:**
|
|
128
|
+
|
|
129
|
+
- Scripts de manutenção que rodam dentro do banco (não via API)
|
|
130
|
+
- Jobs `pg_cron` que precisam acessar dados cross-tenant
|
|
131
|
+
- Migrations que populam dados em massa ignorando policies
|
|
132
|
+
- DBAs investigando incidents em production
|
|
133
|
+
|
|
134
|
+
**Quando NÃO usar:**
|
|
135
|
+
|
|
136
|
+
- Edge Functions — use `SUPABASE_SERVICE_ROLE_KEY` direto (já tem BYPASSRLS implícito)
|
|
137
|
+
- Backend customizado conectado via JDBC — use service_role role do Supabase
|
|
138
|
+
- Qualquer role que receba requisição de cliente
|
|
139
|
+
|
|
140
|
+
**Comparação com service_role:**
|
|
141
|
+
|
|
142
|
+
| | `service_role` (Supabase API key) | Custom role `BYPASSRLS` |
|
|
143
|
+
|---|---|---|
|
|
144
|
+
| Escopo | API key — usado em Authorization header | Postgres role — login direto |
|
|
145
|
+
| Auditoria | Logs do Supabase API | Postgres audit log (pg_audit) |
|
|
146
|
+
| Revoke | Rotacionar key via dashboard | `revoke bypassrls from <role>` |
|
|
147
|
+
| Granularidade | Tudo-ou-nada | Pode escopar GRANT a tabelas específicas |
|
|
148
|
+
|
|
149
|
+
## DEFENSE-03: `service_role` caveat — não bypassa RLS do user logged-in
|
|
150
|
+
|
|
151
|
+
**Caveat crítico:** quando você usa o SDK Supabase com `SUPABASE_SERVICE_ROLE_KEY`, mas a sessão ainda tem `Authorization: Bearer <user_jwt>` set, **o RLS do user é aplicado** — o service_role bypass é **overridden**.
|
|
152
|
+
|
|
153
|
+
```js
|
|
154
|
+
// errado — service_role mas com sessão user ainda ativa
|
|
155
|
+
const supabase = createClient(URL, SERVICE_ROLE_KEY)
|
|
156
|
+
supabase.auth.setSession({ access_token: userJwt, refresh_token: userRefresh })
|
|
157
|
+
// agora .from('tasks').select() aplica RLS do user, não bypass
|
|
158
|
+
|
|
159
|
+
// certo — service_role limpo
|
|
160
|
+
const supabase = createClient(URL, SERVICE_ROLE_KEY, {
|
|
161
|
+
auth: { persistSession: false, autoRefreshToken: false }
|
|
162
|
+
})
|
|
163
|
+
// agora .from('tasks').select() bypassa RLS
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Como Supabase decide:** se a request tem JWT válido em Authorization header, Supabase deriva o `role` (anon/authenticated) e aplica RLS. Service_role só bypassa se NÃO há JWT user OU se você usa o pattern admin client com `persistSession: false`.
|
|
167
|
+
|
|
168
|
+
**Aplicação:** Edge Functions que precisam fazer trabalho cross-tenant **devem** usar admin client separado (não o client da sessão do user que disparou a função):
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
// edge function
|
|
172
|
+
import { createClient } from 'npm:@supabase/supabase-js'
|
|
173
|
+
|
|
174
|
+
Deno.serve(async (req) => {
|
|
175
|
+
// admin client — bypassa RLS porque sem session
|
|
176
|
+
const adminDb = createClient(
|
|
177
|
+
Deno.env.get('SUPABASE_URL')!,
|
|
178
|
+
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!,
|
|
179
|
+
{ auth: { persistSession: false, autoRefreshToken: false } }
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
// user client — respeita RLS (para audit log do que user PODE fazer)
|
|
183
|
+
const userDb = createClient(
|
|
184
|
+
Deno.env.get('SUPABASE_URL')!,
|
|
185
|
+
Deno.env.get('SUPABASE_ANON_KEY')!,
|
|
186
|
+
{ global: { headers: { Authorization: req.headers.get('Authorization')! } } }
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
// exemplo: validar com userDb (RLS aplicado) que user pode acessar org
|
|
190
|
+
const { data: orgCheck } = await userDb.from('organizations').select('id').eq('id', orgId).single()
|
|
191
|
+
if (!orgCheck) return new Response('Forbidden', { status: 403 })
|
|
192
|
+
|
|
193
|
+
// executar mutation cross-tenant com adminDb (bypass RLS)
|
|
194
|
+
const { data } = await adminDb.from('audit_log').insert({ ... })
|
|
195
|
+
return new Response(JSON.stringify(data))
|
|
196
|
+
})
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## DEFENSE-04: `SECURITY DEFINER` functions como bypass controlado
|
|
200
|
+
|
|
201
|
+
Funções `SECURITY DEFINER` rodam com permissões do owner (geralmente `postgres`, que tem `BYPASSRLS`). Use para encapsular lógica admin/cross-tenant que precisa bypassar RLS.
|
|
202
|
+
|
|
203
|
+
### Regras absolutas
|
|
204
|
+
|
|
205
|
+
1. **NUNCA em schema exposto** (`public`) — atacante poderia chamar a função com input arbitrário via REST API. Sempre em schema `private`, `internal`, ou similar **não-exposto** em API settings.
|
|
206
|
+
2. **Sempre `SET search_path = ''`** — evita schema injection (atacante criando função homônima em outro schema).
|
|
207
|
+
3. **Validar inputs** — função `SECURITY DEFINER` é alta autoridade; valide tudo (uuid format, range, ownership) antes de fazer trabalho.
|
|
208
|
+
4. **Auditar invocações** — log quem chamou + o quê + quando.
|
|
209
|
+
|
|
210
|
+
### Example: cross-tenant analytics aggregation
|
|
211
|
+
|
|
212
|
+
```sql
|
|
213
|
+
-- schema private (NÃO exposto via API)
|
|
214
|
+
create schema if not exists private;
|
|
215
|
+
|
|
216
|
+
create or replace function private.org_analytics_summary(org_id_arg uuid)
|
|
217
|
+
returns table(metric text, value bigint)
|
|
218
|
+
language plpgsql
|
|
219
|
+
security definer
|
|
220
|
+
set search_path = ''
|
|
221
|
+
as $$
|
|
222
|
+
begin
|
|
223
|
+
-- validar input (uuid format, ownership do caller, etc.)
|
|
224
|
+
if org_id_arg is null then
|
|
225
|
+
raise exception 'org_id_arg required';
|
|
226
|
+
end if;
|
|
227
|
+
|
|
228
|
+
-- validar que caller pode acessar essa org
|
|
229
|
+
-- (mesmo bypassando RLS, regras de negócio aplicam)
|
|
230
|
+
if not exists (
|
|
231
|
+
select 1 from public.organization_members
|
|
232
|
+
where org_id = org_id_arg and user_id = auth.uid()
|
|
233
|
+
) then
|
|
234
|
+
raise exception 'access denied to org %', org_id_arg;
|
|
235
|
+
end if;
|
|
236
|
+
|
|
237
|
+
-- audit log
|
|
238
|
+
insert into public.audit_log (event, user_id, org_id, payload)
|
|
239
|
+
values ('analytics_summary_query', auth.uid(), org_id_arg, jsonb_build_object('ts', now()));
|
|
240
|
+
|
|
241
|
+
-- query bypass RLS
|
|
242
|
+
return query
|
|
243
|
+
select 'total_tasks' as metric, count(*)::bigint as value
|
|
244
|
+
from public.tasks
|
|
245
|
+
where org_id = org_id_arg
|
|
246
|
+
union all
|
|
247
|
+
select 'total_members', count(*)::bigint
|
|
248
|
+
from public.organization_members
|
|
249
|
+
where org_id = org_id_arg;
|
|
250
|
+
end;
|
|
251
|
+
$$;
|
|
252
|
+
|
|
253
|
+
-- expor via RPC (RLS check feito DENTRO da função, não via policy)
|
|
254
|
+
grant execute on function private.org_analytics_summary(uuid) to authenticated;
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Padrão de uso em policy:** funções `SECURITY DEFINER` podem ser chamadas em policies para fazer JOIN performant sem aplicar RLS recursivamente:
|
|
258
|
+
|
|
259
|
+
```sql
|
|
260
|
+
-- ver supabase-rls-policies Performance section #5
|
|
261
|
+
create function private.has_good_role()
|
|
262
|
+
returns boolean
|
|
263
|
+
language plpgsql
|
|
264
|
+
security definer
|
|
265
|
+
set search_path = ''
|
|
266
|
+
as $$
|
|
267
|
+
begin
|
|
268
|
+
return exists (
|
|
269
|
+
select 1 from public.roles_table
|
|
270
|
+
where (select auth.uid()) = user_id and role = 'good_role'
|
|
271
|
+
);
|
|
272
|
+
end;
|
|
273
|
+
$$;
|
|
274
|
+
|
|
275
|
+
create policy "rls_test_select"
|
|
276
|
+
on public.test_table for select
|
|
277
|
+
to authenticated
|
|
278
|
+
using ((select private.has_good_role()));
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## DEFENSE-05: Views com `security_invoker=true` (Postgres 15+)
|
|
282
|
+
|
|
283
|
+
Por padrão, views são criadas como `SECURITY DEFINER` — rodam com permissões do criador (geralmente `postgres`). Resultado: **views bypassam RLS** das tabelas subjacentes. Atacante pode contornar policies acessando a view ao invés da tabela.
|
|
284
|
+
|
|
285
|
+
### Postgres 15+: `security_invoker=true`
|
|
286
|
+
|
|
287
|
+
```sql
|
|
288
|
+
-- view respeita RLS do role chamador (anon ou authenticated)
|
|
289
|
+
create view public.user_active_tasks
|
|
290
|
+
with (security_invoker = true)
|
|
291
|
+
as
|
|
292
|
+
select id, title, status, created_at
|
|
293
|
+
from public.tasks
|
|
294
|
+
where status = 'active';
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Agora `select * from user_active_tasks` aplica policies de `public.tasks` baseadas no `auth.uid()` do caller.
|
|
298
|
+
|
|
299
|
+
### Postgres < 15: revoke ou schema privado
|
|
300
|
+
|
|
301
|
+
Em versões anteriores, `security_invoker` não está disponível. Mitigação:
|
|
302
|
+
|
|
303
|
+
```sql
|
|
304
|
+
-- alternativa 1: revoke acesso de roles expostos
|
|
305
|
+
revoke select on public.legacy_view from anon, authenticated;
|
|
306
|
+
grant select on public.legacy_view to service_role; -- apenas backend
|
|
307
|
+
|
|
308
|
+
-- alternativa 2: mover view para schema privado (não exposto via API)
|
|
309
|
+
drop view if exists public.legacy_view;
|
|
310
|
+
create view private.legacy_view as ...;
|
|
311
|
+
-- não conceder a anon/authenticated; expor via RPC se necessário
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Auditoria — encontrar views vulneráveis
|
|
315
|
+
|
|
316
|
+
```sql
|
|
317
|
+
-- views sem security_invoker em schemas expostos (Postgres 15+)
|
|
318
|
+
select schemaname, viewname
|
|
319
|
+
from pg_views v
|
|
320
|
+
where schemaname = 'public'
|
|
321
|
+
and not exists (
|
|
322
|
+
select 1 from pg_class c
|
|
323
|
+
join pg_namespace n on n.oid = c.relnamespace
|
|
324
|
+
where n.nspname = v.schemaname
|
|
325
|
+
and c.relname = v.viewname
|
|
326
|
+
and c.reloptions::text like '%security_invoker=true%'
|
|
327
|
+
);
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## DEFENSE-06 (v1.24): Column-Level Privileges para PII/audit/billing/tokens
|
|
331
|
+
|
|
332
|
+
Em tabelas com colunas sensíveis (PII em compliance LGPD/GDPR, audit log payload, billing data, tokens raw), aplique column-level privileges como **Camada 8** de defense-in-depth.
|
|
333
|
+
|
|
334
|
+
### Quando aplicar
|
|
335
|
+
|
|
336
|
+
Use o checklist da skill [`supabase-column-level-security`](../supabase-column-level-security/SKILL.md):
|
|
337
|
+
|
|
338
|
+
- **PII compliance:** SSN, CPF, salary, medical info
|
|
339
|
+
- **Audit log sanitization:** `audit_log.payload` jsonb (legível só por security_admin role)
|
|
340
|
+
- **Billing data:** `credit_card_token`, `bank_account`
|
|
341
|
+
- **Tokens raw:** `org_invites.token_raw` (apenas service_role pós-create)
|
|
342
|
+
|
|
343
|
+
### Pattern canônico
|
|
344
|
+
|
|
345
|
+
```sql
|
|
346
|
+
-- 1. REVOKE table-level (perde acesso a TODAS colunas)
|
|
347
|
+
revoke select on table public.audit_log from authenticated;
|
|
348
|
+
|
|
349
|
+
-- 2. GRANT column-level apenas em colunas não-sensíveis
|
|
350
|
+
grant select (id, event_type, user_id, org_id, occurred_at)
|
|
351
|
+
on table public.audit_log to authenticated;
|
|
352
|
+
|
|
353
|
+
-- 3. service_role ou security_admin role mantém acesso total
|
|
354
|
+
grant select on table public.audit_log to service_role;
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Caveat crítico — Wildcard `*` restriction
|
|
358
|
+
|
|
359
|
+
Com column privileges, **`SELECT *` falha** — clientes devem listar colunas explicitamente:
|
|
360
|
+
|
|
361
|
+
```js
|
|
362
|
+
// ❌ FALHA — wildcard expansion bate em colunas sem permission
|
|
363
|
+
const { data } = supabase.from('audit_log').select()
|
|
364
|
+
|
|
365
|
+
// ✅ OK — colunas explicitamente listadas
|
|
366
|
+
const { data } = supabase.from('audit_log').select('id, event_type, user_id, org_id, occurred_at')
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Auditoria — detectar tabelas com PII sem column-level
|
|
370
|
+
|
|
371
|
+
```sql
|
|
372
|
+
-- detectar colunas potencialmente sensíveis sem column-level GRANT/REVOKE
|
|
373
|
+
select c.table_schema, c.table_name, c.column_name, c.data_type
|
|
374
|
+
from information_schema.columns c
|
|
375
|
+
where c.table_schema = 'public'
|
|
376
|
+
and (
|
|
377
|
+
c.column_name ilike any (array[
|
|
378
|
+
'%email%', '%phone%', '%ssn%', '%cpf%', '%token%',
|
|
379
|
+
'%password%', '%credit_card%', '%bank_account%', '%salary%'
|
|
380
|
+
])
|
|
381
|
+
)
|
|
382
|
+
and not exists (
|
|
383
|
+
select 1 from information_schema.column_privileges p
|
|
384
|
+
where p.table_schema = c.table_schema
|
|
385
|
+
and p.table_name = c.table_name
|
|
386
|
+
and p.column_name = c.column_name
|
|
387
|
+
);
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Cross-ref auditoria sistemática em agent [`supabase-rls-hardener`](../../agents/supabase-rls-hardener.md) Detector 8 (v1.24) — invoca [`supabase-column-privileges-writer`](../../agents/supabase-column-privileges-writer.md) cooperativamente quando detecta gap.
|
|
391
|
+
|
|
392
|
+
## Resumo — checklist defense-in-depth (8 itens, v1.24)
|
|
393
|
+
|
|
394
|
+
Use este checklist ao validar projetos Supabase em produção:
|
|
395
|
+
|
|
396
|
+
- [ ] **DEFENSE-01:** Event trigger `rls_auto_enable` instalado em `ddl_command_end` cobrindo `CREATE TABLE` em schema `public`.
|
|
397
|
+
- [ ] **DEFENSE-02:** Roles admin internos usam `BYPASSRLS` privilege ao invés de service_role API key em scripts/cron jobs.
|
|
398
|
+
- [ ] **DEFENSE-03:** Edge Functions cross-tenant usam admin client separado (`persistSession: false`) ao invés de service_role com sessão user ativa.
|
|
399
|
+
- [ ] **DEFENSE-04:** Lógica admin/cross-tenant encapsulada em funções `SECURITY DEFINER` em schema `private` (não-exposto), com `SET search_path = ''` + input validation + audit log.
|
|
400
|
+
- [ ] **DEFENSE-05:** Views em Postgres 15+ usam `with (security_invoker = true)`; em versões anteriores, revoke acesso de `anon`/`authenticated` ou mover para schema privado.
|
|
401
|
+
- [ ] **GRANT explícito** antes de ENABLE RLS em todas tabelas — sem `grant select to authenticated`, queries falham antes mesmo da policy.
|
|
402
|
+
- [ ] **Cooperative handoff** — qualquer agent/skill/command produzindo SQL passa pelo `supabase-rls-hardener` (v1.23) antes do output final.
|
|
403
|
+
- [ ] **DEFENSE-06 (v1.24):** Column-Level Privileges em tabelas com PII/audit payload/billing/tokens — REVOKE table-level + GRANT column-level granular; clientes listam colunas explicitamente (não `select *`).
|
|
404
|
+
- [ ] **DEFENSE-07 (v1.25):** RBAC via Custom Access Token Auth Hook — `user_role` injetado no JWT durante geração do token; RLS policies consultam claim via `authorize()` function ao invés de JOIN em user_roles; `supabase_auth_admin` tem GRANT EXECUTE no hook + GRANT ALL em user_roles; revogação de role força logout via `auth.admin.signOut()` para invalidação imediata.
|
|
405
|
+
- [ ] **DEFENSE-08 (v1.26):** Postgres Roles Hierarchy — service accounts internos (cron jobs, BI tools, ETL, admin scripts) usam Postgres roles dedicados em vez de service_role API key; custom roles com BYPASSRLS específicos (`security_admin`, `dpo_role`, `lead_manager`, `platform_admin`); role hierarchy via INHERIT/NOINHERIT para multi-level permissions; pg_stat_statements audit por role; cross-ref skill `supabase-postgres-roles` v1.26 para 10 predefined Supabase roles documentados.
|
|
406
|
+
|
|
407
|
+
## Cross-suite handoff cooperativo (v1.23)
|
|
408
|
+
|
|
409
|
+
Esta skill é base para o agent `supabase-rls-hardener` (criado em Phase 126 do v1.23) — que recebe draft SQL via `Task()` upstream context, valida os 6 itens do checklist defense-in-depth acima, e devolve verdict GO/STRENGTHEN/REWRITE-com-confirmação.
|
|
410
|
+
|
|
411
|
+
Princípio canônico v1.23: **agents não-Supabase pensam/planejam; agents Supabase materializam/hardenam; ninguém descarta upstream**. Esta skill consolida o conhecimento que o hardener aplica.
|
|
412
|
+
|
|
413
|
+
## Ver também
|
|
414
|
+
|
|
415
|
+
- [supabase-rls-policies](../supabase-rls-policies/SKILL.md) — Camada 1 (policies granulares + IS NOT NULL + GRANT) + Performance recommendations
|
|
416
|
+
- [supabase-migrations](../supabase-migrations/SKILL.md) — Template canônico v1.23 com 5 blocos obrigatórios (incluindo GRANT + ENABLE RLS + policies + index)
|
|
417
|
+
- [supabase-database-functions](../supabase-database-functions/SKILL.md) — funções `SECURITY INVOKER` (default seguro) vs `SECURITY DEFINER` (com justificativa)
|
|
418
|
+
- [glossário compartilhado](../_shared-supabase/glossary.md) — termos defense-in-depth, hardener, cooperative-handoff, event-trigger-rls-auto-enable, bypassrls, security_invoker
|