{"id":"GHSA-q567-cr4x-96w4","summary":"Trigger.dev: Blind SSRF via alert-channel webhook","details":"### Summary\n\nA WEBHOOK alert channel stores a user-supplied `url`. When an alert fires (deployment/run failure, error groups), the webapp server (`alertsWorker` -\u003e `DeliverAlertService`) POSTs the HMAC-signed alert payload to that URL via `fetch(webhook.url, ...)`. The URL is never validated against a host allowlist or private-IP/metadata blocklist (a repo-wide search for `169.254`, `isPrivate`, `isLoopback`, `net.isIP`, `ssrf` returns ZERO hits), and the API route's URL field is just `z.string().optional()` (no syntax check at all). So an authenticated tenant can point the webhook at internal infrastructure or `169.254.169.254` and the multi-tenant server fetches it.\n\n### Affected\n`apps/webapp`, HEAD `5d99457` (current main).\n\n### Root cause\n- Source (API route): `app/presenters/v3/ApiAlertChannelPresenter.server.ts` `ApiAlertChannelData.url = z.string().optional()` (no host validation); route `app/routes/api.v1.projects.$projectRef.alertChannels.ts` (PAT-auth).\n- Store: `app/v3/services/alerts/createAlertChannel.server.ts` persists `{url, secret, version}` verbatim.\n- Sink: `app/v3/services/alerts/deliverAlert.server.ts:973` `fetch(webhook.url, {method:\"POST\", headers:{\"x-trigger-signature-hmacsha256\":...}, body:rawPayload})`; identical at `deliverErrorGroupAlert.server.ts:258`. Runs inside the webapp process; `fetch` follows redirects (IMDSv1-via-redirect). No egress guard anywhere.\n\n### Runtime PoC (proven on self-host)\nSeeded a project + STAGING env + a WEBHOOK channel with `url=http://host.docker.internal:7766/...` (an internal address from the server's POV) + a DEPLOYING deployment. Triggered via the public API `POST /api/v1/deployments/deployment_ssrfpoc/fail` -\u003e 200 -\u003e FAILED -\u003e alertsWorker -\u003e the server fetched the listener. Captured:\n```\nPOST /ssrf-via-webhook HTTP/1.1\nhost: host.docker.internal:7766\nx-trigger-signature-hmacsha256: \u003credacted\u003e\nuser-agent: node\n{\"type\":\"alert.deployment.failed\", ...}\n```\nThe webapp server (user-agent: node) made an outbound POST to the attacker-chosen internal URL; 169.254.169.254 / any internal host works identically.\n\n### Severity\n`CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N` ~ Medium. Blind (response not reflected) + POST-only (fixed body = signed alert payload), so it enables internal port/host scanning (delivery success/timing oracle), state-changing POSTs to internal services, and IMDSv1-via-redirect — not arbitrary GET exfiltration. PR:L (authenticated tenant). Egress private-IP/metadata blocking on server-issued webhooks is industry standard; its absence is the defect.\n\n### Suggested fix\nValidate the webhook URL on create (require http(s), resolve host, reject private/link-local/loopback/metadata ranges) AND re-check at fetch time (re-resolve after redirects or disable redirects / pin to resolved public IP). A shared `assertPublicUrl(url)` used by `createAlertChannel` + the two delivery sinks.\n\n### Dup\nDistinct CWE-918 class from the IDOR advisories. FRESH. (The Grav maintainer independently hardened the same webhook-URL-SSRF class in grav-plugin-api commit dfcc947 on 2026-06-26 — corroborating the class is real and fixable.)","modified":"2026-10-02T22:45:03.892857668Z","published":"2026-10-02T22:35:31Z","database_specific":{"nvd_published_at":null,"cwe_ids":["CWE-918"],"severity":"MODERATE","github_reviewed":true,"github_reviewed_at":"2026-10-02T22:35:31Z"},"references":[{"type":"WEB","url":"https://github.com/triggerdotdev/trigger.dev/security/advisories/GHSA-q567-cr4x-96w4"},{"type":"WEB","url":"https://github.com/triggerdotdev/trigger.dev/pull/4199"},{"type":"WEB","url":"https://github.com/triggerdotdev/trigger.dev/commit/34b1a181c2a1d33a53ebab88f84b05f81fea4254"},{"type":"PACKAGE","url":"https://github.com/triggerdotdev/trigger.dev"},{"type":"WEB","url":"https://github.com/triggerdotdev/trigger.dev/releases/tag/v4.5.2"}],"affected":[{"package":{"name":"trigger.dev","ecosystem":"npm","purl":"pkg:npm/trigger.dev"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"4.5.2"}]}],"database_specific":{"last_known_affected_version_range":"\u003c= 4.5.1","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-q567-cr4x-96w4/GHSA-q567-cr4x-96w4.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N"}]}