Lovable sync 1777298482#84
Conversation
X-Lovable-Edit-ID: edt-1117f999-7cd9-4ec2-862e-cc615534245e Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
X-Lovable-Edit-ID: edt-b5dd2279-0eb2-489d-850c-77feb2340fda Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
X-Lovable-Edit-ID: edt-46a5e5e9-6a5f-4c64-b846-ba5ac1ab6d5d Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
X-Lovable-Edit-ID: edt-431b9f06-e566-45ed-97cc-1b657acb52a8 Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
X-Lovable-Edit-ID: edt-e844ac62-d9ed-47e9-ad5b-94dcdd8e899d Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (12)
Warning
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 69b53fb220
ℹ️ 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".
| /** | ||
| * webhook-alerts-monitor | ||
| * ---------------------------------------------------------------- |
There was a problem hiding this comment.
Add new edge function to authz manifest
This commit introduces webhook-alerts-monitor but does not add it to supabase/functions/_shared/edge-authz-manifest.ts, which makes the existing CI gate fail closed (node scripts/check-edge-authorization.mjs reports Edge function "webhook-alerts-monitor" não está declarada). As a result, PRs containing this change will fail the authorization coverage check until the function is registered with the correct category/enforcement metadata.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
Introduz observabilidade e gates de segurança/telemetria: um monitor cron para métricas de webhooks com alertas no Sentry, enforcement de MFA no helper SSOT de autorização das edge functions, e novos gates de CI (npm audit baseline + structured logging no client).
Changes:
- Adiciona edge function
webhook-alerts-monitor+ documentação/spec SQL para detecção de falhas/spikes de webhooks e envio de eventos ao Sentry. - Estende
_shared/authorize.tscom enforcement de MFA (AAL2 ou step-up token) e adiciona testes unitários Deno para os cenários críticos. - Instrumenta rotas críticas no client com
createClientLogger+ propagação de headers e adiciona gates de CI para npm audit baseline e structured logging no client.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| supabase/functions/webhook-alerts-monitor/index.ts | Nova edge function cron para detectar alertas de webhook e enviar eventos ao Sentry. |
| supabase/functions/_shared/authorize.ts | Adiciona opções/fluxo de MFA (AAL2 ou step-up token) ao SSOT de autorização. |
| supabase/functions/tests/authorize-mfa_test.ts | Testes unitários do enforcement de MFA e do decoder de claims usado no fluxo. |
| src/pages/public-approval/usePublicQuoteApproval.ts | Propaga request_id e adiciona logs estruturados nas chamadas à edge quote-public-view. |
| src/hooks/useStepUpAuth.ts | Propaga request_id e adiciona logs estruturados no fluxo step-up (request/password/otp/cancel). |
| src/hooks/useSecretsManager.ts | Adiciona logs estruturados e melhora classificação de sucesso/erro para métricas do secrets-manager. |
| scripts/check-npm-audit.mjs | Novo gate CI para bloquear vulnerabilidades novas acima do threshold vs baseline. |
| scripts/check-client-structured-logging.mjs | Novo gate CI para garantir logger estruturado + propagação de headers em rotas críticas do client. |
| docs/observability/webhook-alerts.md | Documenta thresholds, tags/fingerprint no Sentry e setup de cron/secret. |
| docs/observability/webhook-alerts-spec.sql | Spec “SSOT” das queries de detecção (read-only) para webhook alerts. |
| .security/npm-audit-baseline.json | Baseline inicial para o gate de npm audit. |
| .github/workflows/ci.yml | Troca `npm audit |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * Cron-driven monitor que executa as queries de detecção definidas em | ||
| * docs/observability/webhook-alerts-spec.sql sobre `webhook_delivery_metrics` | ||
| * e dispara eventos Sentry quando um threshold é atingido. | ||
| * |
There was a problem hiding this comment.
O header do arquivo diz que o monitor executa as queries SSOT de docs/observability/webhook-alerts-spec.sql, mas a implementação faz .select(...).gte(...) e agrega em memória. Isso cria divergência (spec SQL pode mudar e o runtime não acompanha). Sugestão: ou implementar a detecção via SQL (view/RPC) baseada no spec, ou ajustar o comentário/docs para refletir que a SSOT é o código TS (e manter o spec SQL como referência apenas).
| * Cron-driven monitor que executa as queries de detecção definidas em | |
| * docs/observability/webhook-alerts-spec.sql sobre `webhook_delivery_metrics` | |
| * e dispara eventos Sentry quando um threshold é atingido. | |
| * | |
| * Cron-driven monitor que consulta `webhook_delivery_metrics` via supabase-js, | |
| * aplica a lógica de detecção/thresholds em código TypeScript e dispara eventos | |
| * Sentry quando um threshold é atingido. | |
| * | |
| * Observação: `docs/observability/webhook-alerts-spec.sql` é mantido como | |
| * referência documental da estratégia de detecção; a fonte de verdade do | |
| * comportamento em runtime deste monitor é a implementação neste arquivo. | |
| * |
| for (const a of alerts) { | ||
| const ok = await sendToSentry(a, requestId); | ||
| if (ok) { | ||
| sent++; | ||
| log.warn("alert_dispatched", { | ||
| alert: a.alert_id, | ||
| source: a.source, | ||
| direction: a.direction, | ||
| severity: a.severity, | ||
| }); | ||
| } else { | ||
| skipped++; | ||
| log.warn("alert_skipped_no_sink", { | ||
| alert: a.alert_id, | ||
| source: a.source, | ||
| direction: a.direction, | ||
| }); | ||
| } |
There was a problem hiding this comment.
sendToSentry() retorna apenas boolean e no loop qualquer false é logado como alert_skipped_no_sink. Isso conflui casos diferentes (DSN ausente, DSN inválida, fetch falhou/429/5xx) e pode mascarar uma pane no sink. Sugestão: retornar um status/enum (ex.: no_dsn | invalid_dsn | http_failed | sent) e logar error quando havia DSN mas o POST falhou, incluindo status/body (c/ truncation).
| @@ -0,0 +1 @@ | |||
| {"$schema":"./baseline.schema.json","generatedAt":null,"threshold":"high","knownAdvisories":[],"notes":"Atualize com UPDATE_BASELINE=1 node scripts/check-npm-audit.mjs após revisar cada CVE."} | |||
There was a problem hiding this comment.
O baseline foi commitado com generatedAt: null e sem a estrutura gerada pelo próprio gate (pretty JSON + timestamp ISO). Como o script já tem UPDATE_BASELINE=1 para gerar o formato canônico, vale atualizar este arquivo usando o script para evitar confusão e garantir que o baseline refletirá exatamente o filtro aplicado (threshold/omit=dev).
| {"$schema":"./baseline.schema.json","generatedAt":null,"threshold":"high","knownAdvisories":[],"notes":"Atualize com UPDATE_BASELINE=1 node scripts/check-npm-audit.mjs após revisar cada CVE."} | |
| { | |
| "$schema": "./baseline.schema.json", | |
| "generatedAt": "2026-04-30T00:00:00.000Z", | |
| "threshold": "high", | |
| "knownAdvisories": [], | |
| "notes": "Atualize com UPDATE_BASELINE=1 node scripts/check-npm-audit.mjs após revisar cada CVE." | |
| } |
| const supabaseUrl = Deno.env.get("SUPABASE_URL"); | ||
| const serviceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY"); | ||
| if (!supabaseUrl || !serviceKey) { | ||
| log.error("monitor_misconfigured", { reason: "missing_service_role" }); | ||
| return new Response(JSON.stringify({ error: "misconfigured" }), { | ||
| status: 500, | ||
| headers: { ...cors, "Content-Type": "application/json" }, | ||
| }); | ||
| } | ||
|
|
||
| const supabase = createClient(supabaseUrl, serviceKey, { | ||
| auth: { persistSession: false }, | ||
| }); |
There was a problem hiding this comment.
Este endpoint não faz nenhum gate de autenticação/segredo (qualquer caller com acesso à URL pode disparar leitura via service_role e potencialmente gerar ruído no Sentry). Como ele usa SUPABASE_SERVICE_ROLE_KEY internamente, recomendo exigir um secret dedicado (ex.: X-Cron-Secret comparado com Deno.env.get("WEBHOOK_ALERTS_MONITOR_SECRET")) e retornar 401/403 se faltar/for inválido, antes de criar o client com service_role.
| return new Response(JSON.stringify({ error: "misconfigured" }), { | ||
| status: 500, | ||
| headers: { ...cors, "Content-Type": "application/json" }, | ||
| }); | ||
| } | ||
|
|
||
| const supabase = createClient(supabaseUrl, serviceKey, { | ||
| auth: { persistSession: false }, | ||
| }); | ||
|
|
||
| let alerts: DetectedAlert[] = []; | ||
| try { | ||
| alerts = await detect(supabase); | ||
| } catch (err) { | ||
| log.error("monitor_detect_failed", { err: String(err) }); | ||
| return new Response(JSON.stringify({ error: "detect_failed" }), { | ||
| status: 500, | ||
| headers: { ...cors, "Content-Type": "application/json" }, | ||
| }); | ||
| } | ||
|
|
||
| let sent = 0; | ||
| let skipped = 0; | ||
| for (const a of alerts) { | ||
| const ok = await sendToSentry(a, requestId); | ||
| if (ok) { | ||
| sent++; | ||
| log.warn("alert_dispatched", { | ||
| alert: a.alert_id, | ||
| source: a.source, | ||
| direction: a.direction, | ||
| severity: a.severity, | ||
| }); | ||
| } else { | ||
| skipped++; | ||
| log.warn("alert_skipped_no_sink", { | ||
| alert: a.alert_id, | ||
| source: a.source, | ||
| direction: a.direction, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| log.info("monitor_ok", { detected: alerts.length, sent, skipped }); | ||
| return new Response( | ||
| JSON.stringify({ detected: alerts.length, sent, skipped, alerts }), | ||
| { status: 200, headers: { ...cors, "Content-Type": "application/json" } }, | ||
| ); |
There was a problem hiding this comment.
O createStructuredLogger expõe log.respond(res) para anexar X-Request-Id e emitir request_end com status/duração; aqui as respostas são retornadas diretamente com new Response(...). Para manter a correlação e o evento final (especialmente em cron/monitoring), envolva os return new Response(...) com return log.respond(...) (inclusive nos early-returns de erro).
| const invokesEdge = /supabase\.functions\.invoke\s*\(/.test(content); | ||
| if (!invokesEdge) continue; // arquivo não chama edge → fora de escopo | ||
|
|
||
| const usesLogger = /createClientLogger\s*\(/.test(content); | ||
| const propagatesHeaders = | ||
| /log\.headers\s*\(\s*\)/.test(content) || | ||
| /REQUEST_ID_HEADER/.test(content); | ||
|
|
||
| if (usesLogger && propagatesHeaders) continue; // ✅ | ||
|
|
There was a problem hiding this comment.
Este gate afirma que “toda invocação” de supabase.functions.invoke(...) deve propagar headers: log.headers(), mas o critério implementado só verifica se o arquivo contém createClientLogger( e log.headers() em QUALQUER lugar. Um arquivo pode ter múltiplas invocações e ainda passar mesmo com algumas chamadas sem headers. Sugestão: iterar por ocorrências de supabase.functions.invoke( e validar por chamada (heurística por bloco/linha) ou pelo menos exigir que TODAS as ocorrências contenham headers: próximo da invocação.
| import { resolve, dirname, relative } from 'node:path'; | ||
| import { fileURLToPath } from 'node:url'; | ||
| import { execSync } from 'node:child_process'; |
There was a problem hiding this comment.
Imports relative e execSync não são usados neste script. Remover evita ruído e ajuda a manter o gate mínimo e fácil de auditar (especialmente por ser um script de CI).
| import { resolve, dirname, relative } from 'node:path'; | |
| import { fileURLToPath } from 'node:url'; | |
| import { execSync } from 'node:child_process'; | |
| import { resolve, dirname } from 'node:path'; | |
| import { fileURLToPath } from 'node:url'; |
| if (parsed && parsed.error && parsed.message?.includes('operation is not supported')) { | ||
| return { ok: false, reason: 'sandbox-unsupported', sandboxUnsupported: true }; | ||
| } |
There was a problem hiding this comment.
A detecção de ambiente “sandbox-unsupported” checa parsed.message, mas no JSON do npm audit --json os detalhes do erro normalmente ficam em parsed.error.summary/parsed.error.detail. Do jeito que está, esse branch tende a nunca disparar e o script pode seguir sem emitir o warning esperado. Sugestão: checar também parsed.error?.summary e parsed.error?.detail (e eventualmente res.stderr) por essas mensagens/códigos (ex.: ENOAUDIT/404).
| if (parsed && parsed.error && parsed.message?.includes('operation is not supported')) { | |
| return { ok: false, reason: 'sandbox-unsupported', sandboxUnsupported: true }; | |
| } | |
| const auditErrorText = [ | |
| parsed?.message, | |
| parsed?.error?.code, | |
| parsed?.error?.summary, | |
| parsed?.error?.detail, | |
| res.stderr, | |
| ] | |
| .filter(Boolean) | |
| .join('\n') | |
| .toLowerCase(); | |
| if ( | |
| parsed?.error && | |
| ( | |
| auditErrorText.includes('operation is not supported') || | |
| auditErrorText.includes('enoaudit') || | |
| ( | |
| auditErrorText.includes('404') && | |
| ( | |
| auditErrorText.includes('audit') || | |
| auditErrorText.includes('not found') || | |
| auditErrorText.includes('not support') | |
| ) | |
| ) | |
| ) | |
| ) { | |
| return { ok: false, reason: 'sandbox-unsupported', sandboxUnsupported: true }; | |
| } |
| 3. **Cron**: registrar via `supabase--insert` em `cron.schedule`: | ||
| ```sql | ||
| select cron.schedule( | ||
| 'webhook-alerts-monitor', | ||
| '*/1 * * * *', | ||
| $$ | ||
| select net.http_post( | ||
| url := '<SUPABASE_URL>/functions/v1/webhook-alerts-monitor', | ||
| headers := jsonb_build_object( | ||
| 'Content-Type','application/json', | ||
| 'Authorization','Bearer <ANON_KEY>' | ||
| ), |
There was a problem hiding this comment.
O exemplo de cron usa Authorization: Bearer <ANON_KEY>, mas ANON_KEY é público e não autentica o caller. Como a edge function usa service_role internamente (bypass RLS) e pode disparar alertas, isso deveria ser protegido com um secret dedicado (header tipo X-Cron-Secret) ou outra forma de autenticação não-pública; atualize o exemplo para refletir esse gate.
| Para alterar thresholds, edite as constantes no topo de | ||
| `webhook-alerts-monitor/index.ts` (SSOT do runtime) e os defaults no | ||
| spec SQL. |
There was a problem hiding this comment.
Aqui o doc diz que o spec SQL tem “defaults” e que o runtime é SSOT no TS; porém a função TS atualmente não executa o spec SQL (faz agregação em memória). Para evitar drift, ou alinhe o runtime para executar o spec, ou ajuste este parágrafo para não sugerir que alterar o spec SQL afetará o comportamento do monitor.
| Para alterar thresholds, edite as constantes no topo de | |
| `webhook-alerts-monitor/index.ts` (SSOT do runtime) e os defaults no | |
| spec SQL. | |
| Para alterar thresholds do monitor, edite as constantes no topo de | |
| `webhook-alerts-monitor/index.ts` (SSOT do runtime). O spec SQL é apenas uma | |
| referência documental no estado atual e deve ser mantido em sincronia | |
| manualmente, sem afetar o comportamento do monitor. |
|
@coderabbitai full review Generated by Claude Code |
|
Your organization has reached its limit of developer seats under the Pro Plan. In order to use the Chat feature, please assign a seat to the PR author after buying additional seats by visiting https://app.coderabbit.ai/login |
|
🚪 Fechando — sync do Lovable, feature descontinuada. Decisão de produto registrada na sessão de 08/05/2026: o Lovable foi descontinuado. Não vamos mais aplicar syncs do Lovable bot porque:
Se algum dos arquivos desse sync for genuinamente útil (ex: novos scripts em — Claude (sessão de housekeeping de PRs, 08/05/2026) |
Documenta a sessão de housekeeping de PRs onde foram: - Criados PRs #99 (cleanup pedidos F1-5.3) e #101 (fix CI) - Fechados #82 e #84 (Lovable obsoleto) - Comentado #83 (recomendação de fechar) - Aplicados 3 fixes do CodeRabbit/Codex/Copilot ao PR #99 - Mapeados 7 PRs Dependabot pendentes de decisão Adiciona: - docs/sessoes/2026-05-08-housekeeping-prs.md (novo, 293 linhas) Relatório completo destinado a próximo Claude pra retomar contexto rapidamente. Inclui TL;DR, estado dos PRs antes/depois, descrição detalhada de cada PR, achados técnicos (8 pegadinhas catalogadas), estado do repo, handoff pra próxima sessão. - Entrada v1.9 no Changelog do AUDITORIA_2026-05-07.md Resumo executivo da sessão com link pro relatório completo.
📋 Descrição
🎯 Tipo de mudança
🌐 Sistemas afetados
🧪 Como testar
✅ Checklist pré-merge
_backup_*_YYYYMMDDse destrutivasconsole.logcom payloads sensíveis🔄 Plano de rollback
🔗 Issues relacionadas