@luanpdd/kit-mcp 1.32.0 → 1.34.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 (376) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +168 -168
  3. package/gates/agent-no-recursive-dispatch.md +84 -84
  4. package/kit/COMANDOS.md +138 -138
  5. package/kit/COMPATIBILITY.md +70 -70
  6. package/kit/README.md +76 -76
  7. package/kit/agents/advisor-researcher.md +109 -109
  8. package/kit/agents/ai-mutation-tester.md +289 -289
  9. package/kit/agents/assumptions-analyzer.md +110 -110
  10. package/kit/agents/audit-log-implementer.md +314 -314
  11. package/kit/agents/auditor-consistencia-isolamento.md +414 -414
  12. package/kit/agents/b2b-saas-architect.md +157 -157
  13. package/kit/agents/burn-rate-forecaster.md +153 -153
  14. package/kit/agents/cascading-failures-auditor.md +299 -299
  15. package/kit/agents/codebase-mapper.md +769 -769
  16. package/kit/agents/crm-pipeline-implementer.md +257 -257
  17. package/kit/agents/debugger.md +814 -814
  18. package/kit/agents/designer-ui.md +216 -0
  19. package/kit/agents/detector-tenant-quente.md +338 -338
  20. package/kit/agents/evolution-go-integrator.md +201 -201
  21. package/kit/agents/example-reviewer.md +22 -22
  22. package/kit/agents/executor.md +565 -565
  23. package/kit/agents/golden-signals-instrumenter.md +232 -232
  24. package/kit/agents/incident-investigator.md +238 -238
  25. package/kit/agents/integration-checker.md +203 -203
  26. package/kit/agents/invite-flow-implementer.md +190 -190
  27. package/kit/agents/legacy-characterizer.md +369 -369
  28. package/kit/agents/lgpd-compliance-auditor.md +296 -296
  29. package/kit/agents/load-shedding-instrumenter.md +290 -290
  30. package/kit/agents/multi-tenant-isolation-auditor.md +254 -254
  31. package/kit/agents/multi-tenant-rls-writer.md +341 -341
  32. package/kit/agents/nyquist-auditor.md +181 -181
  33. package/kit/agents/observability-coverage-auditor.md +316 -316
  34. package/kit/agents/observability-instrumenter.md +191 -191
  35. package/kit/agents/omm-auditor.md +291 -291
  36. package/kit/agents/org-onboarding-implementer.md +224 -224
  37. package/kit/agents/payload-capture-instrumenter.md +274 -274
  38. package/kit/agents/phase-researcher.md +697 -697
  39. package/kit/agents/plan-checker.md +275 -275
  40. package/kit/agents/planner.md +923 -923
  41. package/kit/agents/postmortem-writer.md +273 -273
  42. package/kit/agents/project-researcher.md +653 -653
  43. package/kit/agents/prr-conductor.md +287 -287
  44. package/kit/agents/refactor-safety-auditor.md +405 -405
  45. package/kit/agents/release-pipeline-auditor.md +364 -364
  46. package/kit/agents/research-synthesizer.md +246 -246
  47. package/kit/agents/roadmapper.md +678 -678
  48. package/kit/agents/schema-checker.md +160 -160
  49. package/kit/agents/seam-finder.md +360 -360
  50. package/kit/agents/shotgun-surgery-detector.md +350 -350
  51. package/kit/agents/slo-engineer.md +217 -217
  52. package/kit/agents/storytelling-analyst.md +300 -300
  53. package/kit/agents/supabase-architect.md +249 -249
  54. package/kit/agents/supabase-auth-bootstrapper.md +400 -400
  55. package/kit/agents/supabase-auth-hook-writer.md +418 -418
  56. package/kit/agents/supabase-branching-architect.md +563 -563
  57. package/kit/agents/supabase-cicd-pipeline-implementer.md +778 -778
  58. package/kit/agents/supabase-column-privileges-writer.md +400 -400
  59. package/kit/agents/supabase-edge-fn-tester.md +288 -288
  60. package/kit/agents/supabase-edge-fn-writer.md +341 -341
  61. package/kit/agents/supabase-mfa-implementer.md +439 -439
  62. package/kit/agents/supabase-migration-writer.md +386 -386
  63. package/kit/agents/supabase-oauth-server-implementer.md +507 -507
  64. package/kit/agents/supabase-rbac-implementer.md +393 -393
  65. package/kit/agents/supabase-realtime-implementer.md +364 -364
  66. package/kit/agents/supabase-rls-hardener.md +522 -522
  67. package/kit/agents/supabase-rls-writer.md +324 -324
  68. package/kit/agents/supabase-roles-implementer.md +356 -356
  69. package/kit/agents/supabase-social-auth-implementer.md +451 -451
  70. package/kit/agents/supabase-sso-saml-architect.md +549 -549
  71. package/kit/agents/supabase-storage-implementer.md +407 -407
  72. package/kit/agents/super-admin-implementer.md +282 -282
  73. package/kit/agents/toil-auditor.md +268 -268
  74. package/kit/agents/ui-auditor.md +438 -438
  75. package/kit/agents/ui-checker.md +305 -305
  76. package/kit/agents/ui-researcher.md +356 -356
  77. package/kit/agents/user-profiler.md +176 -176
  78. package/kit/agents/validador-evolucao-schema.md +336 -336
  79. package/kit/agents/verifier.md +729 -729
  80. package/kit/commands/adicionar-backlog.md +75 -75
  81. package/kit/commands/adicionar-fase.md +42 -42
  82. package/kit/commands/adicionar-tarefa.md +45 -45
  83. package/kit/commands/adicionar-testes.md +41 -41
  84. package/kit/commands/ajuda.md +21 -21
  85. package/kit/commands/atualizar.md +37 -37
  86. package/kit/commands/auditar-cascading.md +111 -111
  87. package/kit/commands/auditar-marco.md +179 -179
  88. package/kit/commands/auditar-observabilidade-cobertura-workflow.md +121 -0
  89. package/kit/commands/auditar-observabilidade-cobertura.md +183 -183
  90. package/kit/commands/auditar-refactor.md +219 -219
  91. package/kit/commands/auditar-release.md +109 -109
  92. package/kit/commands/auditar-uat.md +23 -23
  93. package/kit/commands/autonomo.md +40 -40
  94. package/kit/commands/branch-pr.md +24 -24
  95. package/kit/commands/burn-rate-status.md +408 -408
  96. package/kit/commands/capturar-payloads.md +193 -193
  97. package/kit/commands/caracterizar.md +212 -212
  98. package/kit/commands/concluir-marco.md +247 -247
  99. package/kit/commands/configuracoes.md +36 -36
  100. package/kit/commands/dados-distribuidos.md +188 -188
  101. package/kit/commands/definir-perfil.md +10 -10
  102. package/kit/commands/depurar.md +190 -190
  103. package/kit/commands/detectar-duplicacao.md +197 -197
  104. package/kit/commands/discutir-fase.md +131 -131
  105. package/kit/commands/encontrar-seams.md +136 -136
  106. package/kit/commands/entrar-discord.md +17 -17
  107. package/kit/commands/estatisticas.md +18 -18
  108. package/kit/commands/example-greeting.md +33 -33
  109. package/kit/commands/executar-fase.md +58 -58
  110. package/kit/commands/expresso.md +56 -56
  111. package/kit/commands/fase-ui.md +34 -34
  112. package/kit/commands/fazer.md +57 -57
  113. package/kit/commands/fio.md +125 -125
  114. package/kit/commands/fluxos-trabalho.md +64 -64
  115. package/kit/commands/forense.md +176 -176
  116. package/kit/commands/gerenciador.md +38 -38
  117. package/kit/commands/inserir-fase.md +31 -31
  118. package/kit/commands/legacy.md +263 -263
  119. package/kit/commands/limpeza.md +17 -17
  120. package/kit/commands/listar-hipoteses-fase.md +45 -45
  121. package/kit/commands/listar-workspaces.md +18 -18
  122. package/kit/commands/load-shedding.md +117 -117
  123. package/kit/commands/mapear-codebase.md +70 -70
  124. package/kit/commands/multi-tenant.md +163 -163
  125. package/kit/commands/nota.md +33 -33
  126. package/kit/commands/novo-marco.md +43 -43
  127. package/kit/commands/novo-projeto.md +41 -41
  128. package/kit/commands/novo-workspace.md +43 -43
  129. package/kit/commands/pausar-trabalho.md +37 -37
  130. package/kit/commands/perfil-usuario.md +45 -45
  131. package/kit/commands/pesquisar-fase.md +195 -195
  132. package/kit/commands/planejar-fase.md +67 -67
  133. package/kit/commands/planejar-lacunas.md +33 -33
  134. package/kit/commands/plantar-ideia.md +25 -25
  135. package/kit/commands/progresso.md +24 -24
  136. package/kit/commands/proximo.md +30 -30
  137. package/kit/commands/publicar.md +490 -490
  138. package/kit/commands/rapido.md +35 -35
  139. package/kit/commands/reaplicar-patches.md +124 -124
  140. package/kit/commands/refactor-seguro.md +321 -321
  141. package/kit/commands/relatorio-sessao.md +19 -19
  142. package/kit/commands/remover-fase.md +31 -31
  143. package/kit/commands/remover-workspace.md +26 -26
  144. package/kit/commands/resumo-marco.md +50 -50
  145. package/kit/commands/retomar-trabalho.md +40 -40
  146. package/kit/commands/revisar-backlog.md +60 -60
  147. package/kit/commands/revisar-ui.md +32 -32
  148. package/kit/commands/revisar.md +37 -37
  149. package/kit/commands/saude.md +21 -21
  150. package/kit/commands/setup-notion.md +93 -93
  151. package/kit/commands/storytelling.md +179 -179
  152. package/kit/commands/supabase.md +238 -238
  153. package/kit/commands/sync-main.md +68 -68
  154. package/kit/commands/validar-fase.md +35 -35
  155. package/kit/commands/verificar-tarefas.md +44 -44
  156. package/kit/commands/verificar-trabalho.md +64 -64
  157. package/kit/file-manifest.json +13 -3
  158. package/kit/framework/bin/lib/commands.cjs +959 -959
  159. package/kit/framework/bin/lib/config.cjs +442 -442
  160. package/kit/framework/bin/lib/core.cjs +1230 -1230
  161. package/kit/framework/bin/lib/frontmatter.cjs +336 -336
  162. package/kit/framework/bin/lib/init.cjs +1442 -1442
  163. package/kit/framework/bin/lib/milestone.cjs +252 -252
  164. package/kit/framework/bin/lib/model-profiles.cjs +68 -68
  165. package/kit/framework/bin/lib/phase.cjs +888 -888
  166. package/kit/framework/bin/lib/profile-output.cjs +952 -952
  167. package/kit/framework/bin/lib/profile-pipeline.cjs +539 -539
  168. package/kit/framework/bin/lib/roadmap.cjs +329 -329
  169. package/kit/framework/bin/lib/security.cjs +382 -382
  170. package/kit/framework/bin/lib/state.cjs +1031 -1031
  171. package/kit/framework/bin/lib/template.cjs +222 -222
  172. package/kit/framework/bin/lib/uat.cjs +282 -282
  173. package/kit/framework/bin/lib/verify.cjs +888 -888
  174. package/kit/framework/bin/lib/workstream.cjs +491 -491
  175. package/kit/framework/bin/tools.cjs +918 -918
  176. package/kit/framework/commands/workstreams.md +63 -63
  177. package/kit/framework/references/checkpoints.md +778 -778
  178. package/kit/framework/references/continuation-format.md +249 -249
  179. package/kit/framework/references/decimal-phase-calculation.md +64 -64
  180. package/kit/framework/references/git-integration.md +295 -295
  181. package/kit/framework/references/git-planning-commit.md +38 -38
  182. package/kit/framework/references/model-profile-resolution.md +36 -36
  183. package/kit/framework/references/model-profiles.md +139 -139
  184. package/kit/framework/references/phase-argument-parsing.md +61 -61
  185. package/kit/framework/references/planning-config.md +202 -202
  186. package/kit/framework/references/questioning.md +162 -162
  187. package/kit/framework/references/tdd.md +263 -263
  188. package/kit/framework/references/ui-brand.md +160 -160
  189. package/kit/framework/references/user-profiling.md +657 -657
  190. package/kit/framework/references/verification-patterns.md +612 -612
  191. package/kit/framework/references/workstream-flag.md +58 -58
  192. package/kit/framework/templates/DEBUG.md +164 -164
  193. package/kit/framework/templates/UAT.md +265 -265
  194. package/kit/framework/templates/UI-SPEC.md +100 -100
  195. package/kit/framework/templates/VALIDATION.md +76 -76
  196. package/kit/framework/templates/claude-md.md +122 -122
  197. package/kit/framework/templates/codebase/architecture.md +185 -185
  198. package/kit/framework/templates/codebase/concerns.md +205 -205
  199. package/kit/framework/templates/codebase/conventions.md +204 -204
  200. package/kit/framework/templates/codebase/integrations.md +192 -192
  201. package/kit/framework/templates/codebase/stack.md +158 -158
  202. package/kit/framework/templates/codebase/structure.md +199 -199
  203. package/kit/framework/templates/codebase/testing.md +301 -301
  204. package/kit/framework/templates/config.json +44 -44
  205. package/kit/framework/templates/context.md +352 -352
  206. package/kit/framework/templates/continue-here.md +78 -78
  207. package/kit/framework/templates/copilot-instructions.md +7 -7
  208. package/kit/framework/templates/debug-subagent-prompt.md +91 -91
  209. package/kit/framework/templates/dev-preferences.md +20 -20
  210. package/kit/framework/templates/discovery.md +146 -146
  211. package/kit/framework/templates/discussion-log.md +63 -63
  212. package/kit/framework/templates/milestone-archive.md +123 -123
  213. package/kit/framework/templates/milestone.md +115 -115
  214. package/kit/framework/templates/phase-prompt.md +610 -610
  215. package/kit/framework/templates/planner-subagent-prompt.md +117 -117
  216. package/kit/framework/templates/project.md +186 -186
  217. package/kit/framework/templates/requirements.md +231 -231
  218. package/kit/framework/templates/research-project/ARCHITECTURE.md +204 -204
  219. package/kit/framework/templates/research-project/FEATURES.md +147 -147
  220. package/kit/framework/templates/research-project/PITFALLS.md +200 -200
  221. package/kit/framework/templates/research-project/STACK.md +120 -120
  222. package/kit/framework/templates/research-project/SUMMARY.md +170 -170
  223. package/kit/framework/templates/research.md +419 -419
  224. package/kit/framework/templates/retrospective.md +54 -54
  225. package/kit/framework/templates/roadmap.md +202 -202
  226. package/kit/framework/templates/state.md +176 -176
  227. package/kit/framework/templates/summary-complex.md +59 -59
  228. package/kit/framework/templates/summary-minimal.md +41 -41
  229. package/kit/framework/templates/summary-standard.md +48 -48
  230. package/kit/framework/templates/summary.md +209 -209
  231. package/kit/framework/templates/user-profile.md +146 -146
  232. package/kit/framework/templates/user-setup.md +256 -256
  233. package/kit/framework/templates/verification-report.md +258 -258
  234. package/kit/framework/workflows/add-phase.md +112 -112
  235. package/kit/framework/workflows/add-tests.md +351 -351
  236. package/kit/framework/workflows/add-todo.md +158 -158
  237. package/kit/framework/workflows/audit-milestone.md +340 -340
  238. package/kit/framework/workflows/audit-uat.md +109 -109
  239. package/kit/framework/workflows/autonomous.md +891 -891
  240. package/kit/framework/workflows/check-todos.md +177 -177
  241. package/kit/framework/workflows/cleanup.md +152 -152
  242. package/kit/framework/workflows/complete-milestone.md +696 -696
  243. package/kit/framework/workflows/diagnose-issues.md +231 -231
  244. package/kit/framework/workflows/discovery-phase.md +289 -289
  245. package/kit/framework/workflows/discuss-phase-assumptions.md +653 -653
  246. package/kit/framework/workflows/discuss-phase.md +784 -784
  247. package/kit/framework/workflows/do.md +104 -104
  248. package/kit/framework/workflows/execute-phase.md +838 -838
  249. package/kit/framework/workflows/execute-plan.md +510 -510
  250. package/kit/framework/workflows/fast.md +102 -102
  251. package/kit/framework/workflows/forensics.md +265 -265
  252. package/kit/framework/workflows/health.md +181 -181
  253. package/kit/framework/workflows/help.md +619 -619
  254. package/kit/framework/workflows/insert-phase.md +130 -130
  255. package/kit/framework/workflows/list-phase-assumptions.md +178 -178
  256. package/kit/framework/workflows/list-workspaces.md +56 -56
  257. package/kit/framework/workflows/manager.md +362 -362
  258. package/kit/framework/workflows/map-codebase.md +377 -377
  259. package/kit/framework/workflows/milestone-summary.md +223 -223
  260. package/kit/framework/workflows/new-milestone.md +486 -486
  261. package/kit/framework/workflows/new-project.md +1159 -1159
  262. package/kit/framework/workflows/new-workspace.md +237 -237
  263. package/kit/framework/workflows/next.md +97 -97
  264. package/kit/framework/workflows/node-repair.md +92 -92
  265. package/kit/framework/workflows/note.md +156 -156
  266. package/kit/framework/workflows/pause-work.md +176 -176
  267. package/kit/framework/workflows/plan-milestone-gaps.md +273 -273
  268. package/kit/framework/workflows/plan-phase.md +765 -765
  269. package/kit/framework/workflows/plant-seed.md +169 -169
  270. package/kit/framework/workflows/pr-branch.md +129 -129
  271. package/kit/framework/workflows/profile-user.md +450 -450
  272. package/kit/framework/workflows/progress.md +507 -507
  273. package/kit/framework/workflows/quick.md +757 -757
  274. package/kit/framework/workflows/remove-phase.md +155 -155
  275. package/kit/framework/workflows/remove-workspace.md +90 -90
  276. package/kit/framework/workflows/research-phase.md +82 -82
  277. package/kit/framework/workflows/resume-project.md +326 -326
  278. package/kit/framework/workflows/review.md +228 -228
  279. package/kit/framework/workflows/session-report.md +146 -146
  280. package/kit/framework/workflows/settings.md +283 -283
  281. package/kit/framework/workflows/ship.md +228 -228
  282. package/kit/framework/workflows/stats.md +60 -60
  283. package/kit/framework/workflows/transition.md +671 -671
  284. package/kit/framework/workflows/ui-phase.md +302 -302
  285. package/kit/framework/workflows/ui-review.md +165 -165
  286. package/kit/framework/workflows/update.md +323 -323
  287. package/kit/framework/workflows/validate-phase.md +174 -174
  288. package/kit/framework/workflows/verify-phase.md +252 -252
  289. package/kit/framework/workflows/verify-work.md +637 -637
  290. package/kit/hooks/check-update.js +118 -118
  291. package/kit/hooks/context-monitor.js +163 -163
  292. package/kit/hooks/kit-attribution-reminder.cjs +92 -92
  293. package/kit/hooks/kit-router.cjs +137 -137
  294. package/kit/hooks/prompt-guard.js +103 -103
  295. package/kit/hooks/statusline.js +125 -125
  296. package/kit/hooks/workflow-guard.js +101 -101
  297. package/kit/settings.json +45 -45
  298. package/kit/skills/ai-prompt-characterization/SKILL.md +335 -335
  299. package/kit/skills/armadilhas-sistemas-distribuidos/SKILL.md +447 -447
  300. package/kit/skills/audit-log-multi-tenant/SKILL.md +340 -340
  301. package/kit/skills/b2b-saas-architecture/SKILL.md +300 -300
  302. package/kit/skills/consistencia-leitura-replica/SKILL.md +385 -385
  303. package/kit/skills/crm-lead-pipeline-patterns/SKILL.md +343 -343
  304. package/kit/skills/escolha-modelo-consistencia/SKILL.md +494 -494
  305. package/kit/skills/evolucao-schema-compativel/SKILL.md +448 -448
  306. package/kit/skills/evolution-go-whatsapp-integration/SKILL.md +322 -322
  307. package/kit/skills/example-skill/SKILL.md +42 -42
  308. package/kit/skills/legacy-api-only-applications/SKILL.md +358 -358
  309. package/kit/skills/legacy-characterization-tests/SKILL.md +330 -330
  310. package/kit/skills/legacy-effect-analysis/SKILL.md +331 -331
  311. package/kit/skills/legacy-extract-class/SKILL.md +203 -203
  312. package/kit/skills/legacy-programming-by-difference/SKILL.md +252 -252
  313. package/kit/skills/legacy-seams-and-test-harness/SKILL.md +460 -460
  314. package/kit/skills/legacy-shotgun-surgery/SKILL.md +286 -286
  315. package/kit/skills/legacy-sprout-wrap-techniques/SKILL.md +434 -434
  316. package/kit/skills/legacy-storytelling-naked-crc/SKILL.md +270 -270
  317. package/kit/skills/lgpd-multi-tenant-compliance/SKILL.md +340 -340
  318. package/kit/skills/member-invite-flow/SKILL.md +305 -305
  319. package/kit/skills/member-management-react-shadcn/SKILL.md +328 -328
  320. package/kit/skills/multi-tenant-performance-scaling/SKILL.md +316 -316
  321. package/kit/skills/multi-tenant-rls-hierarchy/SKILL.md +342 -342
  322. package/kit/skills/org-onboarding-flow/SKILL.md +257 -257
  323. package/kit/skills/org-switcher-react-pattern/SKILL.md +349 -349
  324. package/kit/skills/permission-gate-react-pattern/SKILL.md +271 -271
  325. package/kit/skills/postgres-isolamento-concorrencia/SKILL.md +552 -552
  326. package/kit/skills/pre-refactor-characterization/SKILL.md +421 -421
  327. package/kit/skills/rbac-permissions-matrix-supabase/SKILL.md +338 -338
  328. package/kit/skills/streams-eventos-cdc/SKILL.md +711 -711
  329. package/kit/skills/supabase-auth-hardening/SKILL.md +674 -674
  330. package/kit/skills/supabase-auth-hooks/SKILL.md +875 -875
  331. package/kit/skills/supabase-auth-methods/SKILL.md +486 -486
  332. package/kit/skills/supabase-auth-sessions/SKILL.md +579 -579
  333. package/kit/skills/supabase-auth-ssr/SKILL.md +306 -306
  334. package/kit/skills/supabase-branching-workflow/SKILL.md +544 -544
  335. package/kit/skills/supabase-ci-cd-github-actions/SKILL.md +880 -880
  336. package/kit/skills/supabase-column-level-security/SKILL.md +426 -426
  337. package/kit/skills/supabase-config-toml-remotes/SKILL.md +807 -807
  338. package/kit/skills/supabase-custom-claims-rbac/SKILL.md +472 -472
  339. package/kit/skills/supabase-edge-functions/SKILL.md +330 -330
  340. package/kit/skills/supabase-edge-functions-auth/SKILL.md +309 -309
  341. package/kit/skills/supabase-edge-functions-limits/SKILL.md +302 -302
  342. package/kit/skills/supabase-edge-functions-mcp-server/SKILL.md +279 -279
  343. package/kit/skills/supabase-edge-functions-testing/SKILL.md +277 -277
  344. package/kit/skills/supabase-edge-runtime-builtins/SKILL.md +357 -357
  345. package/kit/skills/supabase-enterprise-sso-saml/SKILL.md +545 -545
  346. package/kit/skills/supabase-jwt-signing-keys/SKILL.md +399 -399
  347. package/kit/skills/supabase-mfa/SKILL.md +488 -488
  348. package/kit/skills/supabase-migration-repair/SKILL.md +823 -823
  349. package/kit/skills/supabase-migrations/SKILL.md +297 -297
  350. package/kit/skills/supabase-oauth-server/SKILL.md +537 -537
  351. package/kit/skills/supabase-pgtap-testing/SKILL.md +1053 -1053
  352. package/kit/skills/supabase-postgres-roles/SKILL.md +392 -392
  353. package/kit/skills/supabase-realtime/SKILL.md +460 -460
  354. package/kit/skills/supabase-rls-defense-in-depth/SKILL.md +418 -418
  355. package/kit/skills/supabase-rls-policies/SKILL.md +635 -635
  356. package/kit/skills/supabase-social-oauth/SKILL.md +480 -480
  357. package/kit/skills/supabase-third-party-auth/SKILL.md +450 -450
  358. package/kit/skills/super-admin-platform-pattern/SKILL.md +326 -326
  359. package/kit/skills/tenant-quente-mitigacao/SKILL.md +605 -605
  360. package/kit/skills/ui-anti-padroes-ia/SKILL.md +261 -0
  361. package/kit/skills/ui-contexto-produto/SKILL.md +248 -0
  362. package/kit/skills/ui-cor-estrategia/SKILL.md +213 -0
  363. package/kit/skills/ui-critica-auditoria/SKILL.md +260 -0
  364. package/kit/skills/ui-motion-funcional/SKILL.md +264 -0
  365. package/kit/skills/ui-ritmo-espacial/SKILL.md +259 -0
  366. package/kit/skills/ui-tipografia/SKILL.md +211 -0
  367. package/kit/skills/whatsapp-conversation-state-machine/SKILL.md +287 -287
  368. package/kit/workflows/auditar-observabilidade-cobertura.workflow.js +250 -0
  369. package/package.json +65 -63
  370. package/src/core/kit.js +333 -216
  371. package/src/core/reflect.js +247 -247
  372. package/src/core/registry.js +123 -112
  373. package/src/core/reverse-sync.js +448 -372
  374. package/src/core/sync.js +477 -437
  375. package/src/core/watch.js +121 -121
  376. package/src/mcp-server/index.js +794 -794
@@ -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