fix(edge): restore schemas.ts after Lovable truncation#91
Conversation
The schemas.ts shared by 27 edge functions was truncated by Lovable's 'Changes' commit (dbcdbe1) on 2026-05-06, going from 329 lines (15.326 bytes, 36+ schemas) to 35 lines (1.180 bytes, 2 schemas). This left 25 of 27 consuming edge functions with broken imports (symbols missing). Restore the last known good version (da8ecaf from 2026-04-16) which contains all 29 symbols imported by consumers. URL fix: zod import goes from deno.land/x/zod@v3.22.4 (residual) to esm.sh/zod@3.23.8 (consistent with other edge functions). Source commit: da8ecaf (Apr 16, 2026) Coverage: 29/29 imported symbols across 27 edge functions Diff: +318 / -24 lines
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughExpansão de um arquivo mínimo de schemas Zod em biblioteca compartilhada com 30+ schemas novos. Atualiza dependência Zod, re-exporta ChangesExpansão Centralizada de Schemas de Validação
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutos Pontos Críticos para Revisão🔴 Segurança & Validação:
🟡 Integração:
🟢 Performance:
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 79adbfb676
ℹ️ 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".
|
|
||
| // ─── Chatbot L1 ────────────────────────────────────────────── | ||
| export const ChatbotL1Schema = z.object({ | ||
| contactId: z.string().uuid("contactId must be a valid UUID"), |
There was a problem hiding this comment.
Allow chatbot health-check payloads through validation
This validator now requires contactId to be a UUID, but the existing settings health check invokes chatbot-l1 with body: { contactId: 'test', message: 'Olá, teste de conexão', connectionId: 'test' } in src/components/settings/ChatbotL1Config.tsx:234-236. In that UI path the request will be rejected by parseBody before the function can report connectivity, so the “Testar Conexão” button always shows an error even when the edge function is reachable.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
This PR restores the previously-truncated shared Edge Functions validation module (supabase/functions/_shared/schemas.ts), reintroducing the full set of Zod schemas and helpers consumed across many Supabase Edge Functions, and aligns the Zod import URL with the rest of the project.
Changes:
- Restores the complete set of shared Zod schemas (AI, ElevenLabs, alerts, bridges, etc.) and helper exports that were removed by truncation.
- Re-exports
zand reinstates common helpers (UUIDSchema,EmailSchema,SafeStringSchema,parseBody) for consistent input validation across functions. - Switches Zod import back to
https://esm.sh/zod@3.23.8to match existing project usage.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export const AiSuggestReplySchema = z.object({ | ||
| messages: z.array(MessageSchema).max(50).optional(), | ||
| contactName: z.string().max(200).optional().default('Cliente'), | ||
| contactId: z.string().uuid().optional().nullable(), | ||
| context: z.string().max(500).optional(), |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with 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.
Inline comments:
In `@supabase/functions/_shared/schemas.ts`:
- Around line 256-264: A schema atual aceita actions sem exigir os campos
mínimos por operação; refatore ExternalDbBridgeSchema para usar uma união
discriminada baseada em action (por exemplo via z.discriminatedUnion or z.union)
e criar variantes específicas para 'select' (requere table plus opcional
filters/limit/offset/countMode), 'rpc' (requere rpc e params), 'insert' (requere
table e params/data), 'update' (requere table e params e/ou match) e 'delete'
(requere table e match), garantindo que cada variant valide os campos
necessários (table, rpc, params, match, etc.); aplique a mesma mudança para
ExternalDbProxySchema (as variantes nas linhas 295-315) para evitar validação
permissiva que deixa erros aparecerem só em runtime ou permite operações
destrutivas sem campos obrigatórios.
- Around line 93-99: The audioUrl field in TranscribeAudioSchema currently only
checks URL format (z.string().url()) and must be restricted to HTTPS and
optionally to an allowlist of domains; update the audioUrl validator on
TranscribeAudioSchema (audioUrl) to first validate URL format, then .refine(...)
to require it starts with "https://" (or check new URL(value).protocol ===
"https:"), and optionally add a hostname allowlist check (parse new
URL(value).hostname and assert it is in the allowedDomains array) so internal
hosts are rejected; apply the same change to the other URL fields mentioned (the
schemas around lines 102-114) using the same pattern and clear error messages.
- Around line 15-20: O MessageSchema está permitindo objetos vazios (por exemplo
{}), o que faz com que entradas sem conteúdo ainda satisfaçam os .min(5) em
AiConversationAnalysisSchema e AiConversationSummarySchema; corrija
MessageSchema tornando o campo content obrigatório e proibindo string vazia (por
exemplo trocar content: z.string().max(5000).optional() por uma string não-vazia
com min(1) e max(5000)), mantendo outros campos opcionais conforme necessário,
para que mensagens sem texto não sejam aceitas.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 6e544eee-77e1-4875-b21f-d9ddd436c943
📒 Files selected for processing (1)
supabase/functions/_shared/schemas.ts
| export const MessageSchema = z.object({ | ||
| sender: z.string().max(50).optional(), | ||
| content: z.string().max(5000).optional(), | ||
| created_at: z.string().optional(), | ||
| message_type: z.string().max(50).optional(), | ||
| }); |
There was a problem hiding this comment.
MessageSchema está aceitando mensagens vazias.
Do jeito que está, {} passa e ainda conta para o .min(5) de AiConversationAnalysisSchema e AiConversationSummarySchema. Isso deixa payload sem conteúdo entrar nas rotas de IA e enfraquece a validação no ponto mais compartilhado.
Diff sugerido
export const MessageSchema = z.object({
sender: z.string().max(50).optional(),
content: z.string().max(5000).optional(),
created_at: z.string().optional(),
message_type: z.string().max(50).optional(),
-});
+}).refine(
+ (message) => Boolean(message.content?.trim() || message.message_type),
+ { message: "Message must include content or message_type" },
+);🤖 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 `@supabase/functions/_shared/schemas.ts` around lines 15 - 20, O MessageSchema
está permitindo objetos vazios (por exemplo {}), o que faz com que entradas sem
conteúdo ainda satisfaçam os .min(5) em AiConversationAnalysisSchema e
AiConversationSummarySchema; corrija MessageSchema tornando o campo content
obrigatório e proibindo string vazia (por exemplo trocar content:
z.string().max(5000).optional() por uma string não-vazia com min(1) e
max(5000)), mantendo outros campos opcionais conforme necessário, para que
mensagens sem texto não sejam aceitas.
| export const TranscribeAudioSchema = z.object({ | ||
| audioUrl: z.string().url("Invalid audio URL").max(2048), | ||
| messageId: z.string().max(100).optional(), | ||
| languageCode: z.enum(ALLOWED_LANGUAGES).optional().default('por'), | ||
| enableDiarization: z.boolean().optional().default(false), | ||
| tagAudioEvents: z.boolean().optional().default(true), | ||
| }); |
There was a problem hiding this comment.
As URLs remotas precisam ser restringidas.
z.string().url() só valida formato. Para functions que baixam áudio/imagem, isso permite apontar para hosts internos ou metadata se o handler fizer fetch() direto nesses campos. Pelo menos force https:; idealmente use allowlist dos domínios esperados.
Diff sugerido
+const RemoteAssetUrlSchema = z.string().url().max(2048).refine((value) => {
+ const url = new URL(value);
+ return url.protocol === "https:";
+}, "Only HTTPS URLs are allowed");
+
export const TranscribeAudioSchema = z.object({
- audioUrl: z.string().url("Invalid audio URL").max(2048),
+ audioUrl: RemoteAssetUrlSchema,
messageId: z.string().max(100).optional(),
languageCode: z.enum(ALLOWED_LANGUAGES).optional().default('por'),
enableDiarization: z.boolean().optional().default(false),
tagAudioEvents: z.boolean().optional().default(true),
});
export const ClassifyAudioMemeSchema = z.object({
- audio_url: z.string().url().max(2048).optional().nullable(),
+ audio_url: RemoteAssetUrlSchema.optional().nullable(),
file_name: z.string().max(500).optional().nullable(),
});
export const ClassifyEmojiSchema = z.object({
- image_url: z.string().url().max(2048).optional().nullable(),
+ image_url: RemoteAssetUrlSchema.optional().nullable(),
file_name: z.string().max(500).optional().nullable(),
});
export const ClassifyStickerSchema = z.object({
- image_url: z.string().url().max(2048).optional().nullable(),
+ image_url: RemoteAssetUrlSchema.optional().nullable(),
});Also applies to: 102-114
🤖 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 `@supabase/functions/_shared/schemas.ts` around lines 93 - 99, The audioUrl
field in TranscribeAudioSchema currently only checks URL format
(z.string().url()) and must be restricted to HTTPS and optionally to an
allowlist of domains; update the audioUrl validator on TranscribeAudioSchema
(audioUrl) to first validate URL format, then .refine(...) to require it starts
with "https://" (or check new URL(value).protocol === "https:"), and optionally
add a hostname allowlist check (parse new URL(value).hostname and assert it is
in the allowedDomains array) so internal hosts are rejected; apply the same
change to the other URL fields mentioned (the schemas around lines 102-114)
using the same pattern and clear error messages.
| export const ExternalDbBridgeSchema = z.object({ | ||
| action: z.enum(['select', 'rpc', 'insert', 'update', 'delete']), | ||
| table: z.string().max(100).optional(), | ||
| rpc: z.string().max(100).optional(), | ||
| params: z.record(z.unknown()).optional(), | ||
| limit: z.number().int().min(1).max(1000).optional(), | ||
| offset: z.number().int().min(0).optional(), | ||
| countMode: z.string().max(20).optional(), | ||
| }); |
There was a problem hiding this comment.
As operações de DB externa não estão validadas por action.
Hoje payloads como { action: "delete" }, { action: "rpc" } ou { action: "update", table: "x" } passam na schema mesmo sem os campos mínimos da operação. Em proxy/bridge de banco isso troca erro de validação por erro em runtime — e, no caso destrutivo, deixa a segurança depender de defaults do handler.
Direção de ajuste
-export const ExternalDbBridgeSchema = z.object({
- action: z.enum(['select', 'rpc', 'insert', 'update', 'delete']),
- table: z.string().max(100).optional(),
- rpc: z.string().max(100).optional(),
- params: z.record(z.unknown()).optional(),
- limit: z.number().int().min(1).max(1000).optional(),
- offset: z.number().int().min(0).optional(),
- countMode: z.string().max(20).optional(),
-});
+export const ExternalDbBridgeSchema = z.discriminatedUnion('action', [
+ z.object({
+ action: z.literal('select'),
+ table: z.string().max(100),
+ limit: z.number().int().min(1).max(1000).optional(),
+ offset: z.number().int().min(0).optional(),
+ countMode: z.string().max(20).optional(),
+ }),
+ z.object({
+ action: z.literal('rpc'),
+ rpc: z.string().max(100),
+ params: z.record(z.unknown()).optional(),
+ }),
+ z.object({
+ action: z.literal('insert'),
+ table: z.string().max(100),
+ params: z.record(z.unknown()),
+ }),
+ z.object({
+ action: z.literal('update'),
+ table: z.string().max(100),
+ params: z.record(z.unknown()),
+ }),
+ z.object({
+ action: z.literal('delete'),
+ table: z.string().max(100),
+ // exigir critério/filtro aqui
+ }),
+]);A mesma ideia vale para ExternalDbProxySchema: tornar action obrigatório e exigir table/rpc/data/match|filters conforme cada variante.
Also applies to: 295-315
🤖 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 `@supabase/functions/_shared/schemas.ts` around lines 256 - 264, A schema atual
aceita actions sem exigir os campos mínimos por operação; refatore
ExternalDbBridgeSchema para usar uma união discriminada baseada em action (por
exemplo via z.discriminatedUnion or z.union) e criar variantes específicas para
'select' (requere table plus opcional filters/limit/offset/countMode), 'rpc'
(requere rpc e params), 'insert' (requere table e params/data), 'update'
(requere table e params e/ou match) e 'delete' (requere table e match),
garantindo que cada variant valide os campos necessários (table, rpc, params,
match, etc.); aplique a mesma mudança para ExternalDbProxySchema (as variantes
nas linhas 295-315) para evitar validação permissiva que deixa erros aparecerem
só em runtime ou permite operações destrutivas sem campos obrigatórios.
| export const SafeStringSchema = (maxLen = 10000) => z.string().max(maxLen).transform(s => s.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '').trim()); | ||
|
|
||
| // ─── AI function schemas ───────────────────────────────────── | ||
| export const MessageSchema = z.object({ |
Decisão de mergeStatus dos checks
CodeRabbit Pro — 3 findings 🟠 Major (não bloqueantes)Os 3 findings já estavam presentes na versão estável
AçãoMergeio agora — escopo deste PR é apenas restaurar o estado funcional das 27 edge functions (de 7% para 100% de cobertura de imports). Os 3 hardenings ficam como backlog dedicado: PR-FUTURO-E (Schemas Hardening). |
Atende finding 🟠 Major do CodeRabbit no PR #91: MessageSchema aceitava {} vazio, deixando entrar payload sem conteúdo nas rotas de IA via cascata em AiConversationAnalysisSchema e AiConversationSummarySchema. Mudança: - sender: agora obrigatório, .min(1) (não-vazio) - content: agora obrigatório (campo presente), .max(5000) mantido, mas SEM .min(1) — content vazio é legítimo em produção (16.9% das mensagens reais: imagens/áudios/documentos sem caption) Validado em 23 cenários (stress test exaustivo) + cruzamento com 672.003 mensagens reais do banco. Refs: PR #91 review comment r3210444961 Source: PR-FUTURO-E1 em /workspace/notes/pr-futuro-e-schemas-hardening.md
* harden(edge): RemoteUrlSchema anti-SSRF (E2) Atende finding 🟠 Major do CodeRabbit no PR #91: URLs remotas usavam apenas z.string().url() que valida formato mas permite hosts internos, criando risco SSRF nas edge functions que fazem fetch direto (ai-transcribe-audio linha 46) ou que enviam URL para serviços externos que fazem fetch (classify-audio-meme/emoji/ sticker -> Lovable AI Gateway / OpenAI Vision). Mudanças: - Novo helper RemoteUrlSchema com proteções: * HTTPS-only (rejeita http/ftp/data/javascript/file) * Bloqueia IPv4 privados (10/8, 127/8, 169.254/16, 172.16/12, 192.168/16) * Bloqueia IPv6 loopback/link-local/ULA (::1, fe80::/10, fc00::/7) * Bloqueia hostnames reservados (localhost, *.local, *.internal) * Max 2048 chars - Aplicado em 4 schemas: * TranscribeAudioSchema.audioUrl * ClassifyAudioMemeSchema.audio_url (preserva .optional().nullable()) * ClassifyEmojiSchema.image_url * ClassifyStickerSchema.image_url Validado em 41 cenários de stress-test cobrindo Supabase Storage, CDNs, S3, Cloudfront, AWS/GCP/DigitalOcean metadata endpoints, IPv6, Punycode, casos exóticos (user@host bypass, FTP, javascript:, data:). Refs: PR #91 review comment r3210444970 Source: PR-FUTURO-E2 em /workspace/notes/pr-futuro-e-schemas-hardening.md * fix(edge): RemoteUrlSchema regex IPv6 não bloqueia FQDNs (E2 fixup) CodeRabbit Pro identificou 2 bugs no commit anterior: 1. host.startsWith('fc') / startsWith('fd') bloqueava FQDNs legítimos começando com fc/fd (ex: fcdn.com, fdic.gov, fc-paris.fr). Fix: aplicar regra IPv6 só quando host contém ':' (literal IPv6). 2. host.startsWith('fe80:') cobria apenas /16 (fe80..fe8f), perdendo o range completo /10 do link-local (fe80..febf). Fix: regex /^fe[89ab][0-9a-f]?:/ cobre fe80..febf. Adicional: documentado que schema validation NÃO previne DNS rebinding nem redirects pós-fetch. Defesa completa requer DNS re-check, controle de redirect, ou outbound proxy — tracked como follow-up arquitetural. Validado em 23 cenários adicionais cobrindo FQDNs com prefixos fc/fd/fe e todo o range link-local fe80::/10. Refs: PR #93 review comments coderabbit no chore/schemas-hardening-e2
…3) (#94) Atende finding 🟠 Major do CodeRabbit no PR #91: ExternalDbBridgeSchema aceitava payloads incompletos como { action: 'delete' } sem table/match — schema permitia DELETE sem condição WHERE chegar no runtime, dependendo apenas da validação manual em external-db-bridge/index.ts:146. Mudanças (defense in depth — shape preservada para compatibilidade): - 6 refines per-action no schema: * SELECT/INSERT/UPDATE/DELETE: table não-vazia obrigatória * RPC: rpc name não-vazio obrigatório * DELETE: params.match não-vazio obrigatório (proteção crítica contra DELETE table-wide acidental) * UPDATE: params.match não-vazio + params.values obrigatórios (proteção crítica contra UPDATE table-wide) * INSERT: params com rows[] ou campos diretos obrigatório Decisão de design — refines em vez de discriminatedUnion: discriminatedUnion mudaria o shape do schema e poderia quebrar callers desconhecidos. Refines mantêm shape compatível, apenas adicionando validação. Banco mostra 0 chamadas reais ao bridge nos últimos 30 dias (telemetria 'web_vital' vai direto via client-observability, não via bridge), tornando o risco de quebrar caller existente praticamente nulo. Validado em 34 cenários de stress-test cobrindo: - DELETE sem match (5 variantes: undefined, null, {}, string, array) - UPDATE sem match/values (6 variantes) - INSERT sem params/rows/fields (4 variantes) - Casos válidos (SELECT, RPC, INSERT, UPDATE, DELETE com payload bom) - Casos absurdos (action inválida, body vazio, null direto) Refs: PR #91 review comment r3210444976 Source: PR-FUTURO-E3 em /workspace/notes/pr-futuro-e-schemas-hardening.md
Contexto
O arquivo
supabase/functions/_shared/schemas.ts, compartilhado por 27 edge functions consumidoras, foi truncado em 2026-05-06 pelo commitdbcdbe112("Changes") do Lovable.parseBody,UUIDSchema,EmailSchema,SafeStringSchema)parseBody)Resultado: 25 das 27 edge functions ficaram com
importquebrado (símbolo ausente).O que este PR faz
Restaura o conteúdo da última versão estável do arquivo:
da8ecafc8(2026-04-16).Diff: +318 / -24 linhas (1 arquivo).
Cobertura validada (29/29 símbolos importados)
Edge functions afetadas (27)
ai-auto-tag,ai-churn-analysis,ai-classify-tickets,ai-conversation-analysis,ai-conversation-summary,ai-enhance-message,ai-proxy,ai-suggest-reply,ai-transcribe-audio,approve-password-reset,chatbot-l1,classify-audio-meme,classify-emoji,classify-sticker,detect-new-device,elevenlabs-dialogue,elevenlabs-sfx,elevenlabs-tts,elevenlabs-tts-stream,elevenlabs-voice-design,external-db-bridge,send-rate-limit-alert,send-scheduled-report,sentiment-alert,sicoob-bridge,sicoob-bridge-reply,webauthn.Bonus: ajuste da URL do zod
A versão atual em main importa de
https://deno.land/x/zod@v3.22.4/mod.ts(resíduo da truncação).Outras edge functions do projeto usam
https://esm.sh/zod@3.23.8.A versão restaurada também usa
esm.sh@3.23.8, alinhando o schemas com o resto do projeto.Risco
🟢 Zero. Apenas restaura conteúdo conhecido (já rodou em produção de 16/abr a 06/mai).
Nenhum código consumidor precisa mudar — todos os imports voltam a resolver.
Plano de validação pós-merge
Source
da8ecafc8(16/abr/2026, "Changes")chore/restore-schemas-shared79adbfb67Summary by CodeRabbit
Tarefas Administrativas