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:
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user