security: SSRF prevention, ownership gaps, host header injection, date validation

- webhooks: block SSRF — reject localhost/127.x/169.254.x/10.x/192.168.x
  URLs at creation and silently skip at dispatch time
- medication-profiles/last-intakes: add family ownership check on babyId
- invite/send-email: build invite URL from NEXTAUTH_URL env only;
  drop req.headers.get("origin") to prevent host header injection
- baby POST: validate birthDate is a real date; add required-fields check
- milk POST: validate storedAt before new Date()
- journal GET: validate date filter before new Date()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 23:33:45 +02:00
parent 432feed88f
commit 1dac9690f5
8 changed files with 91 additions and 5 deletions
+15
View File
@@ -1,6 +1,20 @@
import { createHmac, randomBytes } from "crypto";
import { prisma } from "./prisma";
const PRIVATE_IP_RE =
/^(localhost|127\.|0\.0\.0\.0|::1|10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|169\.254\.)/i;
function isSafeWebhookUrl(raw: string): boolean {
try {
const { protocol, hostname } = new URL(raw);
if (protocol !== "https:" && protocol !== "http:") return false;
if (PRIVATE_IP_RE.test(hostname)) return false;
return true;
} catch {
return false;
}
}
export type WebhookEventType =
| "event.created"
| "growth.created"
@@ -26,6 +40,7 @@ export async function dispatchWebhook(
await Promise.allSettled(
hooks.map(async (hook) => {
if (!isSafeWebhookUrl(hook.url)) return;
const sig = createHmac("sha256", hook.secret).update(body).digest("hex");
await fetch(hook.url, {
method: "POST",