mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
POST /api/webhooks/[id]/test was refactored in PR #2703 to expose full diagnostics — the new testFetch helper performed fetch(webhook.url) without calling parseAndValidatePublicUrl() and returned the first 2 KB of the upstream response as responseBody. Webhook create/update only validated the URL with z.string().min(1).max(2000), so an internal URL could be persisted and probed. Risk: a holder of a manage-scope API key (delegated dashboard admin) could register http://127.0.0.1:20128/..., http://169.254.169.254/... or any RFC1918 endpoint, call /test, and read the upstream body back in the JSON response — internal admin payloads, loopback services, cloud-metadata IAM credentials on cloud deployments. Fix: - testFetch now calls parseAndValidatePublicUrl(url) before fetch(), matching deliverRaw/deliverWebhook in webhookDispatcher.ts. Errors fall through the existing catch and surface as { delivered:false, status:0, responseBody:"", error:"Blocked private or local provider URL" }. - createWebhookSchema.superRefine validates url via parseAndValidatePublicUrl for kind ∈ {custom, slack, discord}. Telegram is exempt because url there is a Telegram chat_id, not an HTTP URL. - PUT /api/webhooks/[id] resolves the effective kind (payload or stored) and runs the same guard before persisting a non-telegram URL change. Also includes an unrelated Codex 'Import auth' button on the provider detail page that was already staged. Tests: tests/unit/api/webhooks/webhook-url-ssrf-guard.test.ts (9 cases) covers loopback, 169.254/16, RFC1918, embedded credentials, file://, public HTTPS happy-path, telegram chat_id non-rejection, PUT flip to loopback, and defense-in-depth on /test against pre-persisted bad rows.