-
Notifications
You must be signed in to change notification settings - Fork 0
fix(security): Onda 20 — RE-GRANT t38 regression + bilateral SECURITY DEFINER gate #224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,163 @@ | ||
| # Padrão SECURITY DEFINER + RLS no PromoGifts | ||
|
|
||
| **Status:** vigente desde Onda 20 (15/mai/2026) | ||
| **Gate:** `scripts/check-security-definer-acl.mjs` + `audit_security_definer_acl()` RPC | ||
| **Aplica-se a:** todas as funções `SECURITY DEFINER` em `public` | ||
|
|
||
| --- | ||
|
|
||
| ## TL;DR — checklist por padrão | ||
|
|
||
| Toda função `SECURITY DEFINER` em `public` cai em uma de 3 categorias. Escolha a categoria correta antes de escrever a migration: | ||
|
|
||
| | Categoria | Quando usar | GRANT EXECUTE para | | ||
| |---|---|---| | ||
| | **A. RLS helper** | Função usada em `pg_policies` (em `qual` ou `with_check`) | `authenticated`, `service_role` | | ||
| | **B. RPC backend-only** | Função chamada só por edge function via `service_role` | `service_role` (apenas) | | ||
| | **C. Public-intent** | Endpoint público intencional (ex: validação de token de aprovação) | `anon`, `authenticated`, `service_role` + adicionar nome na whitelist de `audit_security_definer_acl()` | | ||
|
|
||
| Em **TODOS** os casos, **PUBLIC nunca tem EXECUTE** (revoke default do PostgreSQL). | ||
|
|
||
| --- | ||
|
|
||
| ## Por que isso importa | ||
|
|
||
| PostgreSQL define duas coisas independentes: | ||
|
|
||
| 1. **Quem executa a função** (`SECURITY DEFINER` = como owner; `SECURITY INVOKER` = como caller). | ||
| 2. **Quem pode chamar a função** (grants em `pg_proc.proacl`). | ||
|
|
||
| A confusão comum: pensar que `SECURITY DEFINER` dispensa o GRANT do caller. **Não dispensa.** O caller ainda precisa de `EXECUTE` pra invocar. Se uma RLS policy faz `WHERE is_admin_or_above(auth.uid())` e `authenticated` não tem EXECUTE em `is_admin_or_above`, qualquer SELECT/UPDATE/INSERT/DELETE nessa tabela quebra com: | ||
|
|
||
| ``` | ||
| ERROR: 42501: permission denied for function is_admin_or_above | ||
| ``` | ||
|
|
||
| Esse foi o bug do PR #192 (migration `t38_deploy_hardening_final`): aplicou REVOKE FROM authenticated em 2 funções RLS helper, quebrou 112 policies. Onda 20 corrigiu e adicionou gate bilateral. | ||
|
|
||
| --- | ||
|
|
||
| ## Padrão A — RLS helper (mais comum no PromoGifts) | ||
|
|
||
| **Exemplos no repo:** `is_admin_or_above`, `is_coord_or_above`, `is_supervisor_or_above`, `has_org_role`, `org_has_any_members`, `can_access_quote`. | ||
|
|
||
| **Template canônico:** | ||
|
|
||
| ```sql | ||
| CREATE OR REPLACE FUNCTION public.minha_funcao_rls(_user_id uuid) | ||
| RETURNS boolean | ||
| LANGUAGE sql | ||
| STABLE | ||
| SECURITY DEFINER | ||
| SET search_path = public, pg_catalog | ||
| AS $$ | ||
| SELECT EXISTS ( | ||
| SELECT 1 FROM public.user_roles | ||
| WHERE user_id = _user_id AND role IN ('supervisor', 'dev') | ||
| ); | ||
| $$; | ||
|
|
||
| -- Hardening obrigatório: | ||
| REVOKE EXECUTE ON FUNCTION public.minha_funcao_rls(uuid) FROM PUBLIC; | ||
| REVOKE EXECUTE ON FUNCTION public.minha_funcao_rls(uuid) FROM anon; | ||
| GRANT EXECUTE ON FUNCTION public.minha_funcao_rls(uuid) TO authenticated; | ||
| GRANT EXECUTE ON FUNCTION public.minha_funcao_rls(uuid) TO service_role; | ||
| ``` | ||
|
|
||
| > O comentário `-- rls-helper:` opcional satisfaz o regex do gate `check-security-definer-hardening.mjs` quando a função não tem REVOKE explícito de anon (caso raro). Prefira o REVOKE explícito. | ||
|
|
||
| --- | ||
|
|
||
| ## Padrão B — RPC backend-only | ||
|
|
||
| **Exemplos no repo:** funções chamadas só por edge functions via service_role (ex: `revoke_all_user_tokens`). | ||
|
|
||
| **Template canônico:** | ||
|
|
||
| ```sql | ||
| CREATE OR REPLACE FUNCTION public.minha_rpc_admin(_arg text) | ||
| RETURNS jsonb | ||
| LANGUAGE plpgsql | ||
| SECURITY DEFINER | ||
| SET search_path = public, pg_catalog | ||
| AS $$ | ||
| BEGIN | ||
| -- lógica privilegiada | ||
| RETURN jsonb_build_object('ok', true); | ||
| END; | ||
| $$; | ||
|
|
||
| REVOKE EXECUTE ON FUNCTION public.minha_rpc_admin(text) FROM PUBLIC; | ||
| REVOKE EXECUTE ON FUNCTION public.minha_rpc_admin(text) FROM anon; | ||
| REVOKE EXECUTE ON FUNCTION public.minha_rpc_admin(text) FROM authenticated; | ||
| -- service_role mantém grant default | ||
|
|
||
| ``` | ||
|
|
||
| Não é citada em nenhuma policy. Se for, vira Padrão A. | ||
|
|
||
| --- | ||
|
|
||
| ## Padrão C — Public-intent | ||
|
|
||
| **Exemplos no repo:** `submit_quote_response`, `get_quote_token_by_value` (validam token público). | ||
|
|
||
| **Template canônico:** | ||
|
|
||
| ```sql | ||
| CREATE OR REPLACE FUNCTION public.validate_public_token(_token text) | ||
| RETURNS jsonb | ||
| LANGUAGE plpgsql | ||
| SECURITY DEFINER | ||
| SET search_path = public, pg_catalog | ||
| AS $$ | ||
| BEGIN | ||
| -- valida token, rate-limit interno, etc | ||
| END; | ||
| $$; | ||
|
|
||
| REVOKE EXECUTE ON FUNCTION public.validate_public_token(text) FROM PUBLIC; | ||
| GRANT EXECUTE ON FUNCTION public.validate_public_token(text) TO anon; | ||
| GRANT EXECUTE ON FUNCTION public.validate_public_token(text) TO authenticated; | ||
| GRANT EXECUTE ON FUNCTION public.validate_public_token(text) TO service_role; | ||
| ``` | ||
|
|
||
| **E adicione o nome na whitelist** em `audit_security_definer_acl()`: | ||
|
|
||
| ```sql | ||
| (p.proname IN ('submit_quote_response', 'get_quote_token_by_value', 'validate_public_token')) AS public_intent | ||
| ``` | ||
|
|
||
| Sem esse passo, o gate ACL falha mesmo o grant sendo intencional. | ||
|
|
||
| --- | ||
|
|
||
| ## O gate `audit_security_definer_acl()` — 4 categorias detectadas | ||
|
|
||
| | Caso | Anti-padrão | Mensagem | | ||
| |---|---|---| | ||
| | 1 | PUBLIC com EXECUTE | `PUBLIC has EXECUTE` | | ||
| | 2 | anon com EXECUTE fora da whitelist | `anon has EXECUTE (not in public-intent whitelist)` | | ||
| | 3 | trigger function com EXECUTE pra authenticated (sem sentido — triggers são invocadas pelo PG) | `trigger function has EXECUTE for authenticated` | | ||
| | 4 | função citada em `pg_policies` sem EXECUTE pra authenticated → RLS quebra com 42501 | `used in RLS policy but missing EXECUTE for authenticated (RLS will fail with 42501)` | | ||
|
|
||
| O CI roda o RPC após cada deploy. Qualquer linha retornada → gate falha. | ||
|
|
||
| --- | ||
|
|
||
| ## Workflow recomendado pra criar nova função SECURITY DEFINER | ||
|
|
||
| 1. **Decidir categoria** (A/B/C) antes de escrever. | ||
| 2. **Copiar template** correspondente acima. | ||
| 3. **Substituir nome/args/lógica.** | ||
| 4. **Manter** `SET search_path = public, pg_catalog` (sem isso, search_path injection vira CVE). | ||
| 5. **Manter** REVOKE FROM PUBLIC e GRANTs explícitos do template. | ||
| 6. **Se for Padrão C**, atualizar whitelist da função `audit_security_definer_acl()` na mesma migration. | ||
| 7. **Rodar localmente** (se Supabase CLI disponível): `supabase migration up` → executa também o `RAISE EXCEPTION` da Onda 20 e aborta se houver violação. | ||
|
|
||
| --- | ||
|
|
||
| ## Histórico | ||
|
|
||
| - **Onda 20 (15/mai/2026, PR #TBD):** gate bilateral. Adiciona Caso 4. Corrige regression do t38 (PR #192) e gap do org_has_any_members. | ||
| - **t28 pilot (12/mai/2026, PRs #205-#212):** REVOKE de anon/PUBLIC nas funções admin existentes (257 ocorrências fechadas). | ||
| - **Hardening original (27/abr/2026):** criação inicial de `audit_security_definer_acl()` com Casos 1, 2, 3. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| -- ================================================================= | ||
| -- Onda 20: Fix t38 RLS regression + bilateral SECURITY DEFINER gate | ||
| -- | ||
| -- Contexto: | ||
| -- PR #192 (migration t38_deploy_hardening_final) aplicou | ||
| -- princípio do menor privilégio sem checar uso: revogou EXECUTE | ||
| -- de is_admin_or_above(uuid) e is_coord_or_above(uuid) do role | ||
| -- 'authenticated'. Mas essas funções são chamadas em 83+29 RLS | ||
| -- policies por usuários authenticated, então toda operação | ||
| -- logada quebrou com `42501: permission denied for function ...`. | ||
| -- | ||
| -- Adicionalmente, migration 20260514000000 criou | ||
| -- org_has_any_members(uuid) sem REVOKE FROM PUBLIC/anon — gate | ||
| -- audit_security_definer_acl() começou a falhar. | ||
| -- | ||
| -- Causa raiz dupla: | ||
| -- 1. Erro humano em t38: REVOKE indevido em funções RLS helper. | ||
| -- 2. Erro arquitetural: gate atual é unilateral — detecta | ||
| -- excesso de privilégio (PUBLIC, anon), mas não detecta | ||
| -- CARÊNCIA (função SECURITY DEFINER em policy sem EXECUTE | ||
| -- pra authenticated). Por isso t38 passou pré-merge. | ||
| -- | ||
| -- O que esta migration faz: | ||
| -- 1. RE-GRANT is_admin_or_above e is_coord_or_above pra authenticated | ||
| -- (desbloqueia 112 policies imediatamente) | ||
| -- 2. REVOKE org_has_any_members de PUBLIC e anon | ||
| -- (mantém authenticated + service_role; policy precisa) | ||
| -- 3. Reescreve audit_security_definer_acl() com 4 categorias: | ||
| -- a) PUBLIC has EXECUTE | ||
| -- b) anon EXECUTE fora da whitelist | ||
| -- c) trigger function com EXECUTE pra authenticated | ||
| -- d) NOVO: SECURITY DEFINER citada em pg_policy sem EXECUTE | ||
| -- pra authenticated (RLS quebra com 42501) | ||
| -- 4. RAISE EXCEPTION fail-fast se sobrar violação ao final | ||
| -- (rollback automático garante atomicidade) | ||
| -- | ||
| -- Padrão documentado em docs/SECURITY-DEFINER-PATTERN.md | ||
| -- ================================================================= | ||
|
|
||
| -- ───────────────────────────────────────────────────────────────── | ||
| -- 1. RE-GRANT funções quebradas pelo t38 (Bloco 1 da t38) | ||
| -- ───────────────────────────────────────────────────────────────── | ||
| GRANT EXECUTE ON FUNCTION public.is_admin_or_above(_user_id uuid) TO authenticated; | ||
| GRANT EXECUTE ON FUNCTION public.is_coord_or_above(_user_id uuid) TO authenticated; | ||
|
Comment on lines
+43
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In any preview or rebuilt environment that does not already have these legacy helper functions, these unconditional Useful? React with 👍 / 👎. |
||
|
|
||
| -- ───────────────────────────────────────────────────────────────── | ||
| -- 2. REVOKE excesso em org_has_any_members | ||
| -- (criada por 20260514000000 com default PUBLIC do PostgreSQL) | ||
| -- ───────────────────────────────────────────────────────────────── | ||
| REVOKE EXECUTE ON FUNCTION public.org_has_any_members(_org_id uuid) FROM PUBLIC; | ||
| REVOKE EXECUTE ON FUNCTION public.org_has_any_members(_org_id uuid) FROM anon; | ||
|
Comment on lines
+50
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In preview/partial schemas where Useful? React with 👍 / 👎. |
||
|
|
||
| -- ───────────────────────────────────────────────────────────────── | ||
| -- 3. Gate bilateral: reescreve audit_security_definer_acl() | ||
| -- para também reportar funções em policy sem EXECUTE pra | ||
| -- authenticated (Caso 4). | ||
| -- ───────────────────────────────────────────────────────────────── | ||
| CREATE OR REPLACE FUNCTION public.audit_security_definer_acl() | ||
| RETURNS TABLE( | ||
| function_name TEXT, | ||
| arguments TEXT, | ||
| problem TEXT, | ||
| granted_to TEXT | ||
| ) | ||
| LANGUAGE sql | ||
| STABLE | ||
| SECURITY INVOKER | ||
| SET search_path = public, pg_catalog | ||
| AS $$ | ||
| WITH defs AS ( | ||
| SELECT | ||
| p.oid, | ||
| p.proname, | ||
| pg_get_function_identity_arguments(p.oid) AS args, | ||
| p.proacl, | ||
| (pg_get_function_result(p.oid) = 'trigger') AS is_trigger, | ||
| -- whitelist de funções intencionalmente acessíveis a anon | ||
| (p.proname IN ('submit_quote_response', 'get_quote_token_by_value')) AS public_intent | ||
| FROM pg_proc p | ||
| JOIN pg_namespace n ON n.oid = p.pronamespace | ||
| WHERE n.nspname = 'public' AND p.prosecdef = true | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Because Useful? React with 👍 / 👎. |
||
| ), | ||
| acl_expanded AS ( | ||
| SELECT | ||
| d.oid, | ||
| d.proname, | ||
| d.args, | ||
| d.is_trigger, | ||
| d.public_intent, | ||
| a.grantee::regrole::text AS grantee | ||
| FROM defs d | ||
| LEFT JOIN LATERAL ( | ||
| SELECT (aclexplode(d.proacl)).grantee | ||
| ) a ON true | ||
| WHERE a.grantee IS NOT NULL | ||
| ), | ||
|
Comment on lines
+91
to
+96
Comment on lines
+83
to
+96
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: git ls-files "supabase/migrations/*.sql" | head -20Repository: adm01-debug/Promo_Gifts Length of output: 1161 🏁 Script executed: cat -n supabase/migrations/20260515150000_onda20_fix_t38_regression_and_bilateral_gate.sql | head -150Repository: adm01-debug/Promo_Gifts Length of output: 7627 🏁 Script executed: # Verificar tamanho do arquivo para leitura completa
wc -l supabase/migrations/20260515150000_onda20_fix_t38_regression_and_bilateral_gate.sqlRepository: adm01-debug/Promo_Gifts Length of output: 155 🏁 Script executed: cat -n supabase/migrations/20260515150000_onda20_fix_t38_regression_and_bilateral_gate.sql | tail -30Repository: adm01-debug/Promo_Gifts Length of output: 1743 🏁 Script executed: # Verificar se existem funções SECURITY DEFINER no public com proacl NULL
# usando readymade queries PostgreSQL (syntax check)
cat > /tmp/test_acl_logic.sql << 'EOF'
-- Teste 1: aclexplode() com NULL
SELECT COUNT(*) FROM aclexplode(NULL::aclitem[]);
-- Teste 2: acldefault() para função
SELECT acldefault('f'::char, 1::oid);
-- Teste 3: Comparar resultados
WITH test_null AS (
SELECT (aclexplode(NULL::aclitem[])).grantee AS grantee_null
),
test_default AS (
SELECT (aclexplode(acldefault('f'::char, 1::oid))).grantee AS grantee_default
)
SELECT
(SELECT COUNT(*) FROM test_null) AS null_count,
(SELECT COUNT(*) FROM test_default) AS default_count;
EOF
cat /tmp/test_acl_logic.sqlRepository: adm01-debug/Promo_Gifts Length of output: 560 🏁 Script executed: # Verificar a doc PostgreSQL: aclexplode() behavior
# Além disso, verificar se há referência à proacl no código ou comentários
rg "proacl|acldefault|aclexplode" --type sql supabase/migrations/ -nRepository: adm01-debug/Promo_Gifts Length of output: 521 🌐 Web query:
💡 Result: In PostgreSQL, the aclexplode function is a system function used to decompose an aclitem array (such as those found in system catalog columns like relacl) into a set of rows, where each row represents a specific privilege grant [1][2]. Regarding NULL behavior and default permissions: 1. NULL in ACL Columns: When an ACL column (like relacl in pg_class) is NULL, it signifies that the object is using the system's hard-wired default privileges [3][4]. It does not mean that no privileges exist; rather, it indicates that the default access rules apply [3]. 2. aclexplode and NULLs: The aclexplode function does not automatically resolve NULL values into the hard-wired default privileges [5]. If you pass a NULL value to aclexplode, it will return no rows, as there are no explicit grants to explode [1][5]. 3. Default Privileges: To determine the effective privileges for an object, you must account for both explicit grants (stored in the ACL column) and the implicit hard-wired defaults [3][6]. Because aclexplode only processes the explicit ACL array, it is not sufficient on its own to reconstruct the full set of effective permissions if the ACL column is NULL [5]. 4. Handling NULLs: If you need to programmatically determine effective permissions, you must implement logic to check if the ACL column is NULL and, if so, apply the corresponding hard-wired default privileges for that object type [5][3]. The system catalog pg_default_acl is used for user-defined default privileges (set via ALTER DEFAULT PRIVILEGES), but it is only consulted during object creation and does not replace the hard-wired defaults for existing objects [3][7][6]. In summary, aclexplode only explodes explicit grants [1][8]. A NULL value in an ACL column represents the presence of hard-wired default privileges, which aclexplode does not expand [5][3]. Citations:
🏁 Script executed: # Verificar se proowner está disponível e check lógica proposta
rg "proowner" supabase/migrations/ -A 2 -B 2Repository: adm01-debug/Promo_Gifts Length of output: 962 🏁 Script executed: # Procurar por CREATE FUNCTION sem GRANT explícito nas migrations
rg "CREATE.*FUNCTION.*public\." supabase/migrations/ | head -20Repository: adm01-debug/Promo_Gifts Length of output: 2996 🏁 Script executed: # Verificar funções SECURITY DEFINER públicas neste repo
rg "CREATE.*FUNCTION.*public\." supabase/migrations/ -A 5 | grep -E "CREATE|SECURITY DEFINER" | head -40Repository: adm01-debug/Promo_Gifts Length of output: 4747 🏁 Script executed: # Buscar especificamente por funções SECURITY DEFINER que não têm GRANT explícito logo após
rg "SECURITY DEFINER" supabase/migrations/ -B 2 -A 8 | head -80Repository: adm01-debug/Promo_Gifts Length of output: 7464 Gate de auditoria ACL deixa falso negativo: O gate Risco crítico: Funções com PUBLIC EXECUTE efetivo podem passar pelo gate sem alerta, replicando o problema que a migration tenta corrigir. Patch objetivo WITH defs AS (
SELECT
p.oid,
p.proname,
pg_get_function_identity_arguments(p.oid) AS args,
+ p.proowner,
p.proacl,
(pg_get_function_result(p.oid) = 'trigger') AS is_trigger,
(p.proname IN ('submit_quote_response', 'get_quote_token_by_value')) AS public_intent
FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname = 'public' AND p.prosecdef = true
),
acl_expanded AS (
SELECT
d.oid,
d.proname,
d.args,
d.is_trigger,
d.public_intent,
a.grantee::regrole::text AS grantee
FROM defs d
LEFT JOIN LATERAL (
- SELECT (aclexplode(d.proacl)).grantee
+ SELECT (aclexplode(COALESCE(d.proacl, acldefault('f', d.proowner)))).grantee
) a ON true
WHERE a.grantee IS NOT NULL
),Também afeta Caso 4 (linhas 128–137): funções em policies sem EXECUTE pra authenticated não serão detectadas se usarem ACL default. 🤖 Prompt for AI Agents |
||
| -- funções SECURITY DEFINER citadas em pg_policy (qual ou with_check) | ||
| -- regex \m...\M = word boundary, evita falso positivo (is_admin vs is_admin_or_above) | ||
| policy_uses AS ( | ||
| SELECT DISTINCT d.oid, d.proname, d.args | ||
| FROM defs d | ||
| JOIN pg_policies pp | ||
| ON pp.schemaname = 'public' | ||
| AND ( | ||
| COALESCE(pp.qual, '') ~ ('\m' || d.proname || '\M') | ||
| OR COALESCE(pp.with_check, '') ~ ('\m' || d.proname || '\M') | ||
| ) | ||
| ) | ||
| -- Caso 1: PUBLIC EXECUTE (sempre proibido) | ||
| SELECT proname, args, 'PUBLIC has EXECUTE'::text, 'PUBLIC'::text | ||
| FROM acl_expanded | ||
| WHERE grantee = '-' | ||
| UNION ALL | ||
| -- Caso 2: anon EXECUTE fora da whitelist | ||
| SELECT proname, args, 'anon has EXECUTE (not in public-intent whitelist)'::text, 'anon' | ||
| FROM acl_expanded | ||
| WHERE grantee = 'anon' AND NOT public_intent | ||
| UNION ALL | ||
| -- Caso 3: trigger function com EXECUTE pra authenticated (sem sentido) | ||
| SELECT proname, args, 'trigger function has EXECUTE for authenticated'::text, 'authenticated' | ||
| FROM acl_expanded | ||
| WHERE grantee = 'authenticated' AND is_trigger | ||
| UNION ALL | ||
| -- Caso 4 (NOVO — Onda 20): usada em policy mas authenticated sem EXECUTE | ||
| -- Caller authenticated falha com `42501: permission denied for function` | ||
| -- ao avaliar a RLS policy. Detecta o anti-padrão que quebrou prod | ||
| -- via PR #192 (t38). | ||
| SELECT | ||
| pu.proname, | ||
| pu.args, | ||
| 'used in RLS policy but missing EXECUTE for authenticated (RLS will fail with 42501)'::text, | ||
| 'authenticated (MISSING)'::text | ||
| FROM policy_uses pu | ||
| WHERE NOT EXISTS ( | ||
| SELECT 1 FROM acl_expanded a | ||
| WHERE a.oid = pu.oid AND a.grantee = 'authenticated' | ||
| ) | ||
| ORDER BY 1, 2; | ||
| $$; | ||
|
|
||
| COMMENT ON FUNCTION public.audit_security_definer_acl() IS | ||
| 'Audit gate bilateral: detecta 4 anti-padrões em funções SECURITY DEFINER de public. (1) PUBLIC EXECUTE; (2) anon EXECUTE fora da whitelist (submit_quote_response, get_quote_token_by_value); (3) trigger function com EXECUTE pra authenticated; (4) função citada em pg_policy SEM EXECUTE pra authenticated (RLS quebra em runtime com 42501 — Caso adicionado em Onda 20 após PR #192/t38 regression). Lints Supabase 0028/0029. Usado por scripts/check-security-definer-acl.mjs. Ver docs/SECURITY-DEFINER-PATTERN.md.'; | ||
|
|
||
| GRANT EXECUTE ON FUNCTION public.audit_security_definer_acl() TO authenticated, service_role; | ||
|
|
||
| -- ───────────────────────────────────────────────────────────────── | ||
| -- 4. Fail-fast: rollback se sobrar violação após cirurgia | ||
| -- ───────────────────────────────────────────────────────────────── | ||
| DO $$ | ||
| DECLARE | ||
| v_count int; | ||
| v_violations text; | ||
| BEGIN | ||
| SELECT count(*) INTO v_count FROM public.audit_security_definer_acl(); | ||
|
|
||
| IF v_count > 0 THEN | ||
| SELECT string_agg( | ||
| format(' - %s(%s) | %s [%s]', function_name, arguments, problem, granted_to), | ||
| E'\n' | ||
| ) INTO v_violations | ||
| FROM public.audit_security_definer_acl(); | ||
|
|
||
| RAISE EXCEPTION E'Onda 20: % violação(ões) SECURITY DEFINER restantes após cirurgia:\n%\nVer docs/SECURITY-DEFINER-PATTERN.md', | ||
| v_count, v_violations; | ||
| END IF; | ||
|
|
||
| RAISE NOTICE 'Onda 20: SECURITY DEFINER ACL gate limpo (0 violações).'; | ||
| END $$; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Defina linguagem no bloco cercado para eliminar warning de lint.
O bloco de erro está sem linguagem e dispara MD040.
💡 Ajuste sugerido
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
@docs/SECURITY-DEFINER-PATTERN.mdaround lines 32 - 34, O bloco de erro nodocumento está sem linguagem e aciona o lint MD040; edite o fence que contém
"ERROR: 42501: permission denied for function is_admin_or_above" para declarar a
linguagem, por exemplo trocando
paratext de forma que o bloco comece comtext e termine com, garantindo que o código seja tratado como texto e owarning seja eliminado.