@luanpdd/kit-mcp 1.30.2 → 1.32.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.
Files changed (365) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +168 -168
  3. package/gates/agent-no-recursive-dispatch.md +84 -82
  4. package/kit/COMANDOS.md +138 -138
  5. package/kit/COMPATIBILITY.md +5 -0
  6. package/kit/README.md +76 -76
  7. package/kit/agents/advisor-researcher.md +107 -106
  8. package/kit/agents/ai-mutation-tester.md +1 -0
  9. package/kit/agents/assumptions-analyzer.md +108 -107
  10. package/kit/agents/audit-log-implementer.md +314 -313
  11. package/kit/agents/auditor-consistencia-isolamento.md +414 -413
  12. package/kit/agents/b2b-saas-architect.md +157 -156
  13. package/kit/agents/burn-rate-forecaster.md +1 -0
  14. package/kit/agents/cascading-failures-auditor.md +299 -298
  15. package/kit/agents/codebase-mapper.md +769 -768
  16. package/kit/agents/crm-pipeline-implementer.md +257 -256
  17. package/kit/agents/debugger.md +814 -813
  18. package/kit/agents/detector-tenant-quente.md +338 -337
  19. package/kit/agents/evolution-go-integrator.md +201 -200
  20. package/kit/agents/example-reviewer.md +22 -21
  21. package/kit/agents/executor.md +565 -564
  22. package/kit/agents/golden-signals-instrumenter.md +1 -0
  23. package/kit/agents/incident-investigator.md +1 -0
  24. package/kit/agents/integration-checker.md +201 -200
  25. package/kit/agents/invite-flow-implementer.md +190 -189
  26. package/kit/agents/legacy-characterizer.md +369 -368
  27. package/kit/agents/lgpd-compliance-auditor.md +296 -295
  28. package/kit/agents/load-shedding-instrumenter.md +1 -0
  29. package/kit/agents/multi-tenant-isolation-auditor.md +254 -253
  30. package/kit/agents/multi-tenant-rls-writer.md +341 -340
  31. package/kit/agents/nyquist-auditor.md +179 -178
  32. package/kit/agents/observability-coverage-auditor.md +316 -315
  33. package/kit/agents/observability-instrumenter.md +1 -0
  34. package/kit/agents/omm-auditor.md +1 -0
  35. package/kit/agents/org-onboarding-implementer.md +224 -223
  36. package/kit/agents/payload-capture-instrumenter.md +274 -273
  37. package/kit/agents/phase-researcher.md +697 -696
  38. package/kit/agents/plan-checker.md +273 -272
  39. package/kit/agents/planner.md +923 -922
  40. package/kit/agents/postmortem-writer.md +1 -0
  41. package/kit/agents/project-researcher.md +653 -652
  42. package/kit/agents/prr-conductor.md +1 -0
  43. package/kit/agents/refactor-safety-auditor.md +405 -404
  44. package/kit/agents/release-pipeline-auditor.md +1 -0
  45. package/kit/agents/research-synthesizer.md +246 -245
  46. package/kit/agents/roadmapper.md +678 -677
  47. package/kit/agents/schema-checker.md +1 -0
  48. package/kit/agents/seam-finder.md +360 -359
  49. package/kit/agents/shotgun-surgery-detector.md +350 -349
  50. package/kit/agents/slo-engineer.md +1 -0
  51. package/kit/agents/storytelling-analyst.md +1 -0
  52. package/kit/agents/supabase-architect.md +1 -0
  53. package/kit/agents/supabase-auth-bootstrapper.md +16 -1
  54. package/kit/agents/supabase-auth-hook-writer.md +418 -0
  55. package/kit/agents/supabase-branching-architect.md +563 -562
  56. package/kit/agents/supabase-cicd-pipeline-implementer.md +778 -777
  57. package/kit/agents/supabase-column-privileges-writer.md +400 -399
  58. package/kit/agents/supabase-edge-fn-tester.md +2 -1
  59. package/kit/agents/supabase-edge-fn-writer.md +2 -1
  60. package/kit/agents/supabase-mfa-implementer.md +439 -0
  61. package/kit/agents/supabase-migration-writer.md +386 -385
  62. package/kit/agents/supabase-oauth-server-implementer.md +507 -0
  63. package/kit/agents/supabase-rbac-implementer.md +393 -392
  64. package/kit/agents/supabase-realtime-implementer.md +364 -363
  65. package/kit/agents/supabase-rls-hardener.md +522 -521
  66. package/kit/agents/supabase-rls-writer.md +324 -323
  67. package/kit/agents/supabase-roles-implementer.md +356 -355
  68. package/kit/agents/supabase-social-auth-implementer.md +451 -0
  69. package/kit/agents/supabase-sso-saml-architect.md +549 -0
  70. package/kit/agents/supabase-storage-implementer.md +1 -0
  71. package/kit/agents/super-admin-implementer.md +282 -281
  72. package/kit/agents/toil-auditor.md +1 -0
  73. package/kit/agents/ui-auditor.md +438 -437
  74. package/kit/agents/ui-checker.md +303 -302
  75. package/kit/agents/ui-researcher.md +356 -355
  76. package/kit/agents/user-profiler.md +176 -175
  77. package/kit/agents/validador-evolucao-schema.md +336 -335
  78. package/kit/agents/verifier.md +729 -728
  79. package/kit/commands/adicionar-backlog.md +75 -75
  80. package/kit/commands/adicionar-fase.md +42 -42
  81. package/kit/commands/adicionar-tarefa.md +45 -45
  82. package/kit/commands/adicionar-testes.md +41 -41
  83. package/kit/commands/ajuda.md +21 -21
  84. package/kit/commands/atualizar.md +37 -37
  85. package/kit/commands/auditar-cascading.md +111 -111
  86. package/kit/commands/auditar-marco.md +179 -179
  87. package/kit/commands/auditar-observabilidade-cobertura.md +183 -183
  88. package/kit/commands/auditar-refactor.md +219 -219
  89. package/kit/commands/auditar-release.md +109 -109
  90. package/kit/commands/auditar-uat.md +23 -23
  91. package/kit/commands/autonomo.md +40 -40
  92. package/kit/commands/branch-pr.md +24 -24
  93. package/kit/commands/burn-rate-status.md +408 -408
  94. package/kit/commands/capturar-payloads.md +193 -193
  95. package/kit/commands/caracterizar.md +212 -212
  96. package/kit/commands/concluir-marco.md +247 -247
  97. package/kit/commands/configuracoes.md +36 -36
  98. package/kit/commands/dados-distribuidos.md +188 -188
  99. package/kit/commands/definir-perfil.md +10 -10
  100. package/kit/commands/depurar.md +190 -190
  101. package/kit/commands/detectar-duplicacao.md +197 -197
  102. package/kit/commands/discutir-fase.md +131 -131
  103. package/kit/commands/encontrar-seams.md +136 -136
  104. package/kit/commands/entrar-discord.md +17 -17
  105. package/kit/commands/estatisticas.md +18 -18
  106. package/kit/commands/example-greeting.md +33 -33
  107. package/kit/commands/executar-fase.md +58 -58
  108. package/kit/commands/expresso.md +56 -56
  109. package/kit/commands/fase-ui.md +34 -34
  110. package/kit/commands/fazer.md +57 -57
  111. package/kit/commands/fio.md +125 -125
  112. package/kit/commands/fluxos-trabalho.md +64 -64
  113. package/kit/commands/forense.md +176 -176
  114. package/kit/commands/gerenciador.md +38 -38
  115. package/kit/commands/inserir-fase.md +31 -31
  116. package/kit/commands/legacy.md +263 -263
  117. package/kit/commands/limpeza.md +17 -17
  118. package/kit/commands/listar-hipoteses-fase.md +45 -45
  119. package/kit/commands/listar-workspaces.md +18 -18
  120. package/kit/commands/load-shedding.md +117 -117
  121. package/kit/commands/mapear-codebase.md +70 -70
  122. package/kit/commands/multi-tenant.md +163 -163
  123. package/kit/commands/nota.md +33 -33
  124. package/kit/commands/novo-marco.md +43 -43
  125. package/kit/commands/novo-projeto.md +41 -41
  126. package/kit/commands/novo-workspace.md +43 -43
  127. package/kit/commands/pausar-trabalho.md +37 -37
  128. package/kit/commands/perfil-usuario.md +45 -45
  129. package/kit/commands/pesquisar-fase.md +195 -195
  130. package/kit/commands/planejar-fase.md +67 -67
  131. package/kit/commands/planejar-lacunas.md +33 -33
  132. package/kit/commands/plantar-ideia.md +25 -25
  133. package/kit/commands/progresso.md +24 -24
  134. package/kit/commands/proximo.md +30 -30
  135. package/kit/commands/publicar.md +490 -490
  136. package/kit/commands/rapido.md +35 -35
  137. package/kit/commands/reaplicar-patches.md +124 -124
  138. package/kit/commands/refactor-seguro.md +321 -321
  139. package/kit/commands/relatorio-sessao.md +19 -19
  140. package/kit/commands/remover-fase.md +31 -31
  141. package/kit/commands/remover-workspace.md +26 -26
  142. package/kit/commands/resumo-marco.md +50 -50
  143. package/kit/commands/retomar-trabalho.md +40 -40
  144. package/kit/commands/revisar-backlog.md +60 -60
  145. package/kit/commands/revisar-ui.md +32 -32
  146. package/kit/commands/revisar.md +37 -37
  147. package/kit/commands/saude.md +21 -21
  148. package/kit/commands/setup-notion.md +93 -93
  149. package/kit/commands/storytelling.md +179 -179
  150. package/kit/commands/supabase.md +21 -1
  151. package/kit/commands/sync-main.md +68 -68
  152. package/kit/commands/validar-fase.md +35 -35
  153. package/kit/commands/verificar-tarefas.md +44 -44
  154. package/kit/commands/verificar-trabalho.md +64 -64
  155. package/kit/file-manifest.json +100 -84
  156. package/kit/framework/bin/lib/commands.cjs +959 -959
  157. package/kit/framework/bin/lib/config.cjs +442 -442
  158. package/kit/framework/bin/lib/core.cjs +1230 -1230
  159. package/kit/framework/bin/lib/frontmatter.cjs +336 -336
  160. package/kit/framework/bin/lib/init.cjs +1442 -1442
  161. package/kit/framework/bin/lib/milestone.cjs +252 -252
  162. package/kit/framework/bin/lib/model-profiles.cjs +68 -68
  163. package/kit/framework/bin/lib/phase.cjs +888 -888
  164. package/kit/framework/bin/lib/profile-output.cjs +952 -952
  165. package/kit/framework/bin/lib/profile-pipeline.cjs +539 -539
  166. package/kit/framework/bin/lib/roadmap.cjs +329 -329
  167. package/kit/framework/bin/lib/security.cjs +382 -382
  168. package/kit/framework/bin/lib/state.cjs +1031 -1031
  169. package/kit/framework/bin/lib/template.cjs +222 -222
  170. package/kit/framework/bin/lib/uat.cjs +282 -282
  171. package/kit/framework/bin/lib/verify.cjs +888 -888
  172. package/kit/framework/bin/lib/workstream.cjs +491 -491
  173. package/kit/framework/bin/tools.cjs +918 -918
  174. package/kit/framework/commands/workstreams.md +63 -63
  175. package/kit/framework/references/checkpoints.md +778 -778
  176. package/kit/framework/references/continuation-format.md +249 -249
  177. package/kit/framework/references/decimal-phase-calculation.md +64 -64
  178. package/kit/framework/references/git-integration.md +295 -295
  179. package/kit/framework/references/git-planning-commit.md +38 -38
  180. package/kit/framework/references/model-profile-resolution.md +36 -36
  181. package/kit/framework/references/model-profiles.md +139 -139
  182. package/kit/framework/references/phase-argument-parsing.md +61 -61
  183. package/kit/framework/references/planning-config.md +202 -202
  184. package/kit/framework/references/questioning.md +162 -162
  185. package/kit/framework/references/tdd.md +263 -263
  186. package/kit/framework/references/ui-brand.md +160 -160
  187. package/kit/framework/references/user-profiling.md +657 -657
  188. package/kit/framework/references/verification-patterns.md +612 -612
  189. package/kit/framework/references/workstream-flag.md +58 -58
  190. package/kit/framework/templates/DEBUG.md +164 -164
  191. package/kit/framework/templates/UAT.md +265 -265
  192. package/kit/framework/templates/UI-SPEC.md +100 -100
  193. package/kit/framework/templates/VALIDATION.md +76 -76
  194. package/kit/framework/templates/claude-md.md +122 -122
  195. package/kit/framework/templates/codebase/architecture.md +185 -185
  196. package/kit/framework/templates/codebase/concerns.md +205 -205
  197. package/kit/framework/templates/codebase/conventions.md +204 -204
  198. package/kit/framework/templates/codebase/integrations.md +192 -192
  199. package/kit/framework/templates/codebase/stack.md +158 -158
  200. package/kit/framework/templates/codebase/structure.md +199 -199
  201. package/kit/framework/templates/codebase/testing.md +301 -301
  202. package/kit/framework/templates/config.json +44 -44
  203. package/kit/framework/templates/context.md +352 -352
  204. package/kit/framework/templates/continue-here.md +78 -78
  205. package/kit/framework/templates/copilot-instructions.md +7 -7
  206. package/kit/framework/templates/debug-subagent-prompt.md +91 -91
  207. package/kit/framework/templates/dev-preferences.md +20 -20
  208. package/kit/framework/templates/discovery.md +146 -146
  209. package/kit/framework/templates/discussion-log.md +63 -63
  210. package/kit/framework/templates/milestone-archive.md +123 -123
  211. package/kit/framework/templates/milestone.md +115 -115
  212. package/kit/framework/templates/phase-prompt.md +610 -610
  213. package/kit/framework/templates/planner-subagent-prompt.md +117 -117
  214. package/kit/framework/templates/project.md +186 -186
  215. package/kit/framework/templates/requirements.md +231 -231
  216. package/kit/framework/templates/research-project/ARCHITECTURE.md +204 -204
  217. package/kit/framework/templates/research-project/FEATURES.md +147 -147
  218. package/kit/framework/templates/research-project/PITFALLS.md +200 -200
  219. package/kit/framework/templates/research-project/STACK.md +120 -120
  220. package/kit/framework/templates/research-project/SUMMARY.md +170 -170
  221. package/kit/framework/templates/research.md +419 -419
  222. package/kit/framework/templates/retrospective.md +54 -54
  223. package/kit/framework/templates/roadmap.md +202 -202
  224. package/kit/framework/templates/state.md +176 -176
  225. package/kit/framework/templates/summary-complex.md +59 -59
  226. package/kit/framework/templates/summary-minimal.md +41 -41
  227. package/kit/framework/templates/summary-standard.md +48 -48
  228. package/kit/framework/templates/summary.md +209 -209
  229. package/kit/framework/templates/user-profile.md +146 -146
  230. package/kit/framework/templates/user-setup.md +256 -256
  231. package/kit/framework/templates/verification-report.md +258 -258
  232. package/kit/framework/workflows/add-phase.md +112 -112
  233. package/kit/framework/workflows/add-tests.md +351 -351
  234. package/kit/framework/workflows/add-todo.md +158 -158
  235. package/kit/framework/workflows/audit-milestone.md +340 -340
  236. package/kit/framework/workflows/audit-uat.md +109 -109
  237. package/kit/framework/workflows/autonomous.md +891 -891
  238. package/kit/framework/workflows/check-todos.md +177 -177
  239. package/kit/framework/workflows/cleanup.md +152 -152
  240. package/kit/framework/workflows/complete-milestone.md +696 -696
  241. package/kit/framework/workflows/diagnose-issues.md +231 -231
  242. package/kit/framework/workflows/discovery-phase.md +289 -289
  243. package/kit/framework/workflows/discuss-phase-assumptions.md +653 -653
  244. package/kit/framework/workflows/discuss-phase.md +784 -784
  245. package/kit/framework/workflows/do.md +104 -104
  246. package/kit/framework/workflows/execute-phase.md +838 -838
  247. package/kit/framework/workflows/execute-plan.md +510 -510
  248. package/kit/framework/workflows/fast.md +102 -102
  249. package/kit/framework/workflows/forensics.md +265 -265
  250. package/kit/framework/workflows/health.md +181 -181
  251. package/kit/framework/workflows/help.md +619 -619
  252. package/kit/framework/workflows/insert-phase.md +130 -130
  253. package/kit/framework/workflows/list-phase-assumptions.md +178 -178
  254. package/kit/framework/workflows/list-workspaces.md +56 -56
  255. package/kit/framework/workflows/manager.md +362 -362
  256. package/kit/framework/workflows/map-codebase.md +377 -377
  257. package/kit/framework/workflows/milestone-summary.md +223 -223
  258. package/kit/framework/workflows/new-milestone.md +486 -486
  259. package/kit/framework/workflows/new-project.md +1159 -1159
  260. package/kit/framework/workflows/new-workspace.md +237 -237
  261. package/kit/framework/workflows/next.md +97 -97
  262. package/kit/framework/workflows/node-repair.md +92 -92
  263. package/kit/framework/workflows/note.md +156 -156
  264. package/kit/framework/workflows/pause-work.md +176 -176
  265. package/kit/framework/workflows/plan-milestone-gaps.md +273 -273
  266. package/kit/framework/workflows/plan-phase.md +765 -765
  267. package/kit/framework/workflows/plant-seed.md +169 -169
  268. package/kit/framework/workflows/pr-branch.md +129 -129
  269. package/kit/framework/workflows/profile-user.md +450 -450
  270. package/kit/framework/workflows/progress.md +507 -507
  271. package/kit/framework/workflows/quick.md +757 -757
  272. package/kit/framework/workflows/remove-phase.md +155 -155
  273. package/kit/framework/workflows/remove-workspace.md +90 -90
  274. package/kit/framework/workflows/research-phase.md +82 -82
  275. package/kit/framework/workflows/resume-project.md +326 -326
  276. package/kit/framework/workflows/review.md +228 -228
  277. package/kit/framework/workflows/session-report.md +146 -146
  278. package/kit/framework/workflows/settings.md +283 -283
  279. package/kit/framework/workflows/ship.md +228 -228
  280. package/kit/framework/workflows/stats.md +60 -60
  281. package/kit/framework/workflows/transition.md +671 -671
  282. package/kit/framework/workflows/ui-phase.md +302 -302
  283. package/kit/framework/workflows/ui-review.md +165 -165
  284. package/kit/framework/workflows/update.md +323 -323
  285. package/kit/framework/workflows/validate-phase.md +174 -174
  286. package/kit/framework/workflows/verify-phase.md +252 -252
  287. package/kit/framework/workflows/verify-work.md +637 -637
  288. package/kit/hooks/check-update.js +118 -118
  289. package/kit/hooks/context-monitor.js +163 -163
  290. package/kit/hooks/kit-attribution-reminder.cjs +29 -50
  291. package/kit/hooks/kit-router.cjs +137 -0
  292. package/kit/hooks/prompt-guard.js +103 -103
  293. package/kit/hooks/statusline.js +125 -125
  294. package/kit/hooks/workflow-guard.js +101 -101
  295. package/kit/settings.json +45 -45
  296. package/kit/skills/ai-prompt-characterization/SKILL.md +335 -335
  297. package/kit/skills/armadilhas-sistemas-distribuidos/SKILL.md +447 -447
  298. package/kit/skills/audit-log-multi-tenant/SKILL.md +340 -340
  299. package/kit/skills/b2b-saas-architecture/SKILL.md +300 -300
  300. package/kit/skills/consistencia-leitura-replica/SKILL.md +385 -385
  301. package/kit/skills/crm-lead-pipeline-patterns/SKILL.md +343 -343
  302. package/kit/skills/escolha-modelo-consistencia/SKILL.md +494 -494
  303. package/kit/skills/evolucao-schema-compativel/SKILL.md +448 -448
  304. package/kit/skills/evolution-go-whatsapp-integration/SKILL.md +322 -322
  305. package/kit/skills/example-skill/SKILL.md +42 -42
  306. package/kit/skills/legacy-api-only-applications/SKILL.md +358 -358
  307. package/kit/skills/legacy-characterization-tests/SKILL.md +330 -330
  308. package/kit/skills/legacy-effect-analysis/SKILL.md +331 -331
  309. package/kit/skills/legacy-extract-class/SKILL.md +203 -203
  310. package/kit/skills/legacy-programming-by-difference/SKILL.md +252 -252
  311. package/kit/skills/legacy-seams-and-test-harness/SKILL.md +460 -460
  312. package/kit/skills/legacy-shotgun-surgery/SKILL.md +286 -286
  313. package/kit/skills/legacy-sprout-wrap-techniques/SKILL.md +434 -434
  314. package/kit/skills/legacy-storytelling-naked-crc/SKILL.md +270 -270
  315. package/kit/skills/lgpd-multi-tenant-compliance/SKILL.md +340 -340
  316. package/kit/skills/member-invite-flow/SKILL.md +305 -305
  317. package/kit/skills/member-management-react-shadcn/SKILL.md +328 -328
  318. package/kit/skills/multi-tenant-performance-scaling/SKILL.md +316 -316
  319. package/kit/skills/multi-tenant-rls-hierarchy/SKILL.md +342 -342
  320. package/kit/skills/org-onboarding-flow/SKILL.md +257 -257
  321. package/kit/skills/org-switcher-react-pattern/SKILL.md +349 -349
  322. package/kit/skills/permission-gate-react-pattern/SKILL.md +271 -271
  323. package/kit/skills/postgres-isolamento-concorrencia/SKILL.md +552 -552
  324. package/kit/skills/pre-refactor-characterization/SKILL.md +421 -421
  325. package/kit/skills/rbac-permissions-matrix-supabase/SKILL.md +338 -338
  326. package/kit/skills/streams-eventos-cdc/SKILL.md +711 -711
  327. package/kit/skills/supabase-auth-hardening/SKILL.md +674 -0
  328. package/kit/skills/supabase-auth-hooks/SKILL.md +875 -0
  329. package/kit/skills/supabase-auth-methods/SKILL.md +486 -0
  330. package/kit/skills/supabase-auth-sessions/SKILL.md +579 -0
  331. package/kit/skills/supabase-auth-ssr/SKILL.md +60 -14
  332. package/kit/skills/supabase-branching-workflow/SKILL.md +544 -544
  333. package/kit/skills/supabase-ci-cd-github-actions/SKILL.md +880 -880
  334. package/kit/skills/supabase-column-level-security/SKILL.md +426 -426
  335. package/kit/skills/supabase-config-toml-remotes/SKILL.md +807 -807
  336. package/kit/skills/supabase-custom-claims-rbac/SKILL.md +472 -472
  337. package/kit/skills/supabase-edge-functions/SKILL.md +1 -1
  338. package/kit/skills/supabase-edge-functions-auth/SKILL.md +1 -1
  339. package/kit/skills/supabase-edge-functions-limits/SKILL.md +1 -1
  340. package/kit/skills/supabase-edge-functions-mcp-server/SKILL.md +1 -1
  341. package/kit/skills/supabase-edge-functions-testing/SKILL.md +1 -1
  342. package/kit/skills/supabase-edge-runtime-builtins/SKILL.md +1 -1
  343. package/kit/skills/supabase-enterprise-sso-saml/SKILL.md +545 -0
  344. package/kit/skills/supabase-jwt-signing-keys/SKILL.md +399 -0
  345. package/kit/skills/supabase-mfa/SKILL.md +488 -0
  346. package/kit/skills/supabase-migration-repair/SKILL.md +823 -823
  347. package/kit/skills/supabase-migrations/SKILL.md +297 -297
  348. package/kit/skills/supabase-oauth-server/SKILL.md +537 -0
  349. package/kit/skills/supabase-pgtap-testing/SKILL.md +1053 -1053
  350. package/kit/skills/supabase-postgres-roles/SKILL.md +392 -392
  351. package/kit/skills/supabase-realtime/SKILL.md +460 -460
  352. package/kit/skills/supabase-rls-defense-in-depth/SKILL.md +418 -418
  353. package/kit/skills/supabase-rls-policies/SKILL.md +635 -635
  354. package/kit/skills/supabase-social-oauth/SKILL.md +480 -0
  355. package/kit/skills/supabase-third-party-auth/SKILL.md +450 -0
  356. package/kit/skills/super-admin-platform-pattern/SKILL.md +326 -326
  357. package/kit/skills/tenant-quente-mitigacao/SKILL.md +605 -605
  358. package/kit/skills/whatsapp-conversation-state-machine/SKILL.md +287 -287
  359. package/package.json +1 -1
  360. package/src/core/kit.js +216 -216
  361. package/src/core/reflect.js +247 -247
  362. package/src/core/reverse-sync.js +372 -372
  363. package/src/core/sync.js +437 -418
  364. package/src/core/watch.js +121 -121
  365. package/src/mcp-server/index.js +794 -746
@@ -1,635 +1,635 @@
1
- ---
2
- name: supabase-rls-policies
3
- description: Use ao criar/auditar RLS — sempre (select auth.uid()), policies separadas por operação, GRANTs antes de ENABLE RLS, IS NOT NULL para anti silent-fail, índices nas colunas, NUNCA user_meta…
4
- ---
5
-
6
- # Supabase — RLS Policies
7
-
8
- ## Quando usar
9
-
10
- LLM carrega esta skill quando criar, auditar ou debugar Row Level Security em Supabase. Trigger phrases:
11
-
12
- - "criar policy RLS", "RLS policy", "row level security"
13
- - "policies separadas por operação"
14
- - "auth.uid()", "auth.jwt()"
15
- - "MFA enforcement", "AAL2"
16
- - "auditar segurança de tabela Supabase"
17
- - "GRANT antes de ENABLE RLS", "defense in depth", "security_invoker"
18
- - "anon role vs anonymous user", "raw_app_meta_data vs raw_user_meta_data"
19
-
20
- ## Defense in depth — RLS como camada (v1.23)
21
-
22
- RLS é um **Postgres primitive** que oferece **defense in depth**: protege dados mesmo quando acessados por third-party tooling (Metabase, dbt, ferramentas BI conectadas via JDBC). Mesmo se um vazamento de chave API ou bypass na camada de aplicação acontece, RLS impede acesso indevido **no banco**.
23
-
24
- A regra mestre:
25
-
26
- > **RLS *must* always be enabled on any tables stored in an exposed schema. By default, this is the `public` schema.**
27
-
28
- Se você não tem RLS na camada do banco, você está confiando que **todo cliente** (front-end, backend, third-party, scripts, MCP tools) faça filtering corretamente. Isso é frágil.
29
-
30
- **Princípio em v1.23 (handoff cooperativo):** todo SQL gerado pelo kit passa pelo `supabase-rls-hardener` antes do output final — drafts upstream são preservados, mas hardening RLS é obrigatório.
31
-
32
- ## Regras absolutas
33
-
34
- **WARNING — REGRA #1 (segurança crítica):** **NUNCA** referencie `user_metadata` em policy de autorização. `user_metadata` é editável pelo cliente via `auth.updateUser({data: {...}})` — usuário pode auto-elevar `role: 'admin'` ou `plan: 'premium'`. Use **`app_metadata`** (set apenas via service_role) para roles/permissions. Splinter linter 0015 detecta automaticamente.
35
-
36
- **REGRA #2 (performance crítica):** **SEMPRE** envolva `auth.uid()` em `(select auth.uid())`. Sem o wrapper, Postgres reavalia a função **uma vez por linha** — degrada queries com filtro RLS em **até 1000×**. Documentado nos benchmarks oficiais (`test2a-wrappedSQL-uid`: 179ms → 9ms, 94.97% improvement).
37
-
38
- **REGRA #3 (anti silent-fail anônimo — v1.23):** Para policies que dependem de identidade autenticada, use **`auth.uid() IS NOT NULL AND auth.uid() = user_id`** ao invés de apenas `(select auth.uid()) = user_id`. Quando o usuário não está logado, `auth.uid()` retorna `null`, e `null = user_id` é **sempre false** silenciosamente — a policy "funciona" mas confunde debugging. O check explícito de `IS NOT NULL` deixa intent claro.
39
-
40
- **Outras regras:**
41
-
42
- - **`GRANT` antes de `ENABLE RLS`** (v1.23) — sempre conceda privilégios necessários aos roles `anon`/`authenticated`/`service_role` ANTES de habilitar RLS. Sem GRANT, mesmo policies "permissive" falham porque o role não tem permissão de tabela.
43
- - **`policies separadas por operação`** — uma `for select`, uma `for insert`, uma `for update`, uma `for delete`. **Nunca** `for all` cobrindo CRUD inteiro.
44
- - **`TO authenticated`** ou **`to anon`** sempre explícito — nunca deixar implícito (default `to public` é insecure; impede otimização do executor que skipa execução de policy para roles fora do TO clause).
45
- - `for select` e `for delete` usam **apenas `using`** (sem `with check`).
46
- - `for insert` usa **apenas `with check`** (sem `using`).
47
- - `for update` usa **`using` + `with check`** (using para qual linha pode ser atualizada, with check para qual estado a linha pode assumir).
48
- - Índice obrigatório nas colunas referenciadas pela policy: `create index on public.tasks (user_id);`. Sem index, scan full em cada query.
49
- - `permissive` é default e preferido. `restrictive` é raro e exige justificativa explícita.
50
- - Para MFA enforcement: `(auth.jwt()->>'aal')::text = 'aal2'` em policies que exigem 2FA ativo.
51
- - **Views** com `security_invoker=true` (Postgres 15+) — por padrão views bypassam RLS (criadas como `security_definer` rodando como `postgres`). Ver seção "Views" abaixo.
52
-
53
- ## Setup canônico — GRANTs + ENABLE RLS (v1.23)
54
-
55
- O setup completo para uma tabela em schema exposto é:
56
-
57
- ```sql
58
- -- 1. GRANTs por role (faça ANTES de ENABLE RLS)
59
- grant select on public.tasks to anon;
60
- grant select, insert, update, delete on public.tasks to authenticated;
61
- grant select, insert, update, delete on public.tasks to service_role;
62
-
63
- -- 2. Enable RLS
64
- alter table public.tasks enable row level security;
65
-
66
- -- 3. Policies granulares (sem isso, nada é acessível com publishable key)
67
- create policy "users_select_own_tasks"
68
- on public.tasks for select to authenticated
69
- using ((select auth.uid()) is not null and (select auth.uid()) = user_id);
70
-
71
- -- ... INSERT/UPDATE/DELETE policies análogos
72
-
73
- -- 4. Índice obrigatório
74
- create index tasks_user_id_idx on public.tasks (user_id);
75
- ```
76
-
77
- **Por que GRANTs antes de ENABLE RLS:** RLS é uma camada de filtragem de linhas **adicionada** sobre as permissões de tabela. Sem `GRANT SELECT TO authenticated`, mesmo se a policy retorna true, a query falha com "permission denied". Os GRANTs estabelecem o que o role *pode tentar* fazer; RLS estabelece *quais linhas* ele vê.
78
-
79
- **Roles canônicos Supabase:**
80
-
81
- - `anon` — requisição sem autenticação (usuário deslogado)
82
- - `authenticated` — requisição com JWT válido de Supabase Auth
83
- - `service_role` — bypassa RLS (ver "Bypassing RLS" abaixo); use APENAS em backend/admin
84
-
85
- ## Auto-enable RLS para tabelas novas (v1.23)
86
-
87
- Para evitar esquecimento humano, instale event trigger que ativa RLS automaticamente em `CREATE TABLE`:
88
-
89
- ```sql
90
- create or replace function rls_auto_enable()
91
- returns event_trigger
92
- language plpgsql
93
- security definer
94
- set search_path = pg_catalog
95
- as $$
96
- declare
97
- cmd record;
98
- begin
99
- for cmd in
100
- select *
101
- from pg_event_trigger_ddl_commands()
102
- where command_tag in ('CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO')
103
- and object_type in ('table','partitioned table')
104
- loop
105
- if cmd.schema_name is not null and cmd.schema_name in ('public') and cmd.schema_name not in ('pg_catalog','information_schema') and cmd.schema_name not like 'pg_toast%' and cmd.schema_name not like 'pg_temp%' then
106
- begin
107
- execute format('alter table if exists %s enable row level security', cmd.object_identity);
108
- raise log 'rls_auto_enable: enabled RLS on %', cmd.object_identity;
109
- exception
110
- when others then
111
- raise log 'rls_auto_enable: failed to enable RLS on %', cmd.object_identity;
112
- end;
113
- else
114
- raise log 'rls_auto_enable: skip % (system schema or not in enforced list)', cmd.object_identity;
115
- end if;
116
- end loop;
117
- end;
118
- $$;
119
-
120
- drop event trigger if exists ensure_rls;
121
- create event trigger ensure_rls
122
- on ddl_command_end
123
- when tag in ('CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO')
124
- execute function rls_auto_enable();
125
- ```
126
-
127
- **Caveats:**
128
- - Aplica-se a tabelas criadas **após** o trigger ser instalado — existentes precisam de `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` manual.
129
- - Tabelas em `pg_catalog`, `information_schema`, `pg_toast*`, `pg_temp*` são skipped (sistema).
130
- - Padrão completo + variações em [`supabase-rls-defense-in-depth`](../supabase-rls-defense-in-depth/SKILL.md) (v1.23).
131
-
132
- ## `anon` Postgres role vs anonymous Auth user (v1.23)
133
-
134
- Confusão comum: **`anon` Postgres role** ≠ **anonymous user de Supabase Auth**.
135
-
136
- - **`anon` Postgres role** — requisição sem JWT (usuário deslogado). É um Postgres role como qualquer outro; aparece em `TO anon` clauses.
137
- - **anonymous user de Supabase Auth** — usuário criado via `supabase.auth.signInAnonymously()` (Auth feature). Tem JWT válido, assume o role `authenticated`, e pode ser diferenciado por checar a claim `is_anonymous` no JWT.
138
-
139
- **Implicação em policies:**
140
-
141
- ```sql
142
- -- visivel a qualquer cliente (anon Postgres ou authenticated com is_anonymous=true ou regular)
143
- create policy "public_profiles_view"
144
- on public.profiles for select
145
- to authenticated, anon
146
- using (true);
147
-
148
- -- bloqueia anonymous Auth users mesmo que estejam autenticados
149
- create policy "premium_features_no_anonymous"
150
- on public.premium_data for select
151
- to authenticated
152
- using (
153
- (select auth.jwt()->>'is_anonymous')::boolean is not true
154
- and (select auth.uid()) = user_id
155
- );
156
- ```
157
-
158
- ## Patterns canônicos
159
-
160
- ### SELECT — usuário lê apenas suas próprias linhas
161
-
162
- ```sql
163
- -- política de SELECT com wrapper (select auth.uid()) + IS NOT NULL anti silent-fail
164
- create policy "users_select_own_tasks"
165
- on public.tasks
166
- for select
167
- to authenticated
168
- using (
169
- (select auth.uid()) is not null
170
- and (select auth.uid()) = user_id
171
- );
172
-
173
- -- index obrigatório (sem isso, scan full)
174
- create index tasks_user_id_idx on public.tasks (user_id);
175
- ```
176
-
177
- ### INSERT, UPDATE, DELETE separados
178
-
179
- ```sql
180
- -- INSERT — usuário só pode criar linhas com user_id = ele mesmo
181
- create policy "users_insert_own_tasks"
182
- on public.tasks
183
- for insert
184
- to authenticated
185
- with check (
186
- (select auth.uid()) is not null
187
- and (select auth.uid()) = user_id
188
- );
189
-
190
- -- UPDATE — restringe quais linhas (using) E qual estado novo (with check)
191
- -- IMPORTANTE: UPDATE também exige policy SELECT correspondente (sem ela, UPDATE não funciona)
192
- create policy "users_update_own_tasks"
193
- on public.tasks
194
- for update
195
- to authenticated
196
- using (
197
- (select auth.uid()) is not null
198
- and (select auth.uid()) = user_id
199
- )
200
- with check (
201
- (select auth.uid()) is not null
202
- and (select auth.uid()) = user_id
203
- );
204
-
205
- -- DELETE — apenas a coluna using (sem with check)
206
- create policy "users_delete_own_tasks"
207
- on public.tasks
208
- for delete
209
- to authenticated
210
- using (
211
- (select auth.uid()) is not null
212
- and (select auth.uid()) = user_id
213
- );
214
- ```
215
-
216
- ### Role admin via `app_metadata`
217
-
218
- ```sql
219
- -- segurança: app_metadata é set apenas via service_role (admin API)
220
- -- cliente NÃO pode mutá-lo
221
- create policy "admins_manage_all_tasks"
222
- on public.tasks
223
- for update
224
- to authenticated
225
- using (
226
- (select auth.jwt()->'app_metadata'->>'role') = 'admin'
227
- )
228
- with check (
229
- (select auth.jwt()->'app_metadata'->>'role') = 'admin'
230
- );
231
- ```
232
-
233
- ### Team membership via `app_metadata` (array)
234
-
235
- ```sql
236
- -- exemplo: app_metadata.teams = ["team_a", "team_b"]
237
- create policy "team_members_view"
238
- on public.team_resources
239
- for select
240
- to authenticated
241
- using (
242
- team_id::text in (select jsonb_array_elements_text((select auth.jwt()->'app_metadata'->'teams')))
243
- );
244
- ```
245
-
246
- ### MFA enforcement (AAL2)
247
-
248
- ```sql
249
- -- exigir 2FA ativo para acessar dados sensíveis
250
- -- restrictive force AND com policy SELECT base
251
- create policy "mfa_required_for_billing"
252
- on public.billing_records
253
- as restrictive
254
- for select
255
- to authenticated
256
- using (
257
- (select (auth.jwt()->>'aal')::text) = 'aal2'
258
- );
259
- ```
260
-
261
- ### Views com `security_invoker=true` (Postgres 15+) — v1.23
262
-
263
- Por padrão, views são criadas com `security_definer` (rodam com permissões do criador, geralmente `postgres`) — **bypassam RLS** das tabelas subjacentes. Em Postgres 15+, use `security_invoker=true` para fazer a view respeitar as policies RLS do role chamador:
264
-
265
- ```sql
266
- create view public.user_active_tasks
267
- with (security_invoker = true)
268
- as
269
- select id, title, status, created_at
270
- from public.tasks
271
- where status = 'active';
272
- ```
273
-
274
- **Em versões < Postgres 15:** revoke acesso de `anon`/`authenticated` à view, ou crie em schema não-exposto:
275
-
276
- ```sql
277
- -- alternativa pré-Postgres 15: revoke acesso
278
- revoke select on public.legacy_view from anon, authenticated;
279
-
280
- -- ou crie em schema privado:
281
- create view private.internal_view as ...;
282
- ```
283
-
284
- ## `app_metadata` vs `user_metadata` — caveats canônicos (v1.23)
285
-
286
- A função `auth.jwt()` retorna o JWT do usuário fazendo a requisição. Existem duas claims relacionadas a metadata:
287
-
288
- - **`raw_user_meta_data`** — pode ser atualizado pelo authenticated end-user via `supabase.auth.updateUser({ data: { ... } })`. **NÃO é seguro para authorization.** Use apenas para preferences (tema, idioma, avatar URL).
289
- - **`raw_app_meta_data`** — **NÃO pode ser atualizado pelo cliente** — só via service_role + admin API. **É o lugar correto** para roles, permissions, team memberships, plan tier.
290
-
291
- **Caveat #1 (JWT freshness):** JWT nem sempre está "fresh". Se você remove um user de um team e atualiza `app_metadata`, isso não reflete em `auth.jwt()` até o JWT ser refreshed (geralmente 1h TTL). Para invalidação imediata, force logout do user via `auth.admin.signOut()`.
292
-
293
- **Caveat #2 (cookie 4096 bytes):** Se você usa Cookies para Auth, esteja atento ao JWT size. Browsers limitam cada cookie a 4096 bytes. Se você embarca arrays grandes em `app_metadata.teams` ou similar, o JWT pode passar do limite. Mitigação: store apenas IDs, busque membership via SQL com policy/RPC.
294
-
295
- **Caveat #3 (NULL handling):** Para requests sem auth, `auth.uid()` retorna `null`. `null = user_id` é sempre **false silenciosamente** em SQL — a policy não erra, só não match. Sempre use `IS NOT NULL AND ...` (REGRA #3) para deixar intent claro.
296
-
297
- ## Performance — recomendações canônicas (v1.23)
298
-
299
- Toda authorization tem custo. RLS é poderoso mas pode ser caro em queries que scaneam muitas linhas. Recomendações baseadas em benchmarks oficiais Supabase:
300
-
301
- ### 1. Index nas colunas usadas em policies (99.94% improvement)
302
-
303
- ```sql
304
- -- antes: scan full
305
- -- depois: index scan
306
- create index tasks_user_id_idx on public.tasks (user_id);
307
- ```
308
-
309
- ### 2. Envolva funções em `(select ...)` para caching de plano (até 99.99% improvement)
310
-
311
- ```sql
312
- -- antes: re-executa auth.uid() por linha (179ms para 100k rows)
313
- using (auth.uid() = user_id)
314
-
315
- -- depois: initPlan, executa 1 vez e reusa (9ms)
316
- using ((select auth.uid()) = user_id)
317
- ```
318
-
319
- Aplicável a qualquer função que não muda baseado em row data: `auth.uid()`, `auth.jwt()`, `security definer` functions. **Não funciona** se o resultado depende da linha (ex: `is_owner(row_id)` precisa rodar por linha).
320
-
321
- ### 3. Adicione filtros redundantes nas queries client-side (94.74% improvement)
322
-
323
- Mesmo com policy aplicada, adicione `.eq()` ou `where` explícito no client:
324
-
325
- ```js
326
- // errado — confia 100% na policy
327
- const { data } = supabase.from('tasks').select()
328
-
329
- // certo — Postgres usa o filtro para construir query plan melhor
330
- const { data } = supabase.from('tasks').select().eq('user_id', userId)
331
- ```
332
-
333
- Policy é "implicit where clause"; filtro explícito ajuda o planner Postgres a escolher index scan ao invés de seq scan.
334
-
335
- ### 4. Specify role com `TO` clause (99.78% improvement)
336
-
337
- ```sql
338
- -- antes (sem TO): policy roda para todos roles, inclusive anon
339
- create policy "rls_test_select" on rls_test
340
- using ((select auth.uid()) = user_id);
341
-
342
- -- depois: anon skipa execução
343
- create policy "rls_test_select" on rls_test
344
- to authenticated
345
- using ((select auth.uid()) = user_id);
346
- ```
347
-
348
- ### 5. Use `security definer` functions para policies caras
349
-
350
- Se a policy precisa fazer JOIN ou query custosa, encapsule em função `security definer` que bypassa RLS interno:
351
-
352
- ```sql
353
- -- função pode acessar roles_table sem aplicar RLS recursivamente
354
- create function private.has_good_role()
355
- returns boolean
356
- language plpgsql
357
- security definer
358
- set search_path = ''
359
- as $$
360
- begin
361
- return exists (
362
- select 1 from public.roles_table
363
- where (select auth.uid()) = user_id and role = 'good_role'
364
- );
365
- end;
366
- $$;
367
-
368
- -- policy fica simples e cacheável
369
- create policy "rls_test_select"
370
- on public.test_table
371
- to authenticated
372
- using ((select private.has_good_role()));
373
- ```
374
-
375
- **IMPORTANTE:** funções `security definer` NUNCA em schema exposto (`public`). Sempre em `private` ou similar (não exposto via API settings).
376
-
377
- ### 6. Minimize joins (99.78% improvement)
378
-
379
- Reescreva policies que fazem join na source table para usar `IN` ao invés:
380
-
381
- ```sql
382
- -- antes: join entre test_table (source) e team_user (target)
383
- create policy "rls_test_select" on public.test_table
384
- to authenticated
385
- using (
386
- (select auth.uid()) in (
387
- select user_id
388
- from public.team_user
389
- where team_user.team_id = team_id -- join com source!
390
- )
391
- );
392
-
393
- -- depois: filtra primeiro, depois IN
394
- create policy "rls_test_select" on public.test_table
395
- to authenticated
396
- using (
397
- team_id in (
398
- select team_id
399
- from public.team_user
400
- where user_id = (select auth.uid()) -- sem join
401
- )
402
- );
403
- ```
404
-
405
- Se a lista interna pode passar de 1000 items, considere abordagem com `security definer` function ao invés.
406
-
407
- ## Anti-patterns
408
-
409
- ### Anti-pattern 1: `auth.uid()` sem `(select)` wrapper
410
-
411
- **Errado:**
412
- ```sql
413
- create policy "users_select_own_tasks"
414
- on public.tasks
415
- for select
416
- to authenticated
417
- using (auth.uid() = user_id); -- sem (select) — re-executa por linha
418
- ```
419
-
420
- **Por quê:** Postgres reavalia `auth.uid()` para cada linha sendo testada. Em tabela com 100k linhas, isso é 100k chamadas. O `(select)` permite Postgres executar **uma vez** e reusar — degradação de até **1000×** sem o wrapper. Documentado em [RLS Performance](https://supabase.com/docs/guides/troubleshooting/rls-performance-and-best-practices-Z5Jjwv).
421
-
422
- **Certo:**
423
- ```sql
424
- using ((select auth.uid()) = user_id)
425
- ```
426
-
427
- ### Anti-pattern 2: `WARNING user_metadata` em autorização — privilege escalation
428
-
429
- **Errado:**
430
- ```sql
431
- create policy "admins_manage_all"
432
- on public.tasks
433
- for update
434
- to authenticated
435
- using (
436
- (auth.jwt()->'user_metadata'->>'role') = 'admin' -- editável pelo cliente!
437
- );
438
- ```
439
-
440
- **Por quê:** o cliente pode chamar `supabase.auth.updateUser({ data: { role: 'admin' } })` e instantaneamente ganhar privilégios de admin. `user_metadata` é projetado para preferences do usuário (tema, idioma), não para autorização. Documentado em [Splinter linter 0015](https://supabase.github.io/splinter/0015_rls_references_user_metadata/).
441
-
442
- **Certo:** ver "Role admin via `app_metadata`" acima — `app_metadata` requer service_role para mutar.
443
-
444
- ### Anti-pattern 3: `for all` em vez de policies granulares
445
-
446
- **Errado:**
447
- ```sql
448
- create policy "users_manage_own_tasks"
449
- on public.tasks
450
- for all -- cobre CRUD inteiro com mesma regra
451
- to authenticated
452
- using ((select auth.uid()) = user_id);
453
- ```
454
-
455
- **Por quê:** semântica de `for all` mistura `using` (que controla SELECT/UPDATE/DELETE) com `with check` (que controla INSERT/UPDATE), levando a confusão. Em UPDATE você pode querer regras diferentes para "qual linha tocar" vs "qual estado novo". Granularidade explícita previne erros sutis.
456
-
457
- **Certo:** ver pattern com 4 policies separadas acima (SELECT, INSERT, UPDATE, DELETE).
458
-
459
- ### Anti-pattern 4: Sem índice nas colunas da policy
460
-
461
- **Errado:**
462
- ```sql
463
- -- policy referencia user_id mas não há index
464
- create policy "users_select_own_tasks" on public.tasks
465
- for select to authenticated
466
- using ((select auth.uid()) = user_id);
467
-
468
- -- (esqueceu) create index on public.tasks (user_id);
469
- ```
470
-
471
- **Por quê:** cada query com filtro RLS força sequential scan. Em produção com 100k+ linhas, isso é lentidão crônica.
472
-
473
- **Certo:**
474
- ```sql
475
- create index tasks_user_id_idx on public.tasks (user_id);
476
- ```
477
-
478
- ### Anti-pattern 5: ENABLE RLS sem GRANT — query falha silenciosa (v1.23)
479
-
480
- **Errado:**
481
- ```sql
482
- alter table public.tasks enable row level security;
483
- -- esqueceu de grant select on public.tasks to authenticated;
484
- create policy "users_select" ... using (...);
485
- ```
486
-
487
- **Por quê:** RLS é camada *sobre* permissões de tabela. Sem GRANT, role não pode tentar acessar a tabela — query retorna "permission denied" mesmo se policy permitiria.
488
-
489
- **Certo:** sempre GRANT primeiro, depois ENABLE RLS, depois policies (ver "Setup canônico" acima).
490
-
491
- ### Anti-pattern 6: View sem `security_invoker=true` em Postgres 15+ — bypass de RLS (v1.23)
492
-
493
- **Errado:**
494
- ```sql
495
- -- view criada como postgres user — bypassa RLS de public.tasks
496
- create view public.user_tasks_view as
497
- select id, title from public.tasks where user_id = auth.uid();
498
- ```
499
-
500
- **Por quê:** views por default são `security_definer` (rodam com permissões do criador). Cliente acessando a view bypass RLS das tabelas underlying. Atacante consegue ler dados de outros users via SELECT na view.
501
-
502
- **Certo:**
503
- ```sql
504
- create view public.user_tasks_view
505
- with (security_invoker = true)
506
- as
507
- select id, title from public.tasks
508
- where user_id = (select auth.uid());
509
- ```
510
-
511
- ### Anti-pattern 7: `null = user_id` silent-fail (v1.23)
512
-
513
- **Errado:**
514
- ```sql
515
- using ((select auth.uid()) = user_id)
516
- -- se user não logado, auth.uid() é null
517
- -- null = user_id é always false → policy "funciona" mas confunde
518
- ```
519
-
520
- **Por quê:** intent ambíguo — você queria bloquear não-logados ou tratar como caso especial? `null = X` retornar false silenciosamente esconde o intent e dificulta debug.
521
-
522
- **Certo:**
523
- ```sql
524
- using (
525
- (select auth.uid()) is not null
526
- and (select auth.uid()) = user_id
527
- )
528
- ```
529
-
530
- ## Postgres Roles vs RLS — quando usar qual (v1.26)
531
-
532
- Postgres roles e RLS são **conceitos complementares**:
533
-
534
- | | Postgres Roles | RLS |
535
- |---|---|---|
536
- | Escopo | System access (service accounts, cron, BI) | Application access (end-users) |
537
- | Identidade | Login Postgres (`SET ROLE`) | JWT (`auth.uid()`) |
538
- | Granularidade | Per schema/table/function | Per linha + coluna |
539
- | Audit | pg_stat_statements por role | RLS denial logs |
540
- | Use case | "Cron job pode SELECT em todas tabs" | "User vê apenas próprias rows" |
541
-
542
- **Princípio canônico v1.26:**
543
-
544
- - **Para end-users:** RLS + Custom Claims (v1.25) — NÃO criar role Postgres por user
545
- - **Para service accounts:** Postgres roles dedicados (NÃO usar service_role API key sempre)
546
- - **Para column-level access:** Postgres roles + column-level GRANTs (skill `supabase-column-level-security` v1.24)
547
-
548
- Padrão completo de Postgres roles em [`supabase-postgres-roles`](../supabase-postgres-roles/SKILL.md) (v1.26).
549
-
550
- ## RBAC via Custom Claims + authorize() function (v1.25)
551
-
552
- A partir de v1.25, o pattern **canônico** de RBAC em Supabase é via **Custom Access Token Auth Hook** que injeta `user_role` no JWT durante geração do token. Em vez de policies fazendo JOIN custoso em `user_roles` table, a policy lê o claim direto via `auth.jwt() ->> 'user_role'` (ou via `authorize()` function que abstrai role → permission lookup).
553
-
554
- ```sql
555
- -- Pattern v1.25 — RLS policy usando authorize() (claim do JWT consultado)
556
- create policy "Allow authorized delete access" on public.channels for delete
557
- to authenticated
558
- using ((SELECT authorize('channels.delete')));
559
-
560
- -- vs. pattern v1.21 — RLS policy usando helper function STABLE (JOIN em DB)
561
- create policy "Allow admin delete" on public.channels for delete
562
- to authenticated
563
- using ((SELECT private.has_role('admin')));
564
- ```
565
-
566
- **Vantagens canônicas do pattern v1.25:**
567
-
568
- - **Performance:** claim no JWT é zero-JOIN; helper function STABLE faz query em user_roles table
569
- - **Composability:** `authorize(permission)` abstrai role → permission; trocar quem tem permission = UPDATE em role_permissions (sem alterar policies)
570
- - **Type safety:** `app_permission` enum garante consistência cross-policy
571
-
572
- **Caveat JWT freshness:** mudanças em `user_roles` só refletem no JWT após refresh (TTL 1h). Para revogação imediata, force logout via `auth.admin.signOut(userId)`. Em multi-tenant complexo (role por org), **combine** custom claim (role global) + helper function PG (role context-aware) — claim sozinho não cobre per-org context.
573
-
574
- Padrão completo (7 passos + anti-patterns + caveats) em [`supabase-custom-claims-rbac`](../supabase-custom-claims-rbac/SKILL.md) (v1.25).
575
-
576
- ## Combining RLS with Column-Level Privileges (v1.24)
577
-
578
- RLS row-level e column-level privileges são **camadas complementares**:
579
-
580
- - **RLS** filtra **quais linhas** o role vê/modifica
581
- - **Column privileges** filtra **quais colunas** o role pode acessar dentro da linha
582
-
583
- Combinação canônica: RLS + column-level (Camada 8 de defense-in-depth, skill `supabase-rls-defense-in-depth` v1.24).
584
-
585
- ```sql
586
- -- 1. RLS row-level — user só vê próprias posts
587
- create policy "users_select_own_posts"
588
- on public.posts for select to authenticated
589
- using ((select auth.uid()) is not null and (select auth.uid()) = user_id);
590
-
591
- -- 2. Column-level — mesmo nas próprias posts, user não vê coluna sensível (ex: admin_notes)
592
- revoke select on table public.posts from authenticated;
593
- grant select (id, user_id, title, content, created_at) on table public.posts to authenticated;
594
-
595
- -- 3. service_role / admin_role vê tudo (incluindo admin_notes)
596
- grant select on table public.posts to service_role;
597
-
598
- -- Cliente DEVE listar colunas explicitamente:
599
- -- ❌ supabase.from('posts').select() — FALHA (wildcard expansion → admin_notes)
600
- -- ✅ supabase.from('posts').select('id, user_id, title, content, created_at')
601
- ```
602
-
603
- **Quando combinar:**
604
-
605
- - Compliance LGPD/GDPR onde algumas colunas (PII) precisam restrição extra além do RLS
606
- - Audit log com payload sanitizado — RLS filtra por org, column priv filtra payload
607
- - Billing data — RLS filtra por owner, column priv filtra credit_card_token
608
-
609
- **Quando NÃO combinar:**
610
-
611
- - Caso comum (admin/user roles) — use dedicated role table (skill [`supabase-column-level-security`](../supabase-column-level-security/SKILL.md)) ao invés
612
- - Tabelas sem PII real — overhead sem benefício
613
-
614
- **Caveat crítico:** com column privileges, **todo SELECT deve listar colunas explicitamente** — `SELECT *` falha. Atualize SDK calls + queries SQL ad-hoc + ferramentas BI conectadas.
615
-
616
- Padrão completo + 4 patterns canônicos em [`supabase-column-level-security`](../supabase-column-level-security/SKILL.md) (v1.24).
617
-
618
- ## Bypassing RLS — quando e como
619
-
620
- 3 mecanismos canônicos para bypass de RLS:
621
-
622
- 1. **`service_role`** — chave Supabase com bypass automático. **NUNCA** exponha ao cliente. Use APENAS em backend (Edge Functions com env var `SUPABASE_SERVICE_ROLE_KEY`, scripts admin, migrations). Caveat: ao chamar SDK Supabase com service_role mas com `Authorization: Bearer <user_jwt>` ainda set, RLS do user é aplicado (override).
623
- 2. **`alter role <name> with bypassrls`** — privilégio Postgres que permite role bypass RLS sempre. Use para roles internos (`postgres`, custom admin role). NUNCA conceda a um role que recebe requisições de cliente.
624
- 3. **`security definer` functions** — função roda com permissões do criador (geralmente `postgres` = bypassrls). Encapsule lógica admin/cross-tenant em função `security definer` no schema `private`.
625
-
626
- Padrões avançados em [`supabase-rls-defense-in-depth`](../supabase-rls-defense-in-depth/SKILL.md) (v1.23).
627
-
628
- ## Ver também
629
-
630
- - [supabase-rls-defense-in-depth](../supabase-rls-defense-in-depth/SKILL.md) — event trigger, BYPASSRLS, service_role caveat, security definer, views security_invoker (v1.23)
631
- - [supabase-database-functions](../supabase-database-functions/SKILL.md) — funções com `set search_path = ''` que respeitam RLS
632
- - [supabase-storage](../supabase-storage/SKILL.md) — RLS sobre `storage.objects` (multi-tenant path isolation)
633
- - [supabase-auth-ssr](../supabase-auth-ssr/SKILL.md) — autenticação que popula `auth.uid()`
634
- - [supabase-migrations](../supabase-migrations/SKILL.md) — migrations sempre com GRANT + RLS habilitado em novas tabelas
635
- - [glossário](../_shared-supabase/glossary.md) — termos PT-BR↔EN + roles + comandos CLI
1
+ ---
2
+ name: supabase-rls-policies
3
+ description: Use ao criar/auditar RLS — sempre (select auth.uid()), policies separadas por operação, GRANTs antes de ENABLE RLS, IS NOT NULL para anti silent-fail, índices nas colunas, NUNCA user_meta…
4
+ ---
5
+
6
+ # Supabase — RLS Policies
7
+
8
+ ## Quando usar
9
+
10
+ LLM carrega esta skill quando criar, auditar ou debugar Row Level Security em Supabase. Trigger phrases:
11
+
12
+ - "criar policy RLS", "RLS policy", "row level security"
13
+ - "policies separadas por operação"
14
+ - "auth.uid()", "auth.jwt()"
15
+ - "MFA enforcement", "AAL2"
16
+ - "auditar segurança de tabela Supabase"
17
+ - "GRANT antes de ENABLE RLS", "defense in depth", "security_invoker"
18
+ - "anon role vs anonymous user", "raw_app_meta_data vs raw_user_meta_data"
19
+
20
+ ## Defense in depth — RLS como camada (v1.23)
21
+
22
+ RLS é um **Postgres primitive** que oferece **defense in depth**: protege dados mesmo quando acessados por third-party tooling (Metabase, dbt, ferramentas BI conectadas via JDBC). Mesmo se um vazamento de chave API ou bypass na camada de aplicação acontece, RLS impede acesso indevido **no banco**.
23
+
24
+ A regra mestre:
25
+
26
+ > **RLS *must* always be enabled on any tables stored in an exposed schema. By default, this is the `public` schema.**
27
+
28
+ Se você não tem RLS na camada do banco, você está confiando que **todo cliente** (front-end, backend, third-party, scripts, MCP tools) faça filtering corretamente. Isso é frágil.
29
+
30
+ **Princípio em v1.23 (handoff cooperativo):** todo SQL gerado pelo kit passa pelo `supabase-rls-hardener` antes do output final — drafts upstream são preservados, mas hardening RLS é obrigatório.
31
+
32
+ ## Regras absolutas
33
+
34
+ **WARNING — REGRA #1 (segurança crítica):** **NUNCA** referencie `user_metadata` em policy de autorização. `user_metadata` é editável pelo cliente via `auth.updateUser({data: {...}})` — usuário pode auto-elevar `role: 'admin'` ou `plan: 'premium'`. Use **`app_metadata`** (set apenas via service_role) para roles/permissions. Splinter linter 0015 detecta automaticamente.
35
+
36
+ **REGRA #2 (performance crítica):** **SEMPRE** envolva `auth.uid()` em `(select auth.uid())`. Sem o wrapper, Postgres reavalia a função **uma vez por linha** — degrada queries com filtro RLS em **até 1000×**. Documentado nos benchmarks oficiais (`test2a-wrappedSQL-uid`: 179ms → 9ms, 94.97% improvement).
37
+
38
+ **REGRA #3 (anti silent-fail anônimo — v1.23):** Para policies que dependem de identidade autenticada, use **`auth.uid() IS NOT NULL AND auth.uid() = user_id`** ao invés de apenas `(select auth.uid()) = user_id`. Quando o usuário não está logado, `auth.uid()` retorna `null`, e `null = user_id` é **sempre false** silenciosamente — a policy "funciona" mas confunde debugging. O check explícito de `IS NOT NULL` deixa intent claro.
39
+
40
+ **Outras regras:**
41
+
42
+ - **`GRANT` antes de `ENABLE RLS`** (v1.23) — sempre conceda privilégios necessários aos roles `anon`/`authenticated`/`service_role` ANTES de habilitar RLS. Sem GRANT, mesmo policies "permissive" falham porque o role não tem permissão de tabela.
43
+ - **`policies separadas por operação`** — uma `for select`, uma `for insert`, uma `for update`, uma `for delete`. **Nunca** `for all` cobrindo CRUD inteiro.
44
+ - **`TO authenticated`** ou **`to anon`** sempre explícito — nunca deixar implícito (default `to public` é insecure; impede otimização do executor que skipa execução de policy para roles fora do TO clause).
45
+ - `for select` e `for delete` usam **apenas `using`** (sem `with check`).
46
+ - `for insert` usa **apenas `with check`** (sem `using`).
47
+ - `for update` usa **`using` + `with check`** (using para qual linha pode ser atualizada, with check para qual estado a linha pode assumir).
48
+ - Índice obrigatório nas colunas referenciadas pela policy: `create index on public.tasks (user_id);`. Sem index, scan full em cada query.
49
+ - `permissive` é default e preferido. `restrictive` é raro e exige justificativa explícita.
50
+ - Para MFA enforcement: `(auth.jwt()->>'aal')::text = 'aal2'` em policies que exigem 2FA ativo.
51
+ - **Views** com `security_invoker=true` (Postgres 15+) — por padrão views bypassam RLS (criadas como `security_definer` rodando como `postgres`). Ver seção "Views" abaixo.
52
+
53
+ ## Setup canônico — GRANTs + ENABLE RLS (v1.23)
54
+
55
+ O setup completo para uma tabela em schema exposto é:
56
+
57
+ ```sql
58
+ -- 1. GRANTs por role (faça ANTES de ENABLE RLS)
59
+ grant select on public.tasks to anon;
60
+ grant select, insert, update, delete on public.tasks to authenticated;
61
+ grant select, insert, update, delete on public.tasks to service_role;
62
+
63
+ -- 2. Enable RLS
64
+ alter table public.tasks enable row level security;
65
+
66
+ -- 3. Policies granulares (sem isso, nada é acessível com publishable key)
67
+ create policy "users_select_own_tasks"
68
+ on public.tasks for select to authenticated
69
+ using ((select auth.uid()) is not null and (select auth.uid()) = user_id);
70
+
71
+ -- ... INSERT/UPDATE/DELETE policies análogos
72
+
73
+ -- 4. Índice obrigatório
74
+ create index tasks_user_id_idx on public.tasks (user_id);
75
+ ```
76
+
77
+ **Por que GRANTs antes de ENABLE RLS:** RLS é uma camada de filtragem de linhas **adicionada** sobre as permissões de tabela. Sem `GRANT SELECT TO authenticated`, mesmo se a policy retorna true, a query falha com "permission denied". Os GRANTs estabelecem o que o role *pode tentar* fazer; RLS estabelece *quais linhas* ele vê.
78
+
79
+ **Roles canônicos Supabase:**
80
+
81
+ - `anon` — requisição sem autenticação (usuário deslogado)
82
+ - `authenticated` — requisição com JWT válido de Supabase Auth
83
+ - `service_role` — bypassa RLS (ver "Bypassing RLS" abaixo); use APENAS em backend/admin
84
+
85
+ ## Auto-enable RLS para tabelas novas (v1.23)
86
+
87
+ Para evitar esquecimento humano, instale event trigger que ativa RLS automaticamente em `CREATE TABLE`:
88
+
89
+ ```sql
90
+ create or replace function rls_auto_enable()
91
+ returns event_trigger
92
+ language plpgsql
93
+ security definer
94
+ set search_path = pg_catalog
95
+ as $$
96
+ declare
97
+ cmd record;
98
+ begin
99
+ for cmd in
100
+ select *
101
+ from pg_event_trigger_ddl_commands()
102
+ where command_tag in ('CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO')
103
+ and object_type in ('table','partitioned table')
104
+ loop
105
+ if cmd.schema_name is not null and cmd.schema_name in ('public') and cmd.schema_name not in ('pg_catalog','information_schema') and cmd.schema_name not like 'pg_toast%' and cmd.schema_name not like 'pg_temp%' then
106
+ begin
107
+ execute format('alter table if exists %s enable row level security', cmd.object_identity);
108
+ raise log 'rls_auto_enable: enabled RLS on %', cmd.object_identity;
109
+ exception
110
+ when others then
111
+ raise log 'rls_auto_enable: failed to enable RLS on %', cmd.object_identity;
112
+ end;
113
+ else
114
+ raise log 'rls_auto_enable: skip % (system schema or not in enforced list)', cmd.object_identity;
115
+ end if;
116
+ end loop;
117
+ end;
118
+ $$;
119
+
120
+ drop event trigger if exists ensure_rls;
121
+ create event trigger ensure_rls
122
+ on ddl_command_end
123
+ when tag in ('CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO')
124
+ execute function rls_auto_enable();
125
+ ```
126
+
127
+ **Caveats:**
128
+ - Aplica-se a tabelas criadas **após** o trigger ser instalado — existentes precisam de `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` manual.
129
+ - Tabelas em `pg_catalog`, `information_schema`, `pg_toast*`, `pg_temp*` são skipped (sistema).
130
+ - Padrão completo + variações em [`supabase-rls-defense-in-depth`](../supabase-rls-defense-in-depth/SKILL.md) (v1.23).
131
+
132
+ ## `anon` Postgres role vs anonymous Auth user (v1.23)
133
+
134
+ Confusão comum: **`anon` Postgres role** ≠ **anonymous user de Supabase Auth**.
135
+
136
+ - **`anon` Postgres role** — requisição sem JWT (usuário deslogado). É um Postgres role como qualquer outro; aparece em `TO anon` clauses.
137
+ - **anonymous user de Supabase Auth** — usuário criado via `supabase.auth.signInAnonymously()` (Auth feature). Tem JWT válido, assume o role `authenticated`, e pode ser diferenciado por checar a claim `is_anonymous` no JWT.
138
+
139
+ **Implicação em policies:**
140
+
141
+ ```sql
142
+ -- visivel a qualquer cliente (anon Postgres ou authenticated com is_anonymous=true ou regular)
143
+ create policy "public_profiles_view"
144
+ on public.profiles for select
145
+ to authenticated, anon
146
+ using (true);
147
+
148
+ -- bloqueia anonymous Auth users mesmo que estejam autenticados
149
+ create policy "premium_features_no_anonymous"
150
+ on public.premium_data for select
151
+ to authenticated
152
+ using (
153
+ (select auth.jwt()->>'is_anonymous')::boolean is not true
154
+ and (select auth.uid()) = user_id
155
+ );
156
+ ```
157
+
158
+ ## Patterns canônicos
159
+
160
+ ### SELECT — usuário lê apenas suas próprias linhas
161
+
162
+ ```sql
163
+ -- política de SELECT com wrapper (select auth.uid()) + IS NOT NULL anti silent-fail
164
+ create policy "users_select_own_tasks"
165
+ on public.tasks
166
+ for select
167
+ to authenticated
168
+ using (
169
+ (select auth.uid()) is not null
170
+ and (select auth.uid()) = user_id
171
+ );
172
+
173
+ -- index obrigatório (sem isso, scan full)
174
+ create index tasks_user_id_idx on public.tasks (user_id);
175
+ ```
176
+
177
+ ### INSERT, UPDATE, DELETE separados
178
+
179
+ ```sql
180
+ -- INSERT — usuário só pode criar linhas com user_id = ele mesmo
181
+ create policy "users_insert_own_tasks"
182
+ on public.tasks
183
+ for insert
184
+ to authenticated
185
+ with check (
186
+ (select auth.uid()) is not null
187
+ and (select auth.uid()) = user_id
188
+ );
189
+
190
+ -- UPDATE — restringe quais linhas (using) E qual estado novo (with check)
191
+ -- IMPORTANTE: UPDATE também exige policy SELECT correspondente (sem ela, UPDATE não funciona)
192
+ create policy "users_update_own_tasks"
193
+ on public.tasks
194
+ for update
195
+ to authenticated
196
+ using (
197
+ (select auth.uid()) is not null
198
+ and (select auth.uid()) = user_id
199
+ )
200
+ with check (
201
+ (select auth.uid()) is not null
202
+ and (select auth.uid()) = user_id
203
+ );
204
+
205
+ -- DELETE — apenas a coluna using (sem with check)
206
+ create policy "users_delete_own_tasks"
207
+ on public.tasks
208
+ for delete
209
+ to authenticated
210
+ using (
211
+ (select auth.uid()) is not null
212
+ and (select auth.uid()) = user_id
213
+ );
214
+ ```
215
+
216
+ ### Role admin via `app_metadata`
217
+
218
+ ```sql
219
+ -- segurança: app_metadata é set apenas via service_role (admin API)
220
+ -- cliente NÃO pode mutá-lo
221
+ create policy "admins_manage_all_tasks"
222
+ on public.tasks
223
+ for update
224
+ to authenticated
225
+ using (
226
+ (select auth.jwt()->'app_metadata'->>'role') = 'admin'
227
+ )
228
+ with check (
229
+ (select auth.jwt()->'app_metadata'->>'role') = 'admin'
230
+ );
231
+ ```
232
+
233
+ ### Team membership via `app_metadata` (array)
234
+
235
+ ```sql
236
+ -- exemplo: app_metadata.teams = ["team_a", "team_b"]
237
+ create policy "team_members_view"
238
+ on public.team_resources
239
+ for select
240
+ to authenticated
241
+ using (
242
+ team_id::text in (select jsonb_array_elements_text((select auth.jwt()->'app_metadata'->'teams')))
243
+ );
244
+ ```
245
+
246
+ ### MFA enforcement (AAL2)
247
+
248
+ ```sql
249
+ -- exigir 2FA ativo para acessar dados sensíveis
250
+ -- restrictive force AND com policy SELECT base
251
+ create policy "mfa_required_for_billing"
252
+ on public.billing_records
253
+ as restrictive
254
+ for select
255
+ to authenticated
256
+ using (
257
+ (select (auth.jwt()->>'aal')::text) = 'aal2'
258
+ );
259
+ ```
260
+
261
+ ### Views com `security_invoker=true` (Postgres 15+) — v1.23
262
+
263
+ Por padrão, views são criadas com `security_definer` (rodam com permissões do criador, geralmente `postgres`) — **bypassam RLS** das tabelas subjacentes. Em Postgres 15+, use `security_invoker=true` para fazer a view respeitar as policies RLS do role chamador:
264
+
265
+ ```sql
266
+ create view public.user_active_tasks
267
+ with (security_invoker = true)
268
+ as
269
+ select id, title, status, created_at
270
+ from public.tasks
271
+ where status = 'active';
272
+ ```
273
+
274
+ **Em versões < Postgres 15:** revoke acesso de `anon`/`authenticated` à view, ou crie em schema não-exposto:
275
+
276
+ ```sql
277
+ -- alternativa pré-Postgres 15: revoke acesso
278
+ revoke select on public.legacy_view from anon, authenticated;
279
+
280
+ -- ou crie em schema privado:
281
+ create view private.internal_view as ...;
282
+ ```
283
+
284
+ ## `app_metadata` vs `user_metadata` — caveats canônicos (v1.23)
285
+
286
+ A função `auth.jwt()` retorna o JWT do usuário fazendo a requisição. Existem duas claims relacionadas a metadata:
287
+
288
+ - **`raw_user_meta_data`** — pode ser atualizado pelo authenticated end-user via `supabase.auth.updateUser({ data: { ... } })`. **NÃO é seguro para authorization.** Use apenas para preferences (tema, idioma, avatar URL).
289
+ - **`raw_app_meta_data`** — **NÃO pode ser atualizado pelo cliente** — só via service_role + admin API. **É o lugar correto** para roles, permissions, team memberships, plan tier.
290
+
291
+ **Caveat #1 (JWT freshness):** JWT nem sempre está "fresh". Se você remove um user de um team e atualiza `app_metadata`, isso não reflete em `auth.jwt()` até o JWT ser refreshed (geralmente 1h TTL). Para invalidação imediata, force logout do user via `auth.admin.signOut()`.
292
+
293
+ **Caveat #2 (cookie 4096 bytes):** Se você usa Cookies para Auth, esteja atento ao JWT size. Browsers limitam cada cookie a 4096 bytes. Se você embarca arrays grandes em `app_metadata.teams` ou similar, o JWT pode passar do limite. Mitigação: store apenas IDs, busque membership via SQL com policy/RPC.
294
+
295
+ **Caveat #3 (NULL handling):** Para requests sem auth, `auth.uid()` retorna `null`. `null = user_id` é sempre **false silenciosamente** em SQL — a policy não erra, só não match. Sempre use `IS NOT NULL AND ...` (REGRA #3) para deixar intent claro.
296
+
297
+ ## Performance — recomendações canônicas (v1.23)
298
+
299
+ Toda authorization tem custo. RLS é poderoso mas pode ser caro em queries que scaneam muitas linhas. Recomendações baseadas em benchmarks oficiais Supabase:
300
+
301
+ ### 1. Index nas colunas usadas em policies (99.94% improvement)
302
+
303
+ ```sql
304
+ -- antes: scan full
305
+ -- depois: index scan
306
+ create index tasks_user_id_idx on public.tasks (user_id);
307
+ ```
308
+
309
+ ### 2. Envolva funções em `(select ...)` para caching de plano (até 99.99% improvement)
310
+
311
+ ```sql
312
+ -- antes: re-executa auth.uid() por linha (179ms para 100k rows)
313
+ using (auth.uid() = user_id)
314
+
315
+ -- depois: initPlan, executa 1 vez e reusa (9ms)
316
+ using ((select auth.uid()) = user_id)
317
+ ```
318
+
319
+ Aplicável a qualquer função que não muda baseado em row data: `auth.uid()`, `auth.jwt()`, `security definer` functions. **Não funciona** se o resultado depende da linha (ex: `is_owner(row_id)` precisa rodar por linha).
320
+
321
+ ### 3. Adicione filtros redundantes nas queries client-side (94.74% improvement)
322
+
323
+ Mesmo com policy aplicada, adicione `.eq()` ou `where` explícito no client:
324
+
325
+ ```js
326
+ // errado — confia 100% na policy
327
+ const { data } = supabase.from('tasks').select()
328
+
329
+ // certo — Postgres usa o filtro para construir query plan melhor
330
+ const { data } = supabase.from('tasks').select().eq('user_id', userId)
331
+ ```
332
+
333
+ Policy é "implicit where clause"; filtro explícito ajuda o planner Postgres a escolher index scan ao invés de seq scan.
334
+
335
+ ### 4. Specify role com `TO` clause (99.78% improvement)
336
+
337
+ ```sql
338
+ -- antes (sem TO): policy roda para todos roles, inclusive anon
339
+ create policy "rls_test_select" on rls_test
340
+ using ((select auth.uid()) = user_id);
341
+
342
+ -- depois: anon skipa execução
343
+ create policy "rls_test_select" on rls_test
344
+ to authenticated
345
+ using ((select auth.uid()) = user_id);
346
+ ```
347
+
348
+ ### 5. Use `security definer` functions para policies caras
349
+
350
+ Se a policy precisa fazer JOIN ou query custosa, encapsule em função `security definer` que bypassa RLS interno:
351
+
352
+ ```sql
353
+ -- função pode acessar roles_table sem aplicar RLS recursivamente
354
+ create function private.has_good_role()
355
+ returns boolean
356
+ language plpgsql
357
+ security definer
358
+ set search_path = ''
359
+ as $$
360
+ begin
361
+ return exists (
362
+ select 1 from public.roles_table
363
+ where (select auth.uid()) = user_id and role = 'good_role'
364
+ );
365
+ end;
366
+ $$;
367
+
368
+ -- policy fica simples e cacheável
369
+ create policy "rls_test_select"
370
+ on public.test_table
371
+ to authenticated
372
+ using ((select private.has_good_role()));
373
+ ```
374
+
375
+ **IMPORTANTE:** funções `security definer` NUNCA em schema exposto (`public`). Sempre em `private` ou similar (não exposto via API settings).
376
+
377
+ ### 6. Minimize joins (99.78% improvement)
378
+
379
+ Reescreva policies que fazem join na source table para usar `IN` ao invés:
380
+
381
+ ```sql
382
+ -- antes: join entre test_table (source) e team_user (target)
383
+ create policy "rls_test_select" on public.test_table
384
+ to authenticated
385
+ using (
386
+ (select auth.uid()) in (
387
+ select user_id
388
+ from public.team_user
389
+ where team_user.team_id = team_id -- join com source!
390
+ )
391
+ );
392
+
393
+ -- depois: filtra primeiro, depois IN
394
+ create policy "rls_test_select" on public.test_table
395
+ to authenticated
396
+ using (
397
+ team_id in (
398
+ select team_id
399
+ from public.team_user
400
+ where user_id = (select auth.uid()) -- sem join
401
+ )
402
+ );
403
+ ```
404
+
405
+ Se a lista interna pode passar de 1000 items, considere abordagem com `security definer` function ao invés.
406
+
407
+ ## Anti-patterns
408
+
409
+ ### Anti-pattern 1: `auth.uid()` sem `(select)` wrapper
410
+
411
+ **Errado:**
412
+ ```sql
413
+ create policy "users_select_own_tasks"
414
+ on public.tasks
415
+ for select
416
+ to authenticated
417
+ using (auth.uid() = user_id); -- sem (select) — re-executa por linha
418
+ ```
419
+
420
+ **Por quê:** Postgres reavalia `auth.uid()` para cada linha sendo testada. Em tabela com 100k linhas, isso é 100k chamadas. O `(select)` permite Postgres executar **uma vez** e reusar — degradação de até **1000×** sem o wrapper. Documentado em [RLS Performance](https://supabase.com/docs/guides/troubleshooting/rls-performance-and-best-practices-Z5Jjwv).
421
+
422
+ **Certo:**
423
+ ```sql
424
+ using ((select auth.uid()) = user_id)
425
+ ```
426
+
427
+ ### Anti-pattern 2: `WARNING user_metadata` em autorização — privilege escalation
428
+
429
+ **Errado:**
430
+ ```sql
431
+ create policy "admins_manage_all"
432
+ on public.tasks
433
+ for update
434
+ to authenticated
435
+ using (
436
+ (auth.jwt()->'user_metadata'->>'role') = 'admin' -- editável pelo cliente!
437
+ );
438
+ ```
439
+
440
+ **Por quê:** o cliente pode chamar `supabase.auth.updateUser({ data: { role: 'admin' } })` e instantaneamente ganhar privilégios de admin. `user_metadata` é projetado para preferences do usuário (tema, idioma), não para autorização. Documentado em [Splinter linter 0015](https://supabase.github.io/splinter/0015_rls_references_user_metadata/).
441
+
442
+ **Certo:** ver "Role admin via `app_metadata`" acima — `app_metadata` requer service_role para mutar.
443
+
444
+ ### Anti-pattern 3: `for all` em vez de policies granulares
445
+
446
+ **Errado:**
447
+ ```sql
448
+ create policy "users_manage_own_tasks"
449
+ on public.tasks
450
+ for all -- cobre CRUD inteiro com mesma regra
451
+ to authenticated
452
+ using ((select auth.uid()) = user_id);
453
+ ```
454
+
455
+ **Por quê:** semântica de `for all` mistura `using` (que controla SELECT/UPDATE/DELETE) com `with check` (que controla INSERT/UPDATE), levando a confusão. Em UPDATE você pode querer regras diferentes para "qual linha tocar" vs "qual estado novo". Granularidade explícita previne erros sutis.
456
+
457
+ **Certo:** ver pattern com 4 policies separadas acima (SELECT, INSERT, UPDATE, DELETE).
458
+
459
+ ### Anti-pattern 4: Sem índice nas colunas da policy
460
+
461
+ **Errado:**
462
+ ```sql
463
+ -- policy referencia user_id mas não há index
464
+ create policy "users_select_own_tasks" on public.tasks
465
+ for select to authenticated
466
+ using ((select auth.uid()) = user_id);
467
+
468
+ -- (esqueceu) create index on public.tasks (user_id);
469
+ ```
470
+
471
+ **Por quê:** cada query com filtro RLS força sequential scan. Em produção com 100k+ linhas, isso é lentidão crônica.
472
+
473
+ **Certo:**
474
+ ```sql
475
+ create index tasks_user_id_idx on public.tasks (user_id);
476
+ ```
477
+
478
+ ### Anti-pattern 5: ENABLE RLS sem GRANT — query falha silenciosa (v1.23)
479
+
480
+ **Errado:**
481
+ ```sql
482
+ alter table public.tasks enable row level security;
483
+ -- esqueceu de grant select on public.tasks to authenticated;
484
+ create policy "users_select" ... using (...);
485
+ ```
486
+
487
+ **Por quê:** RLS é camada *sobre* permissões de tabela. Sem GRANT, role não pode tentar acessar a tabela — query retorna "permission denied" mesmo se policy permitiria.
488
+
489
+ **Certo:** sempre GRANT primeiro, depois ENABLE RLS, depois policies (ver "Setup canônico" acima).
490
+
491
+ ### Anti-pattern 6: View sem `security_invoker=true` em Postgres 15+ — bypass de RLS (v1.23)
492
+
493
+ **Errado:**
494
+ ```sql
495
+ -- view criada como postgres user — bypassa RLS de public.tasks
496
+ create view public.user_tasks_view as
497
+ select id, title from public.tasks where user_id = auth.uid();
498
+ ```
499
+
500
+ **Por quê:** views por default são `security_definer` (rodam com permissões do criador). Cliente acessando a view bypass RLS das tabelas underlying. Atacante consegue ler dados de outros users via SELECT na view.
501
+
502
+ **Certo:**
503
+ ```sql
504
+ create view public.user_tasks_view
505
+ with (security_invoker = true)
506
+ as
507
+ select id, title from public.tasks
508
+ where user_id = (select auth.uid());
509
+ ```
510
+
511
+ ### Anti-pattern 7: `null = user_id` silent-fail (v1.23)
512
+
513
+ **Errado:**
514
+ ```sql
515
+ using ((select auth.uid()) = user_id)
516
+ -- se user não logado, auth.uid() é null
517
+ -- null = user_id é always false → policy "funciona" mas confunde
518
+ ```
519
+
520
+ **Por quê:** intent ambíguo — você queria bloquear não-logados ou tratar como caso especial? `null = X` retornar false silenciosamente esconde o intent e dificulta debug.
521
+
522
+ **Certo:**
523
+ ```sql
524
+ using (
525
+ (select auth.uid()) is not null
526
+ and (select auth.uid()) = user_id
527
+ )
528
+ ```
529
+
530
+ ## Postgres Roles vs RLS — quando usar qual (v1.26)
531
+
532
+ Postgres roles e RLS são **conceitos complementares**:
533
+
534
+ | | Postgres Roles | RLS |
535
+ |---|---|---|
536
+ | Escopo | System access (service accounts, cron, BI) | Application access (end-users) |
537
+ | Identidade | Login Postgres (`SET ROLE`) | JWT (`auth.uid()`) |
538
+ | Granularidade | Per schema/table/function | Per linha + coluna |
539
+ | Audit | pg_stat_statements por role | RLS denial logs |
540
+ | Use case | "Cron job pode SELECT em todas tabs" | "User vê apenas próprias rows" |
541
+
542
+ **Princípio canônico v1.26:**
543
+
544
+ - **Para end-users:** RLS + Custom Claims (v1.25) — NÃO criar role Postgres por user
545
+ - **Para service accounts:** Postgres roles dedicados (NÃO usar service_role API key sempre)
546
+ - **Para column-level access:** Postgres roles + column-level GRANTs (skill `supabase-column-level-security` v1.24)
547
+
548
+ Padrão completo de Postgres roles em [`supabase-postgres-roles`](../supabase-postgres-roles/SKILL.md) (v1.26).
549
+
550
+ ## RBAC via Custom Claims + authorize() function (v1.25)
551
+
552
+ A partir de v1.25, o pattern **canônico** de RBAC em Supabase é via **Custom Access Token Auth Hook** que injeta `user_role` no JWT durante geração do token. Em vez de policies fazendo JOIN custoso em `user_roles` table, a policy lê o claim direto via `auth.jwt() ->> 'user_role'` (ou via `authorize()` function que abstrai role → permission lookup).
553
+
554
+ ```sql
555
+ -- Pattern v1.25 — RLS policy usando authorize() (claim do JWT consultado)
556
+ create policy "Allow authorized delete access" on public.channels for delete
557
+ to authenticated
558
+ using ((SELECT authorize('channels.delete')));
559
+
560
+ -- vs. pattern v1.21 — RLS policy usando helper function STABLE (JOIN em DB)
561
+ create policy "Allow admin delete" on public.channels for delete
562
+ to authenticated
563
+ using ((SELECT private.has_role('admin')));
564
+ ```
565
+
566
+ **Vantagens canônicas do pattern v1.25:**
567
+
568
+ - **Performance:** claim no JWT é zero-JOIN; helper function STABLE faz query em user_roles table
569
+ - **Composability:** `authorize(permission)` abstrai role → permission; trocar quem tem permission = UPDATE em role_permissions (sem alterar policies)
570
+ - **Type safety:** `app_permission` enum garante consistência cross-policy
571
+
572
+ **Caveat JWT freshness:** mudanças em `user_roles` só refletem no JWT após refresh (TTL 1h). Para revogação imediata, force logout via `auth.admin.signOut(userId)`. Em multi-tenant complexo (role por org), **combine** custom claim (role global) + helper function PG (role context-aware) — claim sozinho não cobre per-org context.
573
+
574
+ Padrão completo (7 passos + anti-patterns + caveats) em [`supabase-custom-claims-rbac`](../supabase-custom-claims-rbac/SKILL.md) (v1.25).
575
+
576
+ ## Combining RLS with Column-Level Privileges (v1.24)
577
+
578
+ RLS row-level e column-level privileges são **camadas complementares**:
579
+
580
+ - **RLS** filtra **quais linhas** o role vê/modifica
581
+ - **Column privileges** filtra **quais colunas** o role pode acessar dentro da linha
582
+
583
+ Combinação canônica: RLS + column-level (Camada 8 de defense-in-depth, skill `supabase-rls-defense-in-depth` v1.24).
584
+
585
+ ```sql
586
+ -- 1. RLS row-level — user só vê próprias posts
587
+ create policy "users_select_own_posts"
588
+ on public.posts for select to authenticated
589
+ using ((select auth.uid()) is not null and (select auth.uid()) = user_id);
590
+
591
+ -- 2. Column-level — mesmo nas próprias posts, user não vê coluna sensível (ex: admin_notes)
592
+ revoke select on table public.posts from authenticated;
593
+ grant select (id, user_id, title, content, created_at) on table public.posts to authenticated;
594
+
595
+ -- 3. service_role / admin_role vê tudo (incluindo admin_notes)
596
+ grant select on table public.posts to service_role;
597
+
598
+ -- Cliente DEVE listar colunas explicitamente:
599
+ -- ❌ supabase.from('posts').select() — FALHA (wildcard expansion → admin_notes)
600
+ -- ✅ supabase.from('posts').select('id, user_id, title, content, created_at')
601
+ ```
602
+
603
+ **Quando combinar:**
604
+
605
+ - Compliance LGPD/GDPR onde algumas colunas (PII) precisam restrição extra além do RLS
606
+ - Audit log com payload sanitizado — RLS filtra por org, column priv filtra payload
607
+ - Billing data — RLS filtra por owner, column priv filtra credit_card_token
608
+
609
+ **Quando NÃO combinar:**
610
+
611
+ - Caso comum (admin/user roles) — use dedicated role table (skill [`supabase-column-level-security`](../supabase-column-level-security/SKILL.md)) ao invés
612
+ - Tabelas sem PII real — overhead sem benefício
613
+
614
+ **Caveat crítico:** com column privileges, **todo SELECT deve listar colunas explicitamente** — `SELECT *` falha. Atualize SDK calls + queries SQL ad-hoc + ferramentas BI conectadas.
615
+
616
+ Padrão completo + 4 patterns canônicos em [`supabase-column-level-security`](../supabase-column-level-security/SKILL.md) (v1.24).
617
+
618
+ ## Bypassing RLS — quando e como
619
+
620
+ 3 mecanismos canônicos para bypass de RLS:
621
+
622
+ 1. **`service_role`** — chave Supabase com bypass automático. **NUNCA** exponha ao cliente. Use APENAS em backend (Edge Functions com env var `SUPABASE_SERVICE_ROLE_KEY`, scripts admin, migrations). Caveat: ao chamar SDK Supabase com service_role mas com `Authorization: Bearer <user_jwt>` ainda set, RLS do user é aplicado (override).
623
+ 2. **`alter role <name> with bypassrls`** — privilégio Postgres que permite role bypass RLS sempre. Use para roles internos (`postgres`, custom admin role). NUNCA conceda a um role que recebe requisições de cliente.
624
+ 3. **`security definer` functions** — função roda com permissões do criador (geralmente `postgres` = bypassrls). Encapsule lógica admin/cross-tenant em função `security definer` no schema `private`.
625
+
626
+ Padrões avançados em [`supabase-rls-defense-in-depth`](../supabase-rls-defense-in-depth/SKILL.md) (v1.23).
627
+
628
+ ## Ver também
629
+
630
+ - [supabase-rls-defense-in-depth](../supabase-rls-defense-in-depth/SKILL.md) — event trigger, BYPASSRLS, service_role caveat, security definer, views security_invoker (v1.23)
631
+ - [supabase-database-functions](../supabase-database-functions/SKILL.md) — funções com `set search_path = ''` que respeitam RLS
632
+ - [supabase-storage](../supabase-storage/SKILL.md) — RLS sobre `storage.objects` (multi-tenant path isolation)
633
+ - [supabase-auth-ssr](../supabase-auth-ssr/SKILL.md) — autenticação que popula `auth.uid()`
634
+ - [supabase-migrations](../supabase-migrations/SKILL.md) — migrations sempre com GRANT + RLS habilitado em novas tabelas
635
+ - [glossário](../_shared-supabase/glossary.md) — termos PT-BR↔EN + roles + comandos CLI