Recovery: Batches D.1 + D.2 + Fase 2 (Audit & Patch Cirúrgico)#143
Conversation
- Script SQL read-only com 4 queries que extraem: 1. Inventário + colunas + comentários (64 tabelas alvo) 2. Constraints (PK/UNIQUE/FK/CHECK) + indexes 3. Policies RLS completas 4. Triggers + bodies de functions + enums customizados - companies removida do escopo (vive em outro banco - bancodadosclientes) - 100% read-only, validado sintaticamente no banco atual - README com instruções passo-a-passo para o sponsor rodar no dashboard Resultado: 4 JSONs que servem de base para construir a migration final de Recovery das tabelas faltantes do Promo Gifts.
- Estado atual: aguardando dump do schema do Lovable - Documenta todas as descobertas da Fase 1 (drift gigante repo vs banco atual) - Lista próximos passos quando dump chegar - Mantém princípio de zero modificação até aprovação
ACHADOS: - 0% overlap entre repo (331 versions) e banco (204 aplicadas) → Repo é "artefato órfão", banco rodou via Dashboard - Tabelas faltantes reais: 85 (não 64 como Fase 1 estimou) → 30 a mais são backend puro (auditoria, step-up, magic up social) - 19 functions extraídas do block04 Lovable, prontas pra aplicação - Storage backup do _other.sql incorporado Nenhuma modificação em produção. Tudo read-only. Aguardando: blocos 13-16 do Lovable + edge functions restantes (67)
Criado em recovery/agent-db/:
- CLAUDE.md → system prompt do agente, regras de ouro, escopo
- PLANO_20_FASES.md → roadmap completo de Recovery (21 fases: 0 + 1-20)
- HANDOFF.md → estado do projeto, artefatos disponíveis, próximos passos
- PROTOCOLO.md → como agente DB e gerente se comunicam
- progress.md → checklist visual das 20 fases
- EXECUTION_LOG.md → registro de execuções (vazio inicialmente)
- ISSUES.md → registro de problemas (vazio)
- DECISIONS.md → registro de decisões (vazio)
Estrutura: Sponsor → Gerente (Web) → Agente DB (Claude Code VPS)
Modelo: read-only por default, write só com aprovação,
tudo no branch recovery/lovable-introspection.
Aguardando: aprovação do Sponsor pra acionar o Agente DB.
…vable Blocos pequenos do dump pg_dump --schema-only do Lovable: - block02_types.sql → 6 enums (app_role, conversation_event_type, etc) - block06_views.sql → 1 view (v_full_scope_grants) - block07_sequences.sql → vazio (IDENTITY bound) - block08_extensions.sql → 7 extensions (pgcrypto, uuid-ossp, pg_trgm, etc) - block09_storage.sql → 6 buckets + 34 storage policies - block10_realtime.sql → publication supabase_realtime (vazia no Lovable) - block11_cron_jobs.sql → 0 jobs no Lovable, só templates Necessários para Fases 4, 5, 12 do Recovery. Agente DB precisa desses arquivos pra executar o plano.
…lidadas) Functions necessárias pelos triggers das 85 tabelas faltantes. Origem: extraídas do block04_functions.sql do dump Lovable. Idempotente (CREATE OR REPLACE). Pronto pra Fase 6 do plano de Recovery.
Histórico de 366 migrations do repo Lovable + scripts de validação. Lista completa truncada por tamanho - será capturada via Fase 1 (15_migrations.json).
…, secrets, edge auth audit) 5 blocos NOVOS recebidos do Lovable - complementam o dump original: - block16_auth_hooks.md → Análise dos 6 hooks de auth customizados (verify-email, step-up-verify, etc) ACHADO: nenhum Supabase Auth Hook nativo - tudo via edge functions - block17_database_webhooks.md → Sistema próprio de webhooks (outbound_webhooks + webhook-dispatcher) ACHADO: 4 triggers HTTP, 0 webhooks nativos do Dashboard - block18_realtime_diagnostics.sql → Queries de diagnóstico do Realtime (publication, REPLICA IDENTITY, RLS) - block19_secrets_inventory.md → Inventário de 11 secrets + 34 env vars únicas CRÍTICO: 14 secrets exigem provisionamento manual antes do deploy - block20_edge_functions_auth_audit.md → Auditoria de verify_jwt das 85 edge functions CRÍTICO: 22 funções sem auth detectável - top 5 prioritárias: secure-upload, sync-quote-bitrix, dropbox-list, bi-copilot, external-db-inspect NOTA: total real é 85 edge functions (não 77 como tinha no contexto anterior)
…6-20 5 sub-fases adicionadas: - 6.5⚠️ Refatorar dispatch_quote_webhook_event() (remove URL hardcoded) - 11.5⚠️ Auditar 22 edge functions sem auth (top 5: secure-upload, sync-quote-bitrix, dropbox-list, bi-copilot, external-db-inspect) - 15.5⚠️ Checklist de 14 secrets que exigem provisionamento manual - 18.5⚠️ Provisionar 14 secrets no banco novo antes do merge prod - 20.5⚠️ Setup CI gate check-edge-auth.mjs Correções: - Total de edge functions: 77 → 85 (corrigido pelo block20) - vault.secrets: agora confirmado vazio - Auth Hooks nativos: confirmado 0 - Database Webhooks nativos: confirmado 0 Estimativa: 9.5h trabalho efetivo total Atualiza PLANO_20_FASES.md (239 linhas) + progress.md
…ntory granular)
block21_edge_shared_imports_audit.md:
- Auditoria da pasta supabase/functions/_shared/ (26 arquivos)
- TODAS as 85 functions importam pelo menos cors.ts
- 7 functions com auth.ts realmente faltando (refinamento do block20)
- Lista crítica REDUZIDA de 22 → 7 functions sem auth real:
dropbox-list, external-db-inspect, bi-copilot, kit-ai-builder,
comparison-ai-advisor, market-intelligence-insights, generate-mockup-nanobanana
block22_edge_secrets_inventory.md:
- 33 secrets distintos, 81 functions com secrets
- Granular por edge function (vs block19 que era genérico)
- Checklist DEFINITIVO de 28 secrets (21 críticos + 7 opcionais E2E)
- Risco máximo identificado: LOVABLE_API_KEY (17 functions dependem)
- Risco médio: EXTERNAL_SUPABASE_* (7 functions)
IMPACTO NO PLANO:
- Fase 15.5 → usar block22 §2 como checklist SSOT
- Fase 18.5 → 21 secrets críticos pra provisionar (não 14 como antes)
- Fase 15 → deve incluir pasta _shared/ completa (26 arquivos)
- Fase 11.5 → lista de prioridade refinada para 7 functions críticas
…shell (16 arquivos) NOVOS: - block09b_storage_policies_full.sql (34 policies, 7 grupos, idempotente) - block09b_storage_policies_full.md (matriz quem-pode-fazer-o-quê) - migrations/20260511123935_*.sql (purge_expired_step_up_artifacts + cron */15min) - _other.sql (vazio - só CREATE SCHEMA) - examples/RealtimeMessagesExample.tsx - docs/INDEX.md (índice mestre do Lovable) - docs/block10b_replica_identity_benchmark.md - docs/block10c_replica_identity_full_column_leak.md (warning de vazamento) - docs/block14_auth_config.md (config Auth Dashboard + tools) - block11_cron_status_report.sql (6 queries auditoria pg_cron) - scripts/run_blocks.sh (runner fail-fast) - scripts/run_all.sh (runner avançado com --resume, --from, etc) - scripts/diff_validation.sh (comparador OK/MISSING/WARN/FAIL) - scripts/gen_summary.sh (sumário markdown) SUBSTITUI: - block11_cron_jobs.sql (versão antiga simples → expandida com vault setup + helper cron_invoke_edge) - block16_auth_hooks.md (versão refletindo dump atual, sem auth-email-hook)
… dump CONTEÚDO ATUALIZADO COM ACHADOS DESTA SESSÃO: NÚMEROS REAIS DO DUMP (corrigidos): - Tabelas: 85→136 (+60%) - Indexes: ~150→234 (+56%) - Foreign Keys: ~80→64 (-20%) - Tabelas com RLS: ~80→129 (+61%) - RLS Policies: ~150→317 (+111%) - Functions: 19 críticas→163 total (+144) - Edge Functions: 85→87 (+2) ACHADOS-BOMBA: - Project ref REAL do Lovable: jlpkghroyzkmseixtjxv (confirmado em config.toml) - block14 vs block16: conflito sobre auth-email-hook (decisão Fase 19) - block10c: WARNING REPLICA IDENTITY FULL + RLS = vazamento de colunas - block11_cron expandido: vault setup + helper cron_invoke_edge - 21 secrets críticos pra provisionar (não 14) - 7 functions sem auth real (auditoria Fase 11.5) NOVAS SUB-FASES: - Fase 6.5: refatorar dispatch_quote_webhook_event (URL hardcoded) - Fase 10.5: migration purge_step_up_artifacts - Fase 11.5: auditoria edge functions sem auth - Fase 15.5: provisionar 21 secrets - Fase 18.5: BLOQUEADOR pré-merge prod (LOVABLE_API_KEY) - Fase 20.5: CI gate check-edge-auth.mjs ORDEM CANÔNICA OFICIAL (do README do Lovable): 08→02→01→04→05→07→06→03→09→09b→10→11 PENDÊNCIA ABERTA: restore de DADOS (user_roles, auth.users, mcp_keys)
progress.md: - Reflete 25 fases (era 20) - Mostra 14/22 JSONs criados na Fase 1 (60%) - Tabela com status fase a fase + indicadores - Achados consolidados desta sessão HANDOFF.md (NOVO): - Contexto rápido pra próxima sessão (30s) - Comando completo de re-disparo do Agente DB no VPS - 8 queries SQL prontas pra cada JSON faltante - Regras de comportamento do Agente DB - Plano de contingência (incêndio)
ACHADOS PRINCIPAIS: - Destino é PROD ATIVO com evolução massiva pós-Lovable - 195 tabelas (vs 136 dump) - 154 evoluções, 95 ausências - 545 functions (vs 163 dump) - 521 evoluções, 137 ausências CRÍTICAS - 458 policies (vs 317 dump) - 850 indexes (vs 234 dump) GAPS CRÍTICOS IDENTIFICADOS: - Storage policies: 0 (vs 34 do dump)⚠️ - MCP Keys system: 4 tabelas + 13 functions PERDIDAS - Step-up Auth (MFA): 3 tabelas + 5 functions PERDIDAS - Outbound webhooks: 4 tabelas + 4 functions PERDIDAS - Security/Geofence: 5 tabelas + 11 functions PERDIDAS - Quote/Order workflow: 13 functions PERDIDAS EVOLUÇÕES POS-LOVABLE: - ERP completo brindes (suppliers, products c/ 148 cols, tabelas preço gravação) - Mockup AI pipeline - Categoria + materiais com atributos - Stock/inventory snapshots PRÓXIMO PASSO: sponsor decide quais subsistemas resgatar
…ery from scratch PIVOT ESTRATÉGICO COMPLETO: ARQUIVOS NOVOS: - PLANO_AUDIT_PATCH_V4.md (plano v4.0 oficial) ARQUIVOS ATUALIZADOS: - PLANO_20_FASES.md → marcado como OBSOLETO com warning bem visível - progress.md → reflete Fase A concluída + Fase B próxima - HANDOFF.md → instruções pra Fase B.1 (grep RPCs+tables no código) NOVA ESTRATÉGIA (5 fases): - Fase A: Auditoria de Gaps ✅ (concluída) - Fase B: Investigação de Uso (grep no código React) - Fase C: Decisão por Subsistema (sponsor classifica) - Fase D: Patches Cirúrgicos (PRs pequenos, 1 por subsistema) - Fase E: Documentação e Fechamento PRINCÍPIOS NÃO-NEGOCIÁVEIS: - Read-only por default - Backup antes de cada patch - 1 patch = 1 transaction - Rollback documentado - Smoke test obrigatório - NUNCA aplicar dump por cima do prod CRITÉRIO DE SAÍDA da Fase A: ✅ RELATORIO_GAPS.md gerado PRÓXIMO PASSO: Fase B.1 - grep RPCs no código do app
ACHADO BOMBA: - App está chamando 30 RPCs e 57 tables que NÃO EXISTEM no banco - Provavelmente falhando silenciosamente em produção ARTEFATOS GERADOS em recovery/audit/: - rpcs_used_in_app.txt (36 RPCs únicas chamadas no código) - tables_used_in_app.txt (116 tables únicas usadas no código) - _missing_functions.txt (137 functions faltantes do dump) - _missing_tables.txt (95 tables faltantes do dump) - CRITICAL_missing_rpcs.txt (30 = USADAS pelo app E FALTAM no banco)⚠️ - CRITICAL_missing_tables.txt (57 = USADAS pelo app E FALTAM no banco)⚠️ - CRITICAL_rpcs_locations.txt (linha+arquivo+snippet de cada RPC crítica) METODOLOGIA: - grep recursivo em src/ (1636 arquivos .ts/.tsx) - cruzamento via comm -12 das listas sort -u - localização extraída via grep com filename/lineno PRÓXIMO PASSO: Fase B.2 - investigar se existem versões substitutas/renomeadas no destino (renames pós-Lovable) antes de classificar como gap real.
VALIDAÇÃO SQL DIRETA NO BANCO DESTINO: - 30/30 RPCs confirmadas como NÃO EXISTENTES (zero renames) - 57 tables analisadas → 54 GAPS REAIS + 3 falsas alarmes FALSAS ALARMES DESCOBERTAS: - product_groups → VIEW alias de product_similarity_groups ✅ - product_group_members → VIEW alias de product_similarity_group_members ✅ - product_components → possível rename para product_kit_components 🟡 METODOLOGIA: - pg_proc + pg_class para existência exata - Busca fuzzy LIKE wildcards + prefixos (fn_, _v2, etc) - pg_get_viewdef para validar aliases DESCOBERTAS CHAVE: - Time TI usou VIEWS como aliases de compat após refactor - pg_stat_statements NÃO ajuda (queries falhadas geram 404 PostgREST ANTES de chegar no parser do Postgres) - Só GlitchTip (frontend) ou postgres_logs (Supabase logs UI) confirma contagem real de chamadas falhando PRÓXIMA AÇÃO: - Aguardando token GlitchTip do sponsor (Frente A) - Pode seguir direto pra Fase C (decisão por subsistema) Triagem por prioridade: 🔴 P1 (impacto user): Dashboard widgets, Optimization Queue, Collection Items, Kit Collab (6 RPCs + 7 tables) 🟡 P2 (operacional): Security logs, Webhooks, MCP Keys, Connections config (9 RPCs + 9 tables) 🟢 P3 (descartar?): Magic Up, Expert chat, Voice commands, Role migration (1 RPC + 8 tables)
CITAÇÃO LITERAL: "QUERO CRIAR TUDO QUE FOR NECESSARIO PARA O SISTEMA, MANTER TODAS AS FUNCIONALIDADES QUE TINHA NO LOVABLE, APÓS O DEPLOY" ESCOPO FINAL EXPANDIDO: - Nenhuma feature é descartável (P3 inclusive) - Resgatar todas 30 RPCs + 54 tables USADAS pelo app - Resgatar também 107 RPCs + 41 tables NÃO-críticas - Coexistir collection_items com collection_products - Coexistir kit_* (4 tables) com custom_kits ARQUIVOS: - DECISIONS.md: Decisão 003 + 004 documentadas - PLANO_AUDIT_PATCH_V4.md: bumped pra v4.1, Fase D expandida pra D.1 a D.5 cobrindo P1+P2+P3+não-críticas+validação BLOQUEIO ATIVO: Faltam 4 arquivos pesados do dump no VPS (não persistidos entre sessões anteriores): block01_tables, block03_policies, block04_functions, block12_edge_functions (~988 KB total) PRÓXIMO PASSO: sponsor reupload pra iniciar Fase D.0
CONTEXTO:
Enquanto sponsor desbloqueia upload dos 4 arquivos pesados (block01/03/04/12),
preparei o D.1.1 COMPLETO pois usa block09 + block09b que JÁ TEMOS no repo.
ESTRUTURA recovery/patches/D1.1_storage_policies/:
- README.md → plano + contexto + riscos
- backup.sql → snapshot pré-patch (buckets + policies)
- patch.sql → transação atômica: 6 buckets + 34 policies
(com pré-check de is_supervisor_or_above)
- validate.sql → confirma 6 buckets + 34 policies criados
- rollback.sql → DROP policies + DELETE buckets
- smoke_test.md → checklist manual de 8 passos
DESCOBERTAS DA PREPARAÇÃO:
- 🚨 Destino tem APENAS 1 bucket (scripts), os 6 do dump não existem
- ✅ 5/6 funcs de role já existem (is_supervisor_or_above, is_admin, etc)
- 🟡 Risco médio: bucket quarantine usado pelo clamav (verificar infra)
STATUS: ⏳ AGUARDANDO REVIEW + APROVAÇÃO DO SPONSOR
Outras pastas criadas (vazias por ora):
- D1.2_optimization_queue/
- D1.3_collection_items/
- D1.4_kit_collaboration/
- D1.5_dashboard_widgets/
PRÓXIMO PASSO: sponsor revisa D.1.1 + manda os 4 arquivos pesados
UPLOADS RECEBIDOS via GitHub Web Upload (commit 8daf889): ✅ block01_tables_indexes_rls.sql (200 KB, 136 tables + 234 indexes) ✅ block03_policies.sql (91 KB, 317 RLS policies) ✅ block04_functions.sql (203 KB, 157 functions) ✅ block12_edge_functions_batch1-5.md (494 KB total) ✅ BONUS: full-public-schema.sql (526 KB, backup duplo) LIMPEZA: - Removidos 17 arquivos duplicados com sufixo "(1)", "(2)", "(3)" - Mantidos apenas os canônicos sem sufixo VALIDAÇÃO DAS 30 RPCs CRÍTICAS: - 30/30 PRESENTES no block04_functions.sql ✅ - Confirmação dupla no full-public-schema.sql VALIDAÇÃO D.1.2 (Optimization Queue): - 1 table (optimization_queue) no block01 - 2 indexes - 2 policies no block03 - 6 functions no block04 (5 críticas + 1 trigger) BLOQUEIO DA FASE D.0 → RESOLVIDO PRÓXIMO PASSO: aplicar D.1.1 (storage policies, já pronto) + preparar D.1.2 a D.1.5 em sequência
CONTEXTO: Bloqueio dos 4 arquivos pesados foi RESOLVIDO via GitHub Web Upload (commit 8daf889). Com block01/03/04 disponíveis, extraí TODOS os 4 patches restantes do P1. CONTEÚDO: - patches/BATCH_D1_README.md ← visão consolidada do batch + ordem de aplicação - patches/D1.2_optimization_queue/ ← 7 arquivos (patch + 5 aux + stats) - patches/D1.3_collection_items/ ← 7 arquivos - patches/D1.4_kit_collaboration/ ← 7 arquivos - patches/D1.5_dashboard_widgets/ ← 7 arquivos (inclui table user_comparisons descoberta como dep) STATS GERAIS: | Patch | Tables | Indexes | Policies | Alters | Functions | Tamanho | |---|---:|---:|---:|---:|---:|---:| | D.1.1 | 6 buckets | – | 34 | – | – | 12 KB | | D.1.2 | 1 | 1 | 1 | 5 | 6 | 60 KB | | D.1.3 | 2 | 5 | 5 | 8 | 2 | 143 KB | | D.1.4 | 4 | 6 | 13 | 15 | 2 | 303 KB | | D.1.5 | 1 | 3 | 5 | 3 | 6 | 143 KB | | TOTAL | 14 | 15 | 58 | 31 | 18 | ~661 KB | GARANTIAS: - Cada patch é idempotente (CREATE IF NOT EXISTS, DROP IF EXISTS + CREATE) - Cada patch é atômico (BEGIN/COMMIT) - Cada pasta tem 6 arquivos: README, backup, patch, validate, rollback, smoke_test - Smoke check sintático passou: BEGIN/COMMIT 1:1, $$ pairs OK EXTRAÇÃO: - Script Node.js automatizado em /tmp/extract_objects_v2.mjs - Aux files em /tmp/generate_aux_files.mjs - Sem intervenção manual nos SQL gerados DEPENDÊNCIAS DESCOBERTAS: - D.1.5 depende de collection_items (D.1.3) — documentado no BATCH_D1_README.md - D.1.5 ganhou table user_comparisons na revisão (estava faltando) PRÓXIMO PASSO: Sponsor abre BATCH_D1_README.md e os 5 patches/D1.X_*/README.md → revisa em batch → manda GO → eu aplico SEQUENCIALMENTE com validate entre cada Decisão 003 honrada: 100% Lovable, sem migrações, coexistência aceita.
PROBLEMA DESCOBERTO antes de aplicar D.1.2: Os patches D.1.2-5 gerados pelo extract_objects_v2.mjs estavam CORROMPIDOS porque o regex usava `[\s\S]+?` (ganancioso) ao invés de delimitar por blocos. Sintoma: patch.sql do D.1.2 (60KB) tinha 50+ indexes e policies de OUTRAS tabelas (admin_audit_log, ai_usage_logs, app_vitals, etc). SOLUÇÃO (v3): parser baseado em blocos pg_dump - pg_dump separa cada objeto com cabeçalho "-- Name: X; Type: Y; Schema: Z" - Novo extrator parseia esses blocos e filtra pelo target real - Resultado: patches MUITO menores e CORRETOS NOVOS TAMANHOS DOS PATCHES: - D.1.2: 60KB → 12KB (296 linhas, BEGIN/COMMIT 1:1, $$ 18) - D.1.3: 143KB → 6KB (125 linhas, BEGIN/COMMIT 1:1, $$ 8) - D.1.4: 303KB → 10KB (183 linhas, BEGIN/COMMIT 1:1, $$ 12) - D.1.5: 143KB → 7KB (168 linhas, BEGIN/COMMIT 1:1, $$ 14) DEPS DESCOBERTAS NO AUDIT: - D.1.2 ganhou `optimization_queue_runs` (dep de complete_optimization) - D.1.2 ganhou `connection_test_history` (dep de get_auto_test_job_status) D.1.1 (storage policies) NÃO afetado — usa block09 direto sem extração. Já foi aplicado com sucesso (commit anterior): 6 buckets + 34 policies. PRÓXIMO PASSO: aplicar D.1.2 corrigido.
📊 RESUMO: - D.1.1 Storage Policies → 6 buckets + 34 policies - D.1.2 Optimization Queue → 3 tables + 6 funcs - FASE 1 Plano A' RENAME atômico → B2B preservado (7+4433 rows) - D.1.3 Collection Items v2 (Lovable) → 3 tables + 2 funcs - D.1.4 Kit Collaboration → 4 tables + 2 funcs - D.1.5 Dashboard Widgets → 1 table + 6 RPCs TOTAL: 14 tables Lovable resgatadas + 16 RPCs P1 + ~60 policies + 6 buckets 🛡️ B2B PRESERVADO: - b2b_collections: 7 rows (Natal 2026, Linha Térmica, etc) - b2b_collection_products: 4433 rows vinculados - FK + policies + PK renomeados consistentemente 💾 BACKUPS DISPONÍVEIS: - _backup_collections_b2b_20260511 - _backup_collection_products_b2b_20260511 - _backup_collections_policies_b2b_20260511 - _backup_storage_buckets_20260511_d11 - _backup_functions_d12 ⚙️ VALIDAÇÕES: - 0 funcs sem search_path explícito - 0 tables D.1 com RLS off - Frontend volta a funcionar (types.ts já esperava Lovable) ATUALIZADO: - DECISIONS.md: Decision 004 (Plano A') + Decision 005 (D.1 done) - BATCH_D1_README.md: status ✅ em todos + lições aprendidas PENDÊNCIAS: - FK connection_test_history.connection_id (após D.2 criar external_connections) - Cron pg_cron para cleanup_expired_collection_trash (investigar block11) - Smoke test manual do sponsor antes de D.2 - Regenerar types.ts (Supabase CLI) - GlitchTip Auth Token (Frente A)
═══════════════════════════════════════════════════════════════════ RESUMO EXECUTIVO ═══════════════════════════════════════════════════════════════════ ✅ D.1.1 — Storage Policies (6 buckets + 34 policies storage.objects) ✅ D.1.2 — Optimization Queue (3 tables + 5 policies + 6 funcs) ✅ D.1.3 — Collection Items v2 LOVABLE (3 tables + 7 policies + 2 funcs)⚠️ Aplicado APÓS Decision 004 (Plano A' — renomeação B2B) ✅ D.1.4 — Kit Collaboration (4 tables + 13 policies + 2 funcs) ✅ D.1.5 — Dashboard Widgets (1 table + 5 policies + 6 funcs P1) MÉTRICAS: - 13/13 tables Lovable resgatadas - 16/16 RPCs P1 críticas ativas - 30 policies em public.* + 34 em storage.objects - 0 dados perdidos (B2B preservado com prefixo b2b_) ═══════════════════════════════════════════════════════════════════ DECISION 004 — Plano A' (Coexistência B2B + Lovable) ═══════════════════════════════════════════════════════════════════ COLISÃO DESCOBERTA mid-execução: - destino: collections schema B2B (slug, organization_id, 7 rows "Natal 2026", "Linha Térmica", etc + 4433 collection_products) - Lovable: collections schema B2C (user_id, share_token) - frontend useCollections.ts faz .eq("user_id") → HARD BROKEN SIMULAÇÃO: 100 cenários × 4 opções - A' (renomear B2B + criar Lovable): score 87/100 ✅ VENCEDOR - B (renomear destino atual): 8/100 - C (skip): 15/100 - D (pausa+audita): 0/100 APROVADO PELO SPONSOR. APLICADO: - RENAME collections → b2b_collections (7 rows preservados) - RENAME collection_products → b2b_collection_products + RENAME COLUMN collection_id → b2b_collection_id (4433 preservados) - RENAME PK, FK, 4 policies com prefixo b2b_ - Backup completo em _backup_*_b2b_20260511 - CREATE collections schema Lovable - CREATE collection_items + collection_items_trash - Frontend volta a funcionar sem mudar 1 linha React ═══════════════════════════════════════════════════════════════════ BUGS ENCONTRADOS E CORRIGIDOS NO PROCESSO ═══════════════════════════════════════════════════════════════════ 1. Extrator regex ganancioso v2 → v3 (parser pg_dump por blocos) 2. Colisão collections B2B vs Lovable → Decision 004 3. Order-of-creation D.1.4 → reordenar TABLES → FUNCS → POLICIES ═══════════════════════════════════════════════════════════════════ PENDÊNCIAS PARA D.2 ═══════════════════════════════════════════════════════════════════ - Adicionar FK connection_test_history.connection_id (D.2 cria external_connections — FK falhou silenciosamente, esperado) - Regenerar types.ts (npx supabase gen types) - Smoke test no app - Advisors security + performance - Cron pg_cron para cleanup_expired_collection_trash() Estado: SAFE — todos backups intactos, rollback documentado.
D.2.4 External Connections (CRÍTICO - fechou FK D.1): - table external_connections + 4 indexes + 1 policy - 5 RPCs: get/set_connection_failure_window_minutes, get/set_connections_auto_test_interval, sync_external_connections_from_credentials - FK retroativa: connection_test_history.connection_id → external_connections.id ✅ D.2.1 Security & Audit (7 tables + 4 RPCs): - access_security_settings, audit_logs, auth_login_attempts, geo_allowed_countries - hardening_health_snapshots, rls_denial_log, step_up_audit_log - log_access_denied, log_rls_denial, log_user_logout, check_hardening_status - enum step_up_action criado - admin_audit_log expandido: +7 cols (status, source, started_at, finished_at, duration_ms, request_id, payload_summary) D.2.2 Outbound Webhooks: outbound_webhooks + webhook_deliveries D.2.3 MCP Keys: mcp_api_keys (FORCE RLS) + mcp_key_auto_revocations + mcp_full_grantors + mcp_access_violations + can_grant_mcp_full D.2.5 Telemetry: app_vitals + query_telemetry + webhook_delivery_metrics + 6 RPCs Decision 007 (Plano A''): system_settings renomeado para system_settings_legacy (78 rows preservados), criada nova system_settings com schema Lovable (key/value). Smoke test: 19/19 tables OK, 11/11 funcs executam, 3 funcs admin-protected funcionando.
…credentials CONTEXTO: Auditoria do Decision 007 (system_settings_legacy) revelou que 12 secrets estavam em local incorreto: tabela com 1 policy ALL genérica, sem criptografia, misturada com configs e backups históricos. AÇÃO: Migrados para public.integration_credentials (RLS + 4 policies granulares + acesso restrito a is_admin_or_above + trigger auto-derive). 12 secrets migrados: - 10 Cloudflare (ACCOUNT_HASH, ACCOUNT_ID, API_TOKEN, IMAGES_URL, STREAM_SUBDOMAIN, VARIANT_*) - 2 XBZ (CDN_BASE_URL, IMAGE_SOURCE) Melhorias incrementais: - Trigger tg_integration_credentials_derive: adiciona reconhecimento de prefixos CLOUDFLARE_ e XBZ_ (futuras inserções terão provider auto-derivado) - Dual storage temporário (legacy preservado por 1-2 semanas para rollback) Auditoria de uso confirmou: zero código atual (Edge Functions OU Frontend) consome esses tokens — eram cadastrados em antecipação a features ainda não implementadas. Agora estão no local correto, prontos para uso quando a integração Cloudflare for desenvolvida. Sem breaking changes. Rollback disponível via: DELETE FROM integration_credentials WHERE notes LIKE '%Decision 007%'; Documentação completa: recovery/agent-db/SECRETS_MIGRATION_FASE2.md
Caminho B — Fase 1 de 3: atualização da documentação interna. Arquivos atualizados: - DECISIONS.md: adicionadas Decisions 006 (D.2), 007 (rename system_settings A''), 008 (Fase 2 secrets) - EXECUTION_LOG.md: reescrito com status real (D.1+D.2+Fase 2 todos aplicados em PROD) - progress.md: quadro consolidado com 28 tables + 26 RPCs + 2 bugs corrigidos - HANDOFF.md: atualizado para próxima sessão (estado atual + próxima ação = PR) - ISSUES.md: 4 issues fechadas + 5 abertas não-bloqueantes catalogadas Próximos passos: - Fase 2 (Caminho B): criar 20 arquivos auxiliares D.2 (backup/rollback/validate/smoke_test) - Fase 3 (Caminho B): abrir PR recovery/lovable-introspection → main
…om D.1) Caminho B — Fase 2 de 3: cria backup.sql + rollback.sql + validate.sql + smoke_test.md para cada um dos 5 patches D.2. Agora D.1 e D.2 têm a mesma estrutura completa. Arquivos criados (4 por patch × 5 patches = 20): D.2.1 Security & Audit: - backup.sql (snapshot admin_audit_log antes do ALTER) - rollback.sql (DROP 4 RPCs + 7 tables + enum + reverter ALTER de admin_audit_log) - validate.sql (7 checks: tables, RLS, RPCs, enum, alters, policies, indexes) - smoke_test.md (7 testes manuais incluindo enum + RLS) D.2.2 Outbound Webhooks: - backup.sql (tabelas são novas, verificação preventiva) - rollback.sql (DROP webhook_deliveries + outbound_webhooks com CASCADE) - validate.sql (5 checks: tables, RLS, policies, FK, indexes) - smoke_test.md (3 testes incluindo FK CASCADE) D.2.3 MCP API Keys: - backup.sql (verificação preventiva) - rollback.sql (DROP can_grant_mcp_full + 4 tables com CASCADE) - validate.sql (6 checks: tables, FORCE RLS crítico, RLS, RPC, policies, indexes) - smoke_test.md (4 testes incluindo verificação de FORCE RLS) D.2.4 External Connections (fecha bug 18 dias): - backup.sql (snapshot connection_test_history antes da FK CASCADE) - rollback.sql (DROP FK retroativa + 5 RPCs + tabela) - validate.sql (7 checks: table, RLS, RPCs, FK retroativa, indexes, policy, RPC bug-fixed) - smoke_test.md (4 testes incluindo verificação explícita do bug histórico fixado) D.2.5 Telemetry: - backup.sql (verificação preventiva) - rollback.sql (DROP 6 RPCs + 3 tables base) - validate.sql (5 checks: tables, RLS, RPCs + execução de get_app_health_summary) - smoke_test.md (5 testes incluindo cenários admin-protected) Próximo passo (Fase 3): abrir PR recovery/lovable-introspection → main
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughCria extensa base de documentação e scripts de recuperação Supabase, adiciona enums/policies/triggers/views/storage/realtime/cron, exemplos TSX/Edge e auditorias. Inclui novo RPC SECURITY DEFINER para expurgar artefatos step-up e agenda via pg_cron a cada 15 minutos. ChangesExport/Recovery + Purge Job
Sequence Diagram(s)(sem diagramas adicionais) Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cfcdb6c752
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| CREATE POLICY "Acesso de inserção para usuários autenticados em personaliza" | ||
| ON storage.objects FOR INSERT TO authenticated | ||
| WITH CHECK (bucket_id = 'personalization-images'); |
There was a problem hiding this comment.
Recreate storage policies idempotently
When this storage patch is run on a database that already has any of these policy names, the first CREATE POLICY raises a duplicate-object error and rolls back the whole transaction. The patch README/workflow says policy-name collisions are mitigated with DROP POLICY IF EXISTS, but the script never drops the policies before recreating them, so reruns or partially restored environments cannot apply D1.1.
Useful? React with 👍 / 👎.
| CREATE INDEX IF NOT EXISTS idx_kit_collab_user ON public.kit_collaborators USING btree (user_id); | ||
|
|
||
| -- ─────────── POLICIES: public.kit_collaborators (4) ─────────── | ||
| CREATE POLICY "Owner can invite collaborators" ON public.kit_collaborators FOR INSERT WITH CHECK (public.is_kit_owner(kit_id, auth.uid())); |
There was a problem hiding this comment.
Define kit helper functions before policies
If this patch is applied to an environment where public.is_kit_owner/public.is_kit_collaborator are not already present, it fails at the first policy creation because those helper functions are only created later in the same file. Since the patch itself claims to create these functions, a clean recovery/bootstrap of the kit collaboration objects rolls back before reaching the function definitions.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 9
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
🟠 Major comments (14)
recovery/analysis/85_TABELAS_FALTANTES_REAIS.md-1-10 (1)
1-10:⚠️ Potential issue | 🟠 Major | ⚡ Quick winInconsistência com RECOVERY_PLAN.md.
Este documento afirma 85 tabelas faltantes (linha 10), enquanto
RECOVERY_PLAN.mdafirma 65 tabelas faltantes (linhas 1, 15). Uma diferença de 20 tabelas impacta diretamente o planejamento, cronograma e validação da recuperação.Recomendo consolidar os números antes de aplicar qualquer batch em PROD.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/analysis/85_TABELAS_FALTANTES_REAIS.md` around lines 1 - 10, The numbers for missing tables are inconsistent between the analysis doc and the recovery plan: update and reconcile the counts by re-running the canonical dump/counts and then update both "85_TABELAS_FALTANTES_REAIS.md" (the table under "Faltam recuperar") and "RECOVERY_PLAN.md" to the same authoritative value; reference the source of truth you used (e.g., the dump/recount script or query), add a one-line note in both documents stating which script/query produced the count and its timestamp, and if you changed the count from 85 to 65 (or vice versa) also update any downstream totals/estimates that depend on that number so planning numbers remain consistent.recovery/agent-db/SECRETS_MIGRATION_FASE2.md-86-89 (1)
86-89:⚠️ Potential issue | 🟠 Major | ⚡ Quick winRevertir com filtro por
secret_nameexplícito, não pornotes LIKENa linha 88, usar
DELETE ... WHERE notes LIKE '%Decision 007%'é riscado em produção: o camponotesé livre para documentação e admin, qualquer nova credencial documentada com "Decision 007" será deletada no rollback. Filtrar pelos 12secret_nameconcretos (CLOUDFLARE_* e XBZ_*) torna a operação determinística e segura.Diff
-DELETE FROM public.integration_credentials WHERE notes LIKE '%Decision 007%'; +DELETE FROM public.integration_credentials +WHERE secret_name IN ( + 'CLOUDFLARE_ACCOUNT_HASH', + 'CLOUDFLARE_ACCOUNT_ID', + 'CLOUDFLARE_API_TOKEN', + 'CLOUDFLARE_IMAGES_URL', + 'CLOUDFLARE_STREAM_SUBDOMAIN', + 'CLOUDFLARE_VARIANT_LARGE', + 'CLOUDFLARE_VARIANT_MEDIUM', + 'CLOUDFLARE_VARIANT_PUBLIC', + 'CLOUDFLARE_VARIANT_SMALL', + 'CLOUDFLARE_VARIANT_THUMBNAIL', + 'XBZ_CDN_BASE_URL', + 'XBZ_IMAGE_SOURCE' +);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/agent-db/SECRETS_MIGRATION_FASE2.md` around lines 86 - 89, The DELETE rollback uses a fragile notes LIKE filter; change it to delete only the known 12 secrets by filtering on the secret_name column in the integration_credentials table (explicit WHERE secret_name IN (...)) using the concrete CLOUDFLARE_* and XBZ_* secret_name values so the operation is deterministic and won’t accidentally remove other rows; ensure the list contains exactly the 12 original secret_name entries and nothing else.recovery/block15_migrations_history.md-420-421 (1)
420-421:⚠️ Potential issue | 🟠 Major | ⚡ Quick winCast direto de
versionpara timestamp derruba a query com versões legadasA query em linha 420 falha com
ERROR: invalid input syntax for type timestampporque o inventário contém versões em múltiplos formatos (001_,20250103_01_,20250103_complete_schema, com e sem UUIDs). Apenas versões com exatamente 14 dígitos são parseáveis. Isso quebra o bloco inteiro em execução.Use validação antes do cast:
Diff sugerido
- max(version)::timestamp AS ultima_em + max( + CASE + WHEN version ~ '^[0-9]{14}$' + THEN to_timestamp(version, 'YYYYMMDDHH24MISS') + ELSE NULL + END + ) AS ultima_em🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/block15_migrations_history.md` around lines 420 - 421, The query is failing because it casts heterogeneous version strings directly to timestamp; update the SELECT to only convert versions that match exactly 14 digits and ignore others: in the query that selects max(...) AS ultima_em from supabase_migrations.schema_migrations, replace the direct cast of version to timestamp with a guarded expression that checks version ~ '^\d{14}$' (or equivalent regex) and then converts that 14-digit string to a timestamp using to_timestamp with the 'YYYYMMDDHH24MISS' format; non-matching versions should yield NULL so they don't break the aggregate.recovery/block12_edge_functions_batch2.md-310-324 (1)
310-324: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy liftValidação de CPF/CNPJ não verifica dígitos verificadores.
A função aceita qualquer sequência de 11 ou 14 dígitos sem validar os dígitos verificadores do CPF/CNPJ. Isso permite assinaturas com documentos falsos como "111.111.111-11" ou "000.000.000-00".
Para assinaturas com valor jurídico, adicione validação de dígitos verificadores ou ao menos adicione um aviso no metadata da assinatura indicando que a validação algorítmica não foi feita.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/block12_edge_functions_batch2.md` around lines 310 - 324, The current approval branch only checks length of signer_document digits (docDigits) and allows invalid CPFs/CNPJs; update the validation in the block guarded by if (response === "approved") that reads signer_document/signer_name to perform proper CPF and CNPJ checksum validation (implement the standard modulus-11 routines for CPF and the CNPJ two-check-digit algorithm) and reject inputs that fail the checks, returning the same 400 Response pattern, or if you cannot enforce checksum validation right now, add a metadata flag to the created signature object (e.g., signatureMetadata.validationPerformed = false and/or validationResult = "not_checked") and include a warning message in the response body indicating algorithmic validation was not performed; modify the docDigits length check location (the code that constructs docDigits and the error responses) to incorporate the new checksum logic or metadata instead of only length-checking.recovery/agent-db/ISSUES.md-53-59 (1)
53-59: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winIssue
#7(migration idempotente) merece atenção antes do merge.O problema descrito é real: se
supabase db resetfor executado, a migration20260423185624vai cair no mesmo bug deCREATE TABLE IF NOT EXISTScom schema divergente.Sugestão: adicione a migration corretiva
20260511_fix_system_settings_schema.sqlneste PR em vez de deixar para depois. É uma linha adicional de código mas previne repetição do bug crítico de 18 dias.Posso gerar a migration idempotente se quiser.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/agent-db/ISSUES.md` around lines 53 - 59, Add an idempotent fix migration and a warning comment: update the original migration file (migration `20260423185624`) to include a clear alert comment about the non-idempotent CREATE TABLE behavior, and add a new migration named `20260511_fix_system_settings_schema.sql` that performs a safe rename+recreate sequence for the system_settings table (detecting existing schema, renaming the old table if necessary, creating the correct table, copying data, and dropping the old table) so the operations are idempotent on fresh clones and after `supabase db reset`; ensure the new migration logic references the existing table name `system_settings` and uses transactional/conditional SQL to avoid failures on repeated runs.recovery/block05_triggers.sql-640-650 (1)
640-650:⚠️ Potential issue | 🟠 Major | ⚡ Quick winTriggers conflitantes mantendo limites diferentes em
recently_viewed_products.Ambos os triggers disparam em INSERT, mas com lógicas que entram em conflito:
trigger_limit_recently_viewed(INSERT/UPDATE) mantém até 100 items por usuáriotrigger_limit_recently_viewed_products(INSERT) mantém até 50 items por usuárioNa prática, cada INSERT executa ambas as funções na mesma transação. A segunda função sempre sobrescreve a primeira, mantendo apenas 50 items e tornando a primeira inútil. Além disso, a ordem de execução entre triggers com mesmo timing (AFTER INSERT) não é garantida.
Decida qual limite é desejado (100 ou 50) e mantenha apenas um trigger/função.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/block05_triggers.sql` around lines 640 - 650, There are two conflicting triggers for recently_viewed_products—trigger_limit_recently_viewed (AFTER INSERT OR UPDATE -> public.limit_recently_viewed_items()) and trigger_limit_recently_viewed_products (AFTER INSERT -> public.limit_recently_viewed_products())—that both run on INSERT and enforce different limits (100 vs 50); decide which limit is correct, keep only one trigger+function implementing that limit (either consolidate logic into public.limit_recently_viewed_items() or public.limit_recently_viewed_products()), drop the other CREATE TRIGGER line (and remove/rename the unused function if present), and ensure the remaining trigger has the appropriate timing (INSERT or INSERT OR UPDATE) and comment updated in the SQL file.recovery/block11_cron_jobs.sql-22-43 (1)
22-43: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
⚠️ Fortaleça o aviso anti-hardcode de secrets nos templates.Embora exista um aviso na linha 24 para não commitar a
anon_key, o template ainda mostra o padrão<ANON_KEY>diretamente no código comentado (linhas 37-38). Risco: alguém pode substituir o placeholder por uma chave real e acidentalmente commitar.🔐 Sugestão: reforce o aviso e mostre APENAS o padrão vault
--- EXEMPLO 1: invocar edge function a cada minuto ---- +-- ⚠️ ATENÇÃO: NUNCA hardcode secrets no código! +-- Passo 1: crie os secrets no Vault primeiro: +-- SELECT vault.create_secret('https://<project>.supabase.co/...', 'edge_url_webhook_alerts'); +-- SELECT vault.create_secret('<sua_anon_key>', 'edge_anon_key'); +-- +-- Passo 2: use os secrets via vault.decrypted_secrets: SELECT cron.schedule( 'webhook-alerts-monitor', '* * * * *', $$ - SELECT net.http_post( - url := 'https://<PROJECT_REF>.supabase.co/functions/v1/webhook-alerts-monitor', - headers := jsonb_build_object( - 'Content-Type', 'application/json', - 'apikey', '<ANON_KEY>', - 'Authorization','Bearer <ANON_KEY>' - ), - body := jsonb_build_object('triggered_at', now()) - ); + SELECT public.cron_invoke_edge( + 'webhook-alerts-monitor', + jsonb_build_object('triggered_at', now()) + ); -- helper que lê do Vault internamente $$ ); +-- +-- OU, se preferir net.http_post direto (avançado): +-- SELECT net.http_post( +-- url := (SELECT decrypted_secret FROM vault.decrypted_secrets WHERE name = 'edge_url_webhook_alerts'), +-- headers := jsonb_build_object( +-- 'Content-Type', 'application/json', +-- 'apikey', (SELECT decrypted_secret FROM vault.decrypted_secrets WHERE name = 'edge_anon_key'), +-- 'Authorization','Bearer ' || (SELECT decrypted_secret FROM vault.decrypted_secrets WHERE name = 'edge_anon_key') +-- ), +-- body := ... +-- );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/block11_cron_jobs.sql` around lines 22 - 43, Atualize o template para eliminar o placeholder literal "<ANON_KEY>" nas chamadas de net.http_post e em todo o bloco de exemplo e substitua-o por instruções de uso do Vault (referenciando vault.create_secret) e/ou por um placeholder claramente marcado como VAULT_SECRET (por exemplo, "VAULT_EDGE_ANON_KEY"); também fortaleça a mensagem de aviso no topo para obrigar o uso de vault.create_secret antes de executar cron.schedule e adicione uma linha explicita orientando a recuperar o segredo do Vault em tempo de execução em vez de embuti‑lo no código.recovery/agent-db/DECISIONS.md-244-248 (1)
244-248: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winComando de reversão está correto, mas adicione validação.
O comando
DELETE FROM public.integration_credentials WHERE notes LIKE '%Decision 007%'funcionará para rollback. Porém, adicione validação pré-delete para evitar acidentes:🛡️ Comando de reversão mais seguro
-- 1) Verificar quantos rows serão afetados SELECT COUNT(*) as rows_to_delete FROM public.integration_credentials WHERE notes LIKE '%Decision 007%'; -- Esperar: 12 -- 2) Se count = 12, então executar delete BEGIN; DELETE FROM public.integration_credentials WHERE notes LIKE '%Decision 007%'; -- Os 12 originais continuam intactos em system_settings_legacy COMMIT;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/agent-db/DECISIONS.md` around lines 244 - 248, Atualize o rollback para incluir validação pré-delete e execução em transação: antes do comando DELETE FROM public.integration_credentials WHERE notes LIKE '%Decision 007%', execute um SELECT COUNT(*) FROM public.integration_credentials WHERE notes LIKE '%Decision 007%' e verifique que o resultado é 12; só então executar o DELETE dentro de um BEGIN ... COMMIT; se o count for diferente, não execute o DELETE (ou faça ROLLBACK) e registre/lançe um alerta para investigação.recovery/block09_storage.sql-8-18 (1)
8-18: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winConsidere restringir allowed_mime_types para segurança.
Todos os buckets têm
allowed_mime_types = NULL, permitindo qualquer tipo de arquivo. Isso pode ser vetor de ataque (upload de executáveis, scripts maliciosos, etc).Sugestão: Defina mime types específicos por bucket:
personalization-images:ARRAY['image/png','image/jpeg','image/webp']product-videos:ARRAY['video/mp4','video/webm']supplier-logos:ARRAY['image/png','image/jpeg','image/svg+xml']component-media: idem imagesmockup-art-files: images + PDFs se necessárioquarantine: manter NULL (proposital para files suspeitos)🔒 SQL com mime types restritos
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types) VALUES ('component-media', 'component-media', false, 5242880, ARRAY['image/png','image/jpeg','image/webp','image/gif']), ('personalization-images', 'personalization-images', false, 5242880, ARRAY['image/png','image/jpeg','image/webp']), ('product-videos', 'product-videos', false, 104857600, ARRAY['video/mp4','video/webm','video/quicktime']), ('supplier-logos', 'supplier-logos', false, 2097152, ARRAY['image/png','image/jpeg','image/svg+xml']), ('mockup-art-files', 'mockup-art-files', false, 5242880, ARRAY['image/png','image/jpeg','image/webp','application/pdf']), ('quarantine', 'quarantine', false, 5242880, NULL) -- intencional ON CONFLICT (id) DO UPDATE ...🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/block09_storage.sql` around lines 8 - 18, The storage.buckets rows currently set allowed_mime_types = NULL (allowing any upload); update the INSERT/ON CONFLICT to provide explicit MIME restrictions per bucket (set allowed_mime_types to ARRAY[...] for the buckets listed) — e.g., for 'personalization-images' use ARRAY['image/png','image/jpeg','image/webp'], for 'product-videos' use ARRAY['video/mp4','video/webm'], for 'supplier-logos' use ARRAY['image/png','image/jpeg','image/svg+xml'], for 'component-media' and 'mockup-art-files' include appropriate image types (and application/pdf for mockup if needed), and keep 'quarantine' as NULL; apply the same EXCLUDED.allowed_mime_types in the ON CONFLICT DO UPDATE so updates preserve these arrays.recovery/block20_edge_functions_auth_audit.md-166-167 (1)
166-167:⚠️ Potential issue | 🟠 Major | ⚡ Quick winMAJOR: dropbox-list acessa token sem validação.
dropbox-listlêDROPBOX_ACCESS_TOKEN(secret) sem validar quem está chamando. Atacante com acesso à anon key pode listar arquivos Dropbox da empresa.Fix:
const auth = await authorize(req, { requireRole: 'supervisor' }); if (!auth.ok) return auth.response;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/block20_edge_functions_auth_audit.md` around lines 166 - 167, The dropbox-list edge function reads DROPBOX_ACCESS_TOKEN without authenticating callers; add an authorization guard by calling authorize(req, { requireRole: 'supervisor' }) at the start of the dropbox-list handler (and similarly for elevenlabs-scribe-token if applicable) and immediately return auth.response when auth.ok is false, ensuring no secret is accessed unless authorize succeeds.recovery/diff_validation.sh-39-54 (1)
39-54:⚠️ Potential issue | 🟠 Major | ⚡ Quick winEdge case: DSN com múltiplos
=pode quebrar parsing.Linha 40-41 usa
${arg%%=*}e${arg#*=}para split emnome=DSN. Se o DSN contiver=(ex:prod="postgresql://...?options=value"), o parsing falhará.Exemplo problemático:
./diff_validation.sh file.sql \ prod="postgres://host/db?sslmode=require" # dsn será "postgres://host/db?sslmode" (truncado)Fix sugerido:
🔧 Parsing robusto
- name="${arg%%=*}" - dsn="${arg#*=}" + # Split apenas no primeiro = + IFS='=' read -r name dsn <<< "$arg" if [[ -z "$name" || -z "$dsn" || "$name" == "$arg" ]]; then echo "erro: argumento inválido '$arg' (esperado nome=DSN)" >&2; exit 2 fi🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/diff_validation.sh` around lines 39 - 54, The current split using ${arg%%=*} and ${arg#*=} can truncate a DSN that contains '='; replace that parsing with a robust split using IFS='=' read -r name dsn <<< "$arg" so the second variable receives the remainder (including additional '=' characters), then keep the existing validation (check that name and dsn are non-empty) and continue to use ENV_NAMES+=("$name"), out="$OUT_DIR/${name}.tsv" and psql "$dsn" -f "$SQL_FILE" as before; ensure you preserve quoting when passing $dsn to psql.recovery/diff_validation.sh-49-53 (1)
49-53:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPossível command injection via DSN malicioso.
Linha 49 passa
"$dsn"diretamente parapsql. Se um DSN malicioso contiver caracteres especiais (ex:prod="$(rm -rf /)"), pode executar comandos arbitrários.Risco: Baixo em uso normal (DSNs vêm de admin), mas alto se script aceita DSNs de origem não-confiável.
Mitigação sugerida:
🛡️ Validação de DSN
+ # Validar formato básico do DSN (deve começar com postgres:// ou postgresql://) + if [[ ! "$dsn" =~ ^postgres(ql)?:// ]]; then + echo "erro: DSN inválido em '$name' (deve começar com postgres://)" >&2 + exit 2 + fi ENV_NAMES+=("$name")Alternativamente, documentar que DSNs devem vir de fonte confiável apenas.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/diff_validation.sh` around lines 49 - 53, The psql invocation uses the user-supplied variable dsn directly which can allow malicious connection strings; add a validation step (e.g., a validate_dsn function) before the psql call to allow only a safe subset of characters and formats (use a conservative POSIX regex permitting letters, digits, `@/`:._- and basic key=value pairs, or explicitly require a known prefix like "postgres://" or "host="), and if validation fails print an error and exit; reference the variable dsn and the psql invocation in the script (the block that runs psql "$dsn" -X -A -t -F $'\t' ...) and replace it so the script calls validate_dsn "$dsn" first.recovery/block16_auth_hooks_validation.sql-29-29 (1)
29-29:⚠️ Potential issue | 🟠 Major | ⚡ Quick winNome incorreto de tabela:
mcp_keysdeve sermcp_api_keysO script valida a existência de
mcp_keys(linhas 29, 62, 208-212, 264, 319, 406), mas o código das Edge Functions (batch3.md) referencia consistentementemcp_api_keys(linhas 908, 1254, 1520, 1747).Impacto: todas as validações de RLS/indexes/triggers para essa tabela retornarão
MISSING/FAILincorretamente.Fix:
VALUES ('login_attempts'), ('user_known_devices'), ('admin_audit_log'), ('step_up_challenges'), ('step_up_tokens'), ('user_roles'), - ('mcp_keys'), + ('mcp_api_keys'), ('mcp_key_auto_revocations')Aplicar o mesmo fix em todas as 6 ocorrências (linhas 29, 62, 208-212, 264, 319, 406).
Also applies to: 62-62, 208-212, 264-264, 319-319, 406-406
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/block16_auth_hooks_validation.sql` at line 29, The script wrongly references the table name 'mcp_keys' instead of the correct 'mcp_api_keys' in multiple validation checks; update every occurrence of 'mcp_keys' in recovery/block16_auth_hooks_validation.sql (the six places currently validating that table) to 'mcp_api_keys' so RLS/index/trigger validations match the Edge Functions code; ensure you replace all six instances (the ones noted in the review) so validations no longer report MISSING/FAIL.recovery/block12_edge_functions_batch4.md-1845-1851 (1)
1845-1851:⚠️ Potential issue | 🟠 Major | ⚡ Quick winValidação de input fraca em
bi-copilot(sem Zod)O body é cast diretamente para
RequestBodysem validação Zod (linha 1845):const body = (await req.json()) as RequestBody; if (!body.question || body.question.length > 500) { return new Response(JSON.stringify({ error: "Pergunta inválida." }), { status: 400 }); }Problemas:
- Sem validação de
context: atacante pode passarcontext: "string"em vez deRecord<string, any>, causando erro noJSON.stringify(linha 1865) ou comportamento inesperado.- Sem validação de
history: atacante pode passarhistory: [{ role: "hacker", content: "..." }]com roles inválidos, causando erro no AI provider.- JSON.stringify sem try/catch: se
contexttiver referências circulares,JSON.stringifylançaTypeError: Converting circular structure to JSON(linha 1865), quebrando o handler sem tratamento.Fix: adicionar validação Zod:
import { z } from "https://deno.land/x/zod@v3.23.8/mod.ts"; const RequestBodySchema = z.object({ question: z.string().min(1).max(500), context: z.record(z.unknown()), history: z.array(z.object({ role: z.enum(["user", "assistant"]), content: z.string(), })).optional(), }); const parsed = RequestBodySchema.safeParse(await req.json()); if (!parsed.success) { return new Response( JSON.stringify({ error: "Validação falhou", details: parsed.error.flatten().fieldErrors }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } const body = parsed.data;Also applies to: 1865-1865
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/block12_edge_functions_batch4.md` around lines 1845 - 1851, The handler currently casts req.json() to RequestBody and only checks body.question, which allows invalid context/history shapes and can blow up on circular structures; define a Zod schema (e.g., RequestBodySchema) that requires question: string().min(1).max(500), context: record(z.unknown()), and optional history: array(object({ role: z.enum(["user","assistant"]), content: z.string() })), then replace the cast with RequestBodySchema.safeParse(await req.json()) and return a 400 with parsed.error.flatten() details when validation fails; after validation, assign parsed.data to body and wrap any JSON.stringify(body.context) or response serialization in try/catch to handle circular references and return a 400/500 with a clear error instead of letting the handler throw; keep existing corsHeaders and Content-Type in error responses.
🟡 Minor comments (5)
recovery/block19_secrets_inventory.md-30-78 (1)
30-78:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winContagens declaradas estão inconsistentes com a lista
No bloco de Line 30-78, os totais por subtópico não fecham com os itens listados (ex.: 2.3 marcado como 8, mas há mais entradas; 2.5 marcado como 6, mas lista mais itens). Isso pode induzir provisionamento incompleto.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/block19_secrets_inventory.md` around lines 30 - 78, The counts in the headings (e.g., "2) Env vars ... (34 únicas)" and subsection totals like "2.3 Integrações de terceiros (8)" and "2.5 E2E / testes (6)") do not match the actual listed env vars; recount each subsection (2.1, 2.2, 2.3, 2.4, 2.5, 2.6) and update the numeric totals to match the items shown (or remove/adjust items so the counts are accurate), and ensure the overall total ("34 únicas") equals the sum of the subsections; reference subsection titles like "2.3 Integrações de terceiros" and specific env names (e.g., BITRIX24_WEBHOOK_URL, DROPBOX_ACCESS_TOKEN, E2E_CLEANUP_TOKEN) while making the fixes so readers and provisioning scripts see correct counts.recovery/block19_secrets_inventory.md-92-97 (1)
92-97:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winLinha da tabela em formato inválido compromete leitura do checklist
Em Line 96, a linha do “❌ Usado no código...” está como célula única (quebra de colunas), o que prejudica renderização e revisão operacional.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/block19_secrets_inventory.md` around lines 92 - 97, The markdown table row starting with "❌ **Usado no código mas NÃO presente em `fetch_secrets`**" is currently a single-cell line breaking the table; fix it by splitting it into two pipe-separated columns so the first column is the category marker and the second contains the long list of secrets, e.g. replace that line with a row like "| ❌ **Usado no código mas NÃO presente em `fetch_secrets`** | `BITRIX24_WEBHOOK_URL`, `BI_SHARE_SECRET`, `DROPBOX_ACCESS_TOKEN`, `ELEVENLABS_API_KEY`, `GITHUB_PAT`, `N8N_PRODUCT_WEBHOOK_SECRET`, `N8N_QUOTE_WEBHOOK_URL`, `QUOTE_SYNC_API_KEY`, `RESEND_API_KEY`, `SALESPRO_WEBHOOK_URL`, `VIRUSTOTAL_API_KEY`, `E2E_*`, `TEST_*`, `ALLOW_HTTP_FETCH`, `LOG_*` |" ensuring the pipes align with the header and separator rows in block19_secrets_inventory.md so the table renders correctly.recovery/block10b_replica_identity_benchmark.md-30-32 (1)
30-32:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
⚠️ Policy de teste aberta (USING (true)) — garanta remoção após benchmark.A policy "bench read all" (linha 31) expõe todas as linhas de
realtime_benchpara qualquer usuário autenticado. Embora o comentário na linha 29 alerte para remoção, há risco de esquecimento se o benchmark for interrompido ou o cleanup não for executado.🛡️ Sugestão: adicione validação pós-cleanup
Adicione ao final da seção de cleanup (após linha 184):
-- Validação: confirme que a tabela e policy foram removidas DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = 'realtime_bench') THEN RAISE EXCEPTION 'ATENÇÃO: tabela realtime_bench ainda existe! Cleanup incompleto.'; END IF; END $$; -- Confirme que não há policies órfãs SELECT schemaname, tablename, policyname FROM pg_policies WHERE policyname = 'bench read all'; -- Deve retornar 0 linhasOu, alternativamente, documente um tempo de vida máximo (TTL) para a tabela:
-- Adicione um TTL automático (requer pg_cron) SELECT cron.schedule( 'cleanup-realtime-bench', '*/30 * * * *', -- a cada 30 min $$ DROP TABLE IF EXISTS public.realtime_bench CASCADE; SELECT cron.unschedule('cleanup-realtime-bench'); $$ );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/block10b_replica_identity_benchmark.md` around lines 30 - 32, The temporary permissive policy "bench read all" on table realtime_bench is dangerous if left behind; after the cleanup section (after the existing cleanup block) add a post-cleanup validation that checks for existence of public.realtime_bench and for any pg_policies rows with policyname = 'bench read all' and fail/raise if found, and/or install a TTL/scheduled job to drop realtime_bench automatically (e.g., via cron) so the policy/table cannot persist; reference the policy name "bench read all" and table "realtime_bench" when adding these checks and the scheduled cleanup.recovery/agent-db/DECISIONS.md-24-48 (1)
24-48:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winDecisão 004 está duplicada — consolidar para clareza.
A Decisão 004 (rename B2B collections) aparece duas vezes:
- Linhas 24-48: versão resumida
- Linhas 116-152: versão expandida
Ambas documentam o mesmo evento. Sugiro manter apenas a versão expandida (linhas 116-152) e remover a duplicação, ou adicionar nota de referência cruzada.
🧹 Proposta de consolidação
Opção 1: Remover linhas 24-48, manter 116-152.
Opção 2: Manter ambas, mas adicionar cross-reference:
## ✅ Decisão 004 — Plano A' para colisão de `collections` (atualizada 2026-05-11 20:30) +> **Nota:** Versão expandida desta decisão está documentada abaixo (linhas 116-152). **Contexto:** Mid-batch durante D.1.3, descoberta colisão SEMÂNTICA crítica:Also applies to: 116-152
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/agent-db/DECISIONS.md` around lines 24 - 48, Remove the duplicate "## ✅ Decisão 004 — Plano A' para colisão de `collections`" summary block and keep the expanded version (the fuller "Decisão 004" section) as the single source of truth; ensure any internal cross-references or links pointing to the removed summary now point to the remaining expanded "Decisão 004" heading and update the document's table of contents or indices if present to avoid stale references.recovery/agent-db/DECISIONS.md-73-79 (1)
73-79:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAtualizar status da pendência
#1— já foi resolvida.A FK
connection_test_history.connection_idfoi criada na Decisão 006 (Batch D.2, linha 171). Sugestão:-### `#1` — FK pendente (resolverá em D.2) +### `#1` — FK pendente ✅ RESOLVIDA em D.2 `connection_test_history.connection_id` sem FK pra `external_connections`. Após D.2:🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/agent-db/DECISIONS.md` around lines 73 - 79, Atualize a entrada “#1 — FK pendente” em DECISIONS.md para marcar como resolvida: note que a FK connection_test_history.connection_id => external_connections(id) foi criada na Decisão 006 (Batch D.2, linha 171) com o constraint name connection_test_history_connection_id_fkey; remova/altere o texto que diz que a FK está pendente e adicione uma referência à Decisão 006 indicando onde a alteração foi aplicada.
🧹 Nitpick comments (11)
recovery/block06_views.sql (1)
6-28: View criada corretamente com security_invoker.O uso de
security_invoker='on'(linha 6) garante que RLS das tabelas base seja aplicado.Observação: os casts diretos nas linhas 14 e 16 (
::uuid,::timestamptz) falharão semetadatacontiver dados inválidos. Para maior robustez, considere wrapping comCASEou funções de validação, mas para uma view de audit/recovery o comportamento fail-fast pode ser intencional.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/block06_views.sql` around lines 6 - 28, The view v_full_scope_grants currently casts JSON metadata fields directly ((sual.metadata ->> 'key_id')::uuid and ((sual.metadata ->> 'expires_at'))::timestamp with time zone), which will error if metadata contains invalid values; modify the SELECT to validate before casting (e.g., check the text with a regex or EXISTS condition and only cast when it matches, otherwise return NULL) for the key_id and key_expires_at expressions so the view is robust to malformed metadata while keeping the same column names (key_id, key_expires_at) and preserving the security_invoker behavior of v_full_scope_grants.recovery/RealtimeMessagesExample.tsx (1)
131-154: Handlers CRUD apropriados para código de exemplo.O error handling via
console.erroré suficiente para um exemplo demonstrativo. Em código de produção, recomenda-se feedback visual de erro ao usuário (toast, alert, etc.).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/RealtimeMessagesExample.tsx` around lines 131 - 154, The CRUD handlers (handleInsert, handleUpdate, handleDelete) currently only log errors to console.error which is acceptable for a demo; update these functions to provide user-visible error feedback in production by replacing or augmenting console.error with a UI notification (e.g., toast/alert/modal) or at minimum add a TODO comment referencing a notification helper so callers can see where to hook in user-facing error handling.recovery/block12_edge_functions_batch2.md (2)
69-76: ⚡ Quick winRate limit pode ser generoso demais para endpoint de aprovação.
30 requisições/minuto permite tentar 1800 tokens diferentes por hora antes do bloqueio. Para um endpoint que valida tokens de aprovação de orçamento, considere reduzir para 10-15 req/min para dificultar ataques de força bruta.
🔒 Sugestão de endurecimento
const protection = await runBotProtection(req, { endpoint: 'quote-public-view', - maxRequests: 30, + maxRequests: 10, windowSeconds: 60, blockSeconds: 3600, allowSearchBots: false, }, corsHeaders);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/block12_edge_functions_batch2.md` around lines 69 - 76, The rate limit for the approval endpoint configured via runBotProtection (endpoint: 'quote-public-view') is too permissive; reduce maxRequests from 30 to a lower value (e.g., 10–15) while keeping windowSeconds at 60 and preserving/blocking behavior via blockSeconds (3600) to increase brute-force resistance; update the call-site that sets { maxRequests: 30, windowSeconds: 60, blockSeconds: 3600, allowSearchBots: false } so maxRequests is lowered and ensure any related tests or docs referencing this limit are adjusted accordingly.
257-285: 💤 Low valueWhitelist de dados públicos bem implementada, mas client_id pode vazar info.
A exclusão explícita de
negotiation_markup_percent,real_subtotalereal_discount_percentestá correta para proteger dados de pricing interno. Porém,client_id(linha 262) exposto permite enumerar IDs de clientes via força bruta de tokens, o que pode ser usado para inferir volume de negócios.Se
client_idnão for necessário no frontend público, remova. Se for, considere adicionar ao log de acesso para auditoria.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/block12_edge_functions_batch2.md` around lines 257 - 285, The publicQuote object is leaking internal identifiers via client_id; remove client_id from the whitelist (delete the client_id: quote.client_id line in the publicQuote construction) unless the frontend truly needs it—if it must remain, replace direct exposure with a safe reference (e.g., a hashed/opaque id) and add an access audit in the code path that returns publicQuote (log the access with request/user context). Update usages that expect publicQuote.client_id to use the new safe reference or server-side lookups instead.recovery/block10b_replica_identity_benchmark.md (1)
92-157: ⚡ Quick winBenchmark SQL bem estruturado, mas atenção ao ambiente de execução.
O script usa
\gset(linhas 100-101, 133-134), que é sintaxe específica dopsql. O comentário nas linhas 159-161 documenta isso, mas seria útil fornecer a versão alternativa completa para SQL Editor (Lovable Cloud) logo após cada rodada, não apenas a nota final.📝 Sugestão: adicione versão SQL Editor inline
Após a rodada A (linha 125), adicione:
-- Alternativa para SQL Editor (sem \gset): -- 1. Rode antes da carga: -- SELECT wal_records, wal_bytes, now() FROM pg_stat_wal; -- 2. Anote os valores -- 3. Rode a carga (INSERT/UPDATE/DELETE) -- 4. Rode novamente: -- SELECT wal_records, wal_bytes, now() FROM pg_stat_wal; -- 5. Subtraia manualmenteIsso reduz fricção para quem não tem acesso a
psql.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/block10b_replica_identity_benchmark.md` around lines 92 - 157, The script relies on psql-only `\gset` usage in the pg_stat_wal snapshots (the SELECTs that set :rec_a0/:bytes_a0/:t_a0 and :rec_b0/:bytes_b0/:t_b0 around the "RODADA A" and "RODADA B" blocks); add an inline SQL-Editor-friendly alternative immediately after each psql snapshot SELECT that shows the equivalent manual steps: a plain SELECT of wal_records, wal_bytes, now() to run before the workload, instructions to record those values, re-run the same SELECT after the workload, and guidance to compute the deltas manually (so users of SQL editors like Lovable Cloud can follow without `\gset`).recovery/block09_storage.sql (1)
24-62: ⚡ Quick winPolicies duplicadas/redundantes para personalization-images.
Detectadas 4 policies redundantes:
- Linhas 24-26 e 36-38: ambas são INSERT para authenticated no mesmo bucket
- Linhas 28-30 e 32-34: ambas são SELECT para authenticated no mesmo bucket
Impacto: Não quebra funcionalidade (Postgres aceita múltiplas policies do mesmo tipo com OR), mas dificulta auditoria e manutenção.
Sugestão: Consolidar em uma policy por operação:
🧹 Limpeza de policies duplicadas
-- Remover linhas 24-30 (versões PT-BR duplicadas) -- Manter apenas linhas 32-62 (versões EN mais descritivas) -- OU consolidar em policies únicas mais claras: CREATE POLICY "personalization_images_select_authenticated" ON storage.objects FOR SELECT TO authenticated USING (bucket_id = 'personalization-images'); CREATE POLICY "personalization_images_insert_authenticated" ON storage.objects FOR INSERT TO authenticated WITH CHECK (bucket_id = 'personalization-images');🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/block09_storage.sql` around lines 24 - 62, There are duplicate storage policies for the personalization-images bucket (e.g., "Acesso de inserção para usuários autenticados em personaliza" and "Authenticated users can upload personalization images" for INSERT, and "Acesso de leitura para usuários autenticados em personalizatio" and "Authenticated users can view personalization images" for SELECT); remove the redundant entries and keep a single clear policy per operation (one INSERT policy and one SELECT policy) — either retain the existing English descriptive policies ("Authenticated users can upload personalization images" and "Authenticated users can view personalization images") or replace them with consolidated names like personalization_images_insert_authenticated and personalization_images_select_authenticated so that INSERT uses WITH CHECK (bucket_id = 'personalization-images') and SELECT uses USING (bucket_id = 'personalization-images').recovery/block22_edge_secrets_inventory.md (1)
101-1061: ⚡ Quick winMapeamento detalhado útil, mas considere padronização futura.
O mapeamento por função está completo. Observação:
SUPABASE_SERVICE_ROLE_KEYtem 11+ aliases diferentes (SERVICE_KEY, serviceRoleKey, localService, etc). Isso não é um bug bloqueador agora, mas dificulta grep/refactoring futuro.Sugestão: após o recovery, considere criar um helper centralizado
getSupabaseServiceKey()em_shared/para padronizar.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/block22_edge_secrets_inventory.md` around lines 101 - 1061, Summary: SUPABASE_SERVICE_ROLE_KEY is referenced with many different local aliases across edge functions, hindering discoverability and refactoring; create a centralized accessor to standardize usage. Add a new helper function getSupabaseServiceKey() in _shared/ (e.g. in _shared/credentials.ts or a new _shared/supabase.ts) that reads Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") with any fallback logic you need, export it, and replace direct Deno.env.get(...) calls and local aliases like SERVICE_KEY, serviceRoleKey, serviceKey, localService, supabaseServiceKey, SERVICE, etc. in the listed files to call getSupabaseServiceKey(); update places that construct createClient(...) to use the helper, run tests/linters, and do a repo-wide grep to ensure no remaining aliases are used.recovery/block09b_storage_policies_full.sql (1)
41-68: ⚡ Quick winPolicies duplicadas dificultam manutenção
O bucket
mockup-art-filestem 7 policies, sendo várias duplicadas com lógica similar mas roles diferentes (authenticatedvspublic):
- DELETE: linhas 42-43 + 46-47
- INSERT: linhas 50-51 + 54-55
- SELECT: linhas 58-59 + 62-63
- UPDATE: linha 66-67 (sem par)
Como policies do mesmo comando são combinadas com OR no Postgres, a policy
public(mais permissiva) já cobre usuáriosauthenticatedna maioria dos casos, tornando as policiesauthenticatedredundantes exceto pela verificação extra deis_supervisor_or_abovena linha 59.♻️ Sugestão de consolidação
Considere manter apenas uma policy por operação, usando role
authenticatede incluindo a lógica de supervisor:-- DELETE consolidado DROP POLICY IF EXISTS "Users can delete their own art files" ON storage.objects; DROP POLICY IF EXISTS "Users delete own art files in storage" ON storage.objects; CREATE POLICY "Users can delete own mockup art files" ON storage.objects FOR DELETE TO authenticated USING ((bucket_id = 'mockup-art-files') AND ((storage.foldername(name))[1] = (auth.uid())::text)); -- INSERT consolidado DROP POLICY IF EXISTS "Users can upload their own art files" ON storage.objects; DROP POLICY IF EXISTS "Users upload own art files to storage" ON storage.objects; CREATE POLICY "Users can upload own mockup art files" ON storage.objects FOR INSERT TO authenticated WITH CHECK ((bucket_id = 'mockup-art-files') AND ((storage.foldername(name))[1] = (auth.uid())::text)); -- SELECT consolidado (mantendo supervisor check) DROP POLICY IF EXISTS "Users can view their own or shared art files" ON storage.objects; DROP POLICY IF EXISTS "Users view own art files in storage" ON storage.objects; CREATE POLICY "Users can view own or shared mockup art files" ON storage.objects FOR SELECT TO authenticated USING ((bucket_id = 'mockup-art-files') AND (((storage.foldername(name))[1] = (auth.uid())::text) OR is_supervisor_or_above(auth.uid()))); -- UPDATE consolidado CREATE POLICY "Users can update own mockup art files" ON storage.objects FOR UPDATE TO authenticated USING ((bucket_id = 'mockup-art-files') AND ((storage.foldername(name))[1] = (auth.uid())::text));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/block09b_storage_policies_full.sql` around lines 41 - 68, There are duplicate row-level policies on storage.objects (e.g., "Users can delete their own art files"/"Users delete own art files in storage", insert/select pairs, and an update without a pair) causing redundancy; consolidate to a single policy per operation by removing the redundant TO public policies and DROPs and create one policy per operation on storage.objects using TO authenticated, reusing storage.foldername(name)[1] = auth.uid()::text for ownership checks and preserving the is_supervisor_or_above(auth.uid()) check in the SELECT policy; update policy names (e.g., "Users can delete own mockup art files", "Users can upload own mockup art files", "Users can view own or shared mockup art files", "Users can update own mockup art files") and ensure DELETE/INSERT use USING/WITH CHECK as appropriate and SELECT keeps the supervisor OR clause.recovery/block12_edge_functions_batch4.md (1)
2118-2118: ⚡ Quick winFetch sem timeout em webhook dispatch (risco de hang)
Os fetches para webhook endpoints (linhas 2030, 2118) não definem timeout:
const res = await fetch(hook.url, { method: "POST", headers, body: bodyJson });Se o endpoint alvo trava ou demora indefinidamente, o request do dispatcher fica pendurado, consumindo recursos e potencialmente bloqueando outros dispatches na fila.
Sugestão: adicionar timeout usando
AbortSignal:const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 30_000); // 30s timeout try { const res = await fetch(hook.url, { method: "POST", headers, body: bodyJson, signal: controller.signal, }); clearTimeout(timeoutId); // ... resto do código } catch (err) { clearTimeout(timeoutId); if (err.name === 'AbortError') { // Timeout atingido await supabase.from("webhook_deliveries").insert({ webhook_id: hook.id, event, payload: payload ?? null, payload_hash: phash, status_code: null, response_body_truncated: "Timeout após 30s", attempt, success: false, error_message: "Request timeout", }); } else { // ... erro de rede normal } }Also applies to: 2030-2030
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/block12_edge_functions_batch4.md` at line 2118, The fetch to hook.url currently has no timeout and can hang; wrap the fetch call with an AbortController, start a 30s timeout (setTimeout -> controller.abort()), pass controller.signal into fetch, clear the timeout on success, and in the catch handle AbortError separately: record a failed delivery into the webhook_deliveries insert (use webhook_id: hook.id, event, payload, payload_hash: phash, attempt, success: false, status_code null, response_body_truncated "Timeout após 30s", error_message "Request timeout"), otherwise handle normal network errors as before; update both occurrences around the fetch lines (including the one at line with hook.url and the one at 2030) and ensure timeoutId is cleared in all paths.recovery/block12_edge_functions_batch5.md (1)
188-188: ⚡ Quick winValidação fraca de URL scheme em inputs de imagem (potencial SSRF)
Os schemas Zod validam
imageUrl,productImageUrlelogoUrlapenas comoz.string().url()ouz.string().min(10), sem restringir o scheme parahttp/https:
- magic-up-score (linha 188):
imageUrl: z.string().min(10)— não valida se é URL- generate-ad-image (linha 533):
productImageUrl: z.string().url(),logoUrl: z.string().url().optional()Se o AI provider (Lovable AI Gateway) faz fetch server-side dessas URLs, um atacante pode passar:
file:///etc/passwd(leitura de arquivo local)http://169.254.169.254/latest/meta-data/(SSRF para metadata AWS)http://internal-service/admin(acesso a serviços internos)Sugestão: restringir schemes permitidos:
const HttpUrlSchema = z.string().url().refine( (url) => url.startsWith('http://') || url.startsWith('https://'), { message: 'URL deve usar http ou https' } ); // Aplicar em: imageUrl: HttpUrlSchema, productImageUrl: HttpUrlSchema, logoUrl: HttpUrlSchema.optional(),Nota: se o AI provider já filtra schemes no backend, o risco é menor. Mas validação client-side adiciona camada extra de defesa.
Also applies to: 533-533
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/block12_edge_functions_batch5.md` at line 188, Define a strict HTTP URL Zod schema (e.g., HttpUrlSchema using z.string().url().refine to allow only "http://" or "https://") and replace the weak validators: change magic-up-score's imageUrl (currently z.string().min(10)) to HttpUrlSchema, and in generate-ad-image replace productImageUrl: z.string().url() and logoUrl: z.string().url().optional() with productImageUrl: HttpUrlSchema and logoUrl: HttpUrlSchema.optional(); ensure the new schema is exported/available where those schemas are defined so server-side fetches cannot accept non-http(s) schemes.recovery/block12_edge_functions_batch3.md (1)
1768-1777: ⚡ Quick winStep-up não exigido para update de metadata de chave FULL (inconsistência com revoke)
O update de
nameoudescriptionde uma chave MCP com scope FULL não exige step-up (linha 1768-1777 apenas exige paraescalating). Comparando com:
- revoke (linha 1492-1517): exige step-up para QUALQUER chave
- issue (linha 847-876): exige step-up para QUALQUER chave
- rotate (linha 1186-1213): exige step-up para QUALQUER chave
Um dev com sessão comprometida pode alterar o
name/descriptionde chaves FULL existentes sem confirmação recente (senha+OTP). Isso reduz a fricção para metadata, mas cria inconsistência de segurança.Sugestão: se o design é intencional (metadata menos sensível), documente explicitamente. Caso contrário, considere exigir step-up para qualquer mutation de chave FULL:
- if (escalating) { + const isFullKey = willBeFull; // true se chave atual ou futura é FULL + if (isFullKey && Object.keys(patch).length > 0) { if (!step_up_token) { - await auditFailure("denied", { reason: "step_up_required" }, key_id); + await auditFailure("denied", { reason: "step_up_required", scope: "full" }, key_id); return jsonResponse( - { error: "step_up_required", message: "Confirme sua identidade (senha + código por e-mail) antes de escalar a chave para escopo total." }, + { error: "step_up_required", message: "Confirme sua identidade (senha + código por e-mail) antes de modificar uma chave FULL." }, 403, requestId, ); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@recovery/block12_edge_functions_batch3.md` around lines 1768 - 1777, The metadata update path currently only enforces step-up when "escalating" is true, allowing updates to name/description on FULL-scoped keys without recent password+OTP; change the logic handling key metadata updates to require step-up for any key with scope FULL by checking the key's scope (e.g., key.scope === "FULL") in the same place you check "escalating" and "step_up_token"; if no step_up_token is present for a FULL key, call auditFailure("denied", { reason: "step_up_required" }, key_id) and return the same jsonResponse( { error: "step_up_required", ... }, 403, requestId ) flow used elsewhere so metadata updates follow the same protection as revoke/issue/rotate.
| -- Name: public; Type: SCHEMA; Schema: -; Owner: - | ||
| -- | ||
|
|
||
| CREATE SCHEMA public; |
There was a problem hiding this comment.
Falta proteção IF NOT EXISTS — script vai falhar se o schema já existir.
O comando CREATE SCHEMA public; vai quebrar em ambientes onde o schema public já existe (que é o padrão em PostgreSQL/Supabase). Para execução idempotente (exigência do recovery plan), use CREATE SCHEMA IF NOT EXISTS public;.
🛡️ Fix proposto
-CREATE SCHEMA public;
+CREATE SCHEMA IF NOT EXISTS public;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| CREATE SCHEMA public; | |
| CREATE SCHEMA IF NOT EXISTS public; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@recovery/_other.sql` at line 5, O comando CREATE SCHEMA public; falha se o
schema já existir — torne o script idempotente alterando a instrução CREATE
SCHEMA public para usar a proteção IF NOT EXISTS (ou seja, use a variante que
verifica existência antes de criar) para que a execução do recovery/_other.sql
não quebre em ambientes onde o schema public já existe.
| -- Name: file_scan_logs Apenas administradores podem visualizar logs de scan; Type: POLICY; Schema: public; Owner: - | ||
| -- | ||
|
|
||
| CREATE POLICY "Apenas administradores podem visualizar logs de scan" ON public.file_scan_logs FOR SELECT TO authenticated USING (((auth.jwt() ->> 'email'::text) ~~ '%admin%'::text)); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
# Localiza todas as policies com checagem por email substring
rg -n -C2 "auth\\.jwt\\(\\).*email|LIKE '%admin%'" recovery/block03_policies.sqlRepository: adm01-debug/Promo_Gifts
Length of output: 276
🏁 Script executed:
# 1. Verificar se existe função is_admin
rg -n "is_admin|CREATE FUNCTION.*admin" recovery/Repository: adm01-debug/Promo_Gifts
Length of output: 48626
🏁 Script executed:
# 2. Verificar todas as policies com padrões de email inseguros
rg -n "email.*~~|email.*LIKE|auth\.jwt\(\).*email" recovery/Repository: adm01-debug/Promo_Gifts
Length of output: 1218
🏁 Script executed:
# 3. Verificar estrutura da tabela auth e constraints de email
rg -n "CREATE TABLE.*auth\|ALTER TABLE.*auth\|email" recovery/ | head -30Repository: adm01-debug/Promo_Gifts
Length of output: 49
🏁 Script executed:
# 4. Procurar por funções de autorização existentes
rg -n "CREATE FUNCTION|RETURNS.*boolean" recovery/block03_policies.sqlRepository: adm01-debug/Promo_Gifts
Length of output: 49
Autorização por email LIKE '%admin%' em múltiplas policies é bypass de privilégio crítico
A policy em linha 476 usa substring de email para controle ('%admin%'), permitindo que qualquer conta com esse padrão no email acesse file_scan_logs. Problema ainda maior: esse padrão vulnerável está replicado em outras policies (block09_storage.sql, block09b_storage_policies_full.sql).
Qualquer usuário com emails como usuario+admin@domain.com, admin-test@domain.com ou similares consegue ler logs sensíveis.
Substituir por public.is_admin(auth.uid()) que já existe na base:
Solução proposta
-- Linha 476 (e políticas equivalentes)
CREATE POLICY "Apenas administradores podem visualizar logs de scan"
ON public.file_scan_logs
FOR SELECT TO authenticated
USING (public.is_admin(auth.uid()));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@recovery/block03_policies.sql` at line 476, Replace the insecure
email-substring authorization in the CREATE POLICY named "Apenas administradores
podem visualizar logs de scan" on table file_scan_logs (and the equivalent
policies in block09_storage.sql and block09b_storage_policies_full.sql) so it
calls the existing admin-check function instead of matching email text;
specifically update the USING clause to invoke public.is_admin(auth.uid())
rather than ((auth.jwt() ->> 'email') ~~ '%admin%') to prevent privilege bypass.
| -- Name: orders set_order_number; Type: TRIGGER; Schema: public; Owner: - | ||
| -- | ||
|
|
||
| CREATE TRIGGER set_order_number BEFORE INSERT ON public.orders FOR EACH ROW EXECUTE FUNCTION public.generate_order_number(); | ||
|
|
||
|
|
||
| -- | ||
| -- Name: integration_credentials sync_external_connections_on_credential_change; Type: TRIGGER; Schema: public; Owner: - | ||
| -- | ||
|
|
||
| CREATE TRIGGER sync_external_connections_on_credential_change AFTER INSERT OR DELETE OR UPDATE ON public.integration_credentials FOR EACH ROW EXECUTE FUNCTION public.trg_sync_external_connections(); | ||
|
|
||
|
|
||
| -- | ||
| -- Name: orders tr_generate_order_number; Type: TRIGGER; Schema: public; Owner: - | ||
| -- | ||
|
|
||
| CREATE TRIGGER tr_generate_order_number BEFORE INSERT ON public.orders FOR EACH ROW EXECUTE FUNCTION public.generate_order_number_v5(); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verificar se existem outros triggers duplicados no schema
rg -n "CREATE TRIGGER.*BEFORE INSERT.*orders" recovery/Repository: adm01-debug/Promo_Gifts
Length of output: 1443
🏁 Script executed:
#!/bin/bash
# Procurar por DROP TRIGGER para esses triggers
rg -n "DROP TRIGGER.*(set_order_number|tr_generate_order_number)" recovery/
# Procurar por comentários ou documentação sobre qual usar
rg -B5 -A5 "generate_order_number_v5" recovery/block05_triggers.sql | head -30
# Verificar se há mais triggers BEFORE INSERT na tabela orders além dos já identificados
rg -n "CREATE TRIGGER.*BEFORE INSERT.*public.orders" recovery/block05_triggers.sqlRepository: adm01-debug/Promo_Gifts
Length of output: 946
🌐 Web query:
PostgreSQL multiple BEFORE INSERT triggers same table execution order
💡 Result:
In PostgreSQL, if you define multiple triggers of the same timing and event (e.g., multiple row-level BEFORE INSERT triggers) on the same table, they fire in alphabetical order by trigger name [1]. For BEFORE triggers, each trigger can modify the row; the (possibly modified) NEW row value returned by one BEFORE trigger becomes the input to the next BEFORE trigger [1]. If any BEFORE (or INSTEAD OF) trigger returns NULL for a row, that row’s operation is abandoned and subsequent triggers are not fired for that row [1]. So to control order for multiple BEFORE INSERT triggers on the same table, name them so the desired order corresponds to alphabetical order (e.g., prefix with 01_, 02_, etc.) [1][2].
Citations:
- 1: http://www.postgresql.org/docs/current/interactive/trigger-definition.html
- 2: http://www.postgresql.org/docs/current/interactive/sql-createtrigger.html
🏁 Script executed:
#!/bin/bash
# Procurar por referências a ambas funções no schema
rg -n "generate_order_number" recovery/block04_functions.sql | head -20
# Procurar se há migrations ou commits que indicam qual é a versão "ativa"
git log --oneline -n 50 --all -- recovery/ 2>/dev/null | grep -i "order_number" || echo "Git history not available"
# Verificar se há comentários no arquivo sobre qual trigger usar
sed -n '70,95p' recovery/block05_triggers.sqlRepository: adm01-debug/Promo_Gifts
Length of output: 1407
🏁 Script executed:
#!/bin/bash
# Ler as funções para confirmar que ambas modificam NEW.order_number
sed -n '2640,2665p' recovery/block04_functions.sql
echo "=== FUNÇÃO V5 ==="
sed -n '2694,2720p' recovery/block04_functions.sqlRepository: adm01-debug/Promo_Gifts
Length of output: 1526
Remover trigger set_order_number — está sobrescrevendo o número gerado.
Dois triggers BEFORE INSERT disparam em ordem alfabética na tabela orders:
set_order_number→generate_order_number()(executa primeiro)tr_generate_order_number→generate_order_number_v5()(executa depois e sobrescreve)
PostgreSQL executa triggers BEFORE INSERT em ordem alfabética pelo nome. O segundo trigger sobrescreve o NEW.order_number gerado pelo primeiro. Além disso, as funções geram formatos diferentes:
- v1:
'PED-' || YYYY || '-' || número - v5:
'PED-' || YY || '-' || número
O resultado final é sempre do v5 (trigger tr_generate_order_number), deixando o primeiro trigger inútil e o código confuso.
Delete o trigger set_order_number da linha 76. A versão _v5 é a implementação ativa correta.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@recovery/block05_triggers.sql` around lines 73 - 90, Remova a criação do
trigger redundante "set_order_number" que executa a função
public.generate_order_number() para evitar sobrescrita pelo trigger
"tr_generate_order_number" (que executa public.generate_order_number_v5());
localize e delete a declaração CREATE TRIGGER set_order_number BEFORE INSERT ON
public.orders FOR EACH ROW EXECUTE FUNCTION public.generate_order_number();
deixando apenas o CREATE TRIGGER tr_generate_order_number ... que usa a versão
v5.
| CREATE POLICY "Admins podem visualizar quarentena" | ||
| ON storage.objects FOR SELECT TO authenticated | ||
| USING (bucket_id = 'quarantine' | ||
| AND ((auth.jwt() ->> 'email') LIKE '%admin%' | ||
| OR ((auth.jwt() -> 'app_metadata') ->> 'role') = 'admin')); |
There was a problem hiding this comment.
Auth frágil em quarantine — requer is_supervisor_or_above.
Linha 176-177 usa (auth.jwt() ->> 'email') LIKE '%admin%' para autorizar acesso a quarantine. Isso é extremamente frágil:
- Qualquer email contendo "admin" (ex:
badmin@attacker.com) passa - Bypass trivial se usuário conseguir manipular email
Fix obrigatório:
🔒 Policy segura usando RBAC
CREATE POLICY "Admins podem visualizar quarentena"
ON storage.objects FOR SELECT TO authenticated
USING (bucket_id = 'quarantine'
- AND ((auth.jwt() ->> 'email') LIKE '%admin%'
- OR ((auth.jwt() -> 'app_metadata') ->> 'role') = 'admin'));
+ AND public.is_supervisor_or_above(auth.uid()));Isso usa a função RBAC já disponível (pré-requisito linha 4) e evita bypass por email spoofing.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@recovery/block09_storage.sql` around lines 173 - 177, The SELECT policy
"Admins podem visualizar quarentena" on storage.objects uses a fragile email
LIKE check via (auth.jwt() ->> 'email') LIKE '%admin%'; replace that condition
with the RBAC check (call the available is_supervisor_or_above() function or
check ((auth.jwt() -> 'app_metadata') ->> 'role') against the RBAC-approved
roles) so only users with supervisor-or-higher privileges can access bucket_id =
'quarantine'; update the USING clause to remove the email LIKE predicate and
rely on the RBAC function/role check (keep bucket_id = 'quarantine' and the
policy name intact).
| DROP POLICY IF EXISTS "Admins podem visualizar quarentena" ON storage.objects; | ||
| CREATE POLICY "Admins podem visualizar quarentena" ON storage.objects FOR SELECT TO authenticated | ||
| USING (((bucket_id = 'quarantine'::text) AND (((auth.jwt() ->> 'email'::text) ~~ '%admin%'::text) OR (((auth.jwt() -> 'app_metadata'::text) ->> 'role'::text) = 'admin'::text)))); | ||
|
|
There was a problem hiding this comment.
A condição (auth.jwt() ->> 'email'::text) ~~ '%admin%'::text aceita qualquer email contendo a substring "admin", incluindo:
hacker+admin@evil.combadmin@attacker.orgadministrator.fake@gmail.com
Como está usando OR, basta passar na primeira condição para ter acesso total à quarentena.
🔒 Correção recomendada
DROP POLICY IF EXISTS "Admins podem visualizar quarentena" ON storage.objects;
CREATE POLICY "Admins podem visualizar quarentena" ON storage.objects FOR SELECT TO authenticated
- USING (((bucket_id = 'quarantine'::text) AND (((auth.jwt() ->> 'email'::text) ~~ '%admin%'::text) OR (((auth.jwt() -> 'app_metadata'::text) ->> 'role'::text) = 'admin'::text))));
+ USING (((bucket_id = 'quarantine'::text) AND (((auth.jwt() -> 'app_metadata'::text) ->> 'role'::text) = 'admin'::text)));Ou, se precisar validar email, use lista exata:
USING ((bucket_id = 'quarantine'::text)
AND ((auth.jwt() ->> 'email') = ANY(ARRAY['admin@promogifts.com.br', 'suporte@promogifts.com.br'])
OR ((auth.jwt() -> 'app_metadata' ->> 'role') = 'admin')))📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| DROP POLICY IF EXISTS "Admins podem visualizar quarentena" ON storage.objects; | |
| CREATE POLICY "Admins podem visualizar quarentena" ON storage.objects FOR SELECT TO authenticated | |
| USING (((bucket_id = 'quarantine'::text) AND (((auth.jwt() ->> 'email'::text) ~~ '%admin%'::text) OR (((auth.jwt() -> 'app_metadata'::text) ->> 'role'::text) = 'admin'::text)))); | |
| DROP POLICY IF EXISTS "Admins podem visualizar quarentena" ON storage.objects; | |
| CREATE POLICY "Admins podem visualizar quarentena" ON storage.objects FOR SELECT TO authenticated | |
| USING (((bucket_id = 'quarantine'::text) AND (((auth.jwt() -> 'app_metadata'::text) ->> 'role'::text) = 'admin'::text))); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@recovery/block09b_storage_policies_full.sql` around lines 137 - 140, The
policy "Admins podem visualizar quarentena" on storage.objects uses a permissive
pattern match (auth.jwt() ->> 'email' ~~ '%admin%') that grants access to any
email containing "admin"; replace that predicate with a strict check against
exact allowed emails or a membership test (e.g., compare auth.jwt() ->> 'email'
for equality or = ANY(ARRAY[...])) and keep the role check ((auth.jwt() ->
'app_metadata' ->> 'role') = 'admin') as the fallback; update the USING clause
in the CREATE POLICY for "Admins podem visualizar quarentena" to require
bucket_id = 'quarantine' AND (exact-email-membership OR role-check) so only
explicit admin addresses or role=admin are authorized.
| }); | ||
|
|
||
| // We resolve the auth context from a per-request module-level holder. | ||
| let currentCtx: AuthCtx | null = null; |
There was a problem hiding this comment.
Race condition crítica em currentCtx (module-level variable compartilhada)
currentCtx é uma variável module-level compartilhada entre todas as requisições concorrentes. Em Deno.serve, múltiplas requests rodam no mesmo isolate:
- Request A chega, seta
currentCtx = auth.ctx(linha 583) - Request B chega imediatamente, sobrescreve
currentCtxcom seu próprio contexto - Request A executa um tool handler que lê
currentCtx(linha 314) — MAS agora tem o contexto de B - Tool de A é executado com scopes/keyId de B → violação de autorização
Impacto: um usuário com chave limitada pode ter seus tools executados com scopes de outra chave ativa simultaneamente (potencialmente FULL access).
Fix: passar ctx como parâmetro explícito no closure do handler em vez de ler de variável global:
function defineTool<I>(
name: string,
guard: ToolGuard,
description: string,
inputSchema: Record<string, unknown>,
handler: ToolHandler<I>,
) {
mcpServer.tool(name, {
description: `${description} [scope: ${guard.scope} | mode: ${guard.mode}]`,
inputSchema,
- handler: async (input: I): Promise<ToolResult> => {
- const ctx = currentCtx;
+ handler: async (input: I, runtimeCtx: AuthCtx | null): Promise<ToolResult> => {
+ const ctx = runtimeCtx;
const startedAt = Date.now();
const payloadSummary = summarizePayload(input);
try {
const authed = authorizeTool(ctx, name, guard);E no bind:
app.all("/*", async (c) => {
const auth = await authenticate(c.req.raw);
// ... validações ...
const ctx = auth.ctx;
- currentCtx = ctx;
try {
- const res = await httpHandler(c.req.raw);
+ const res = await httpHandler(c.req.raw, ctx); // passar ctx
// ...
- } finally {
- currentCtx = null;
}
});Also applies to: 583-583, 314-314
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@recovery/block12_edge_functions_batch3.md` at line 361, currentCtx is a
module-level AuthCtx shared across requests causing a race; stop reading/writing
currentCtx and instead thread the request-specific AuthCtx through closures and
bind calls: remove usage of the global currentCtx, change any code that sets
currentCtx = auth.ctx to pass auth.ctx as an explicit parameter into the tool
handler factory/closure (e.g., when creating/binding handlers for tools) and
update the bind/invoke sites so each handler captures its own ctx variable
(AuthCtx) rather than accessing currentCtx; ensure all places that previously
read currentCtx (tool handler code paths) accept ctx as a param or capture it in
their closure so request A cannot read request B's context.
| 1. Usuário clica em link recebido (template default Supabase). | ||
| 2. Frontend extrai `token` (= `user.id`) e chama `POST /verify-email`. | ||
| 3. Edge usa `auth.admin.getUserById(token)` para validar. | ||
| 4. Marca `email_confirm: true` via `auth.admin.updateUserById`. | ||
| 5. Retorna `{ success: true }` ou `400 token inválido/expirado`. |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
# Verifica implementação da edge function verify-email
fd -i "verify-email" supabase/functions
rg -n -C3 "getUserById|updateUserById|token|email_confirm|confirm" supabase/functions --iglob "*verify-email*"Repository: adm01-debug/Promo_Gifts
Length of output: 100
🏁 Script executed:
find supabase/functions/verify-email -type f -name "*.ts" -o -name "*.js" -o -name "*.tsx" -o -name "*.jsx"Repository: adm01-debug/Promo_Gifts
Length of output: 108
🏁 Script executed:
cat -n recovery/block16_auth_hooks.md | head -60Repository: adm01-debug/Promo_Gifts
Length of output: 3150
🏁 Script executed:
fd -type f "verify-email" supabase/functions --exec cat {}Repository: adm01-debug/Promo_Gifts
Length of output: 238
🏁 Script executed:
cat -n supabase/functions/verify-email/index.tsRepository: adm01-debug/Promo_Gifts
Length of output: 3213
🏁 Script executed:
cat -n recovery/block16_auth_hooks.md | sed -n '40,80p'Repository: adm01-debug/Promo_Gifts
Length of output: 2006
Token previsível e sem expiração em verify-email permite bypass de confirmação
A função verify-email usa user.id (UUID) como token sem expiração ou validação de unicidade. Isso permite que qualquer pessoa com acesso ao UUID do usuário confirme indevida e permanentemente o email, mesmo fora da janela do link original. Implemente token assinado/criptografado com TTL curto (15-30min), use token de uso único em base de dados, ou migre para fluxo nativo de confirmação da Supabase Auth.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@recovery/block16_auth_hooks.md` around lines 42 - 46, A atual rota
verify-email aceita o user.id como token sem expiração nem unicidade, permitindo
confirmação indevida; change verify-email to validate a short-lived, single-use
token instead of raw UUID: generate a signed/crypted token with TTL (15–30min)
when sending the email and store a one-time token record in the DB (or use
Supabase Auth’s native confirmation flow), then in verify-email verify the
signature and TTL (or lookup and consume the one-time token record) before
calling auth.admin.updateUserById; remove acceptance of raw user.id in
auth.admin.getUserById and ensure tokens are invalidated after use.
| - Edge chama RPC `start_step_up_challenge()` que devolve | ||
| `{ challenge_id, otp_plain, expires_at }` e dispara email com OTP | ||
| (via `send-notification` ou template inline). | ||
| 2. `step="verify_password"` → confere senha do usuário (re-login). | ||
| 3. `step="verify_otp"` → valida OTP e emite **step-up token** (de | ||
| curta duração) consumido pelos endpoints sensíveis via | ||
| header `X-Step-Up-Token`. |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
# Confirma se otp_plain está no retorno da edge/RPC
rg -n -C3 "start_step_up_challenge|otp_plain|verify_otp|step-up" supabase/functions supabase/migrations recoveryRepository: adm01-debug/Promo_Gifts
Length of output: 50381
🏁 Script executed:
#!/bin/bash
# Verificar o que é retornado ao cliente HTTP na edge function step-up-verify
rg -n -A 10 "step=\"request\"" supabase/functions/step-up-verify/index.ts | head -60Repository: adm01-debug/Promo_Gifts
Length of output: 49
🏁 Script executed:
#!/bin/bash
# Verificar resposta HTTP ao cliente quando step="request"
cat supabase/functions/step-up-verify/index.ts | head -250 | tail -100Repository: adm01-debug/Promo_Gifts
Length of output: 3237
🏁 Script executed:
#!/bin/bash
# Confirmar se otp_plain é usado em logs ou outras respostas
rg -n "otp_plain|otp:" supabase/functions/step-up-verify/index.tsRepository: adm01-debug/Promo_Gifts
Length of output: 392
RPC start_step_up_challenge() não deve retornar otp_plain em plaintext
O contrato { challenge_id, otp_plain, expires_at } (linha 104 da migration 20260425203103) expõe o OTP em plaintext na camada backend. Embora o HTTP response ao cliente omita o otp_plain (retorna apenas challenge_id, expires_at), a RPC retorna plaintext para a edge function, criando surface de vazamento: logs, monitoramento, memory dumps, insider risk.
Refatoração necessária:
- RPC deve retornar apenas
{ challenge_id, hash_otp, expires_at } - RPC gera OTP, envia por email internamente, retorna apenas hash para comparação
- Ou: edge function chama segundo RPC
send_otp_and_get_hash()que trata dispatch + retorna hash - Eliminar linhas 195, 209 que lidam com plaintext no edge function
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@recovery/block16_auth_hooks.md` around lines 63 - 69, O RPC
start_step_up_challenge() não deve retornar otp_plain — altere sua assinatura
para retornar { challenge_id, hash_otp, expires_at } e faça com que o RPC gere o
OTP, envie o email internamente e só exponha o hash; alternativamente,
implementem um segundo RPC send_otp_and_get_hash() que cuide do dispatch e
retorne o hash. Atualize o edge function que chama start_step_up_challenge()
para não readicionar ou logar otp_plain (remova qualquer uso/variável/registro
de otp_plain) e passe apenas challenge_id/expires_at to the client; ajuste a
validação em verify_otp() para comparar hash_otp em vez de plaintext. Ensure no
otp_plain flows into logs, monitoring or memory paths.
| | secure-upload | upload de arquivos | **Adicionar `authorize`** ⚠️ alto risco | | ||
| | sync-quote-bitrix | sync CRM | **Adicionar `authorize`** ⚠️ | |
There was a problem hiding this comment.
CRÍTICO: secure-upload e sync-quote-bitrix SEM autenticação.
Linha 182: secure-upload permite upload de arquivos SEM nenhuma autenticação detectável. Isso é vetor de ataque direto (upload de malware, exhaustion de storage, etc).
Linha 183: sync-quote-bitrix faz sync de CRM também sem auth. Atacante pode injetar dados falsos no CRM.
Ação obrigatória antes de produção:
🚨 Adicionar auth imediatamente
// supabase/functions/secure-upload/index.ts
import { authorize } from "../_shared/edge-authz.ts";
const auth = await authorize(req, {
requireRole: 'authenticated', // ou 'supervisor' se só admins podem upload
enforceServerSide: true,
});
if (!auth.ok) return auth.response;Mesmo padrão para sync-quote-bitrix.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@recovery/block20_edge_functions_auth_audit.md` around lines 182 - 183, As
correção: ambos endpoints estão sem autenticação; injetar a verificação de
autorização chamando a função authorize do shared edge-authz em cada handler
(por exemplo no arquivo supabase/functions/secure-upload/index.ts e no handler
de sync-quote-bitrix) e exigir papel/escopo adequado (por exemplo requireRole:
'authenticated' ou 'supervisor') com enforceServerSide: true; atribua o
resultado a uma variável (ex. auth), cheque auth.ok e retorne auth.response
quando false para bloquear requisições não autorizadas antes de processar
uploads ou syncs.
Detectado via Supabase Security Advisor após o merge do PR #143: a tabela auth_login_attempts (criada no D.2.1) ficou com RLS habilitado mas sem policies, tornando-a inacessível para todos os roles exceto service_role (que bypassa RLS). Root cause: o extrator regex do dump Lovable não capturou as policies dessa tabela específica no patch.sql do D.2.1. Fix aplicado em PROD: - Admins podem SELECT (auditoria) - Service role pode INSERT (Edge Functions de auth) Padrão idêntico ao usado em audit_logs (mesma natureza). Documentação: recovery/agent-db/POST_MERGE_FIX_AUTH_LOGIN_ATTEMPTS.md ISSUES.md: Issue #10 fechada
Recovery completo dos batches D.3-D.5 após merge do PR #143: - 85/85 funções extraídas do block04_functions.sql aplicadas em PROD - Agrupadas em 11 batches funcionais: * D.4.1 Step-Up MFA (8 funcs) * D.4.2 Quote Advanced (15 funcs) * D.4.3 Ownership Audit (1 func) * D.4.5 Reactions+Trash (3 funcs) * D.4.6 Security/Auth Rate Limit (17 funcs) * D.4.7 MCP Advanced (10 funcs) * D.2.2 Webhooks Extra (4 funcs, URL adaptada) * D.2.4 External Conn Extra (3 funcs) * D.3.4 Role Migration (1 func) * D.3.5 Analytics/UX (3 funcs, types uuid) * D.5 Misc Cross-cutting (19 funcs) 3 tables auxiliares criadas (deps): - e2e_cleanup_rate_limit - security_settings - organization_members Adaptações vs Lovable: - URLs hardcoded substituídas (nmojwp -> doufsx) - maintain_webhook_metrics sem partition logic - Types text -> uuid em get_bundle_suggestions, get_client_seasonality, get_industry_seasonality, fn_create_quote_v3 Validação: 85/85 funcoes aplicadas com SECURITY DEFINER + search_path. Refs: Decision 009, BATCH_D3_D5_COMPLETE.md, EXECUTION_LOG.md
🎯 Recovery: Restaura o banco Promo_Gifts via Audit & Patch Cirúrgico
Este PR consolida 3 fases de trabalho aplicadas em PROD entre 10 e 11 de Maio de 2026:
Total: 28 tabelas novas + 26 RPCs novas + 1 enum + 7 colunas em admin_audit_log + 12 secrets migrados + 2 bugs corrigidos.
📦 O que está aplicado em PROD
Batch D.1 — P1 Features (já aplicado, este PR documenta)
Batch D.2 — P2 Infrastructure (já aplicado, este PR documenta)
Fase 2 — Migração de secrets (segurança)
integration_credentialstg_integration_credentials_deriveampliado (CLOUDFLARE_/XBZ_)🗂️ Decisions documentadas (DECISIONS.md)
collections)system_settings)🐛 Bugs corrigidos durante o recovery
🚨 CRÍTICO — Bug de 18 dias em produção
A migration
20260423185624_*.sqltentou criarsystem_settingscom schema novo viaCREATE TABLE IF NOT EXISTS, mas a tabela já existia com schema antigo (setting_key/setting_value). OIF NOT EXISTSpulou silenciosamente.Resultado:
get_connection_failure_window_minutesapontava para colunavalueque NÃO EXISTIA desde 23/Abr. CardFailureWindowCardem/admin/conexoesnunca chegou a funcionar.Fix: Decision 007 — renomeia legacy + cria nova
system_settingscom schema Lovable.🔧 Menor — Trigger sem prefixos CLOUDFLARE/XBZ
tg_integration_credentials_derivenão auto-derivava provider para CLOUDFLARE_ e XBZ_. Ampliado na Fase 2.📂 Arquivos adicionados
Patches (60 arquivos)
Documentação (12 arquivos)
🛡️ Garantias de rollback
Todos os patches têm
backup.sql+rollback.sql+validate.sql+smoke_test.md.Backups críticos preservados em PROD
_backup_collections_b2b_20260511(7 rows B2B)_backup_collection_products_b2b_20260511(4433 rows)_backup_system_settings_legacy_20260511(78 rows)_backup_admin_audit_log_pre_d21(criado durante D.2.1)_backup_connection_test_history_pre_d24(criado durante D.2.4)Dual storage temporário
12 secrets ainda em
system_settings_legacy(paralelo aintegration_credentials) por 1-2 semanas, conforme ISSUES.md #6.✅ Validação executada
⏳ Pós-merge (não bloqueante)
Conforme
ISSUES.md:src/integrations/supabase/types.tsvia Supabase CLIsystem_settings(preventivo)📊 Numbers summary
Sponsor: Joaquim (adm01@promobrindes.com.br)
Recovery executado: 10-11 de Maio de 2026
Summary by CodeRabbit
Release Notes
Documentação
Banco de Dados
Exemplos de Código