The session callbackUrl becomes a server-side fetch target in api.save-to-journal (POSTed with the callback bearer token, and the journal's response is reflected to the caller). The /new query-param loader already validated it, but the programmatic POST /api/sessions entry point — anonymous, since the Planner is stateless — stored it unvalidated. An attacker could make the Planner backend POST to arbitrary hosts, including 169.254.169.254 and other internal targets. - validateFetchUrl now blocks private / loopback / link-local / CGNAT / cloud-metadata hosts (IPv4, IPv6, IPv4-mapped) when an explicit allowlist isn't set. Gated on NODE_ENV=production && !E2E (the requireSecret idiom) so the dev/e2e journal-on-localhost save flow is unaffected. An explicit PLANNER_CALLBACK_ALLOWED_HOSTS still takes precedence and remains the full-closure control (it also stops DNS-name-to-private rebinding, which literal blocking does not). - POST /api/sessions now validates callbackUrl exactly as /new does. - api.save-to-journal re-validates immediately before the fetch (defense in depth: covers sessions persisted before this change and narrows the create→save rebinding window). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| components | ||
| jobs | ||
| lib | ||
| routes | ||
| types | ||
| entry.client.tsx | ||
| entry.server.tsx | ||
| root.tsx | ||
| routes.ts | ||
| styles.css | ||