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
+47 -3
View File
@@ -11,11 +11,11 @@
| Severity | Found | Fixed |
|----------|-------|-------|
| CRITICAL | 9 | 9 |
| HIGH | 11 | 10 |
| MEDIUM | 7 | 5 |
| HIGH | 13 | 12 |
| MEDIUM | 10 | 8 |
| LOW | 1 | 0 |
_Updated after second-pass audit (pass 2 of 2)._
_Updated after third-pass audit (pass 3 of 3)._
---
@@ -209,6 +209,50 @@ If `SUPERADMIN_EMAIL` env var is not set, the condition `SUPERADMIN_EMAIL && ses
---
---
## HIGH — Fixed (pass 3)
### 25. SSRF via webhook URL — internal IP/localhost allowed
**Affected:** `webhooks/route.ts` POST (creation) and `lib/webhooks.ts` dispatch
**Problem:** Webhook URLs were accepted without hostname validation. An attacker could register `http://localhost:5432` or `http://169.254.169.254/latest/meta-data/` (AWS metadata endpoint) and cause the server to make requests to internal services, leaking credentials or probing the internal network.
**Fix:** Added `isSafeWebhookUrl()` helper using regex to reject `localhost`, `127.x`, `0.0.0.0`, `::1`, `10.x`, `172.1631.x`, `192.168.x`, `169.254.x`. Applied at both creation (returns 400) and dispatch (skips the hook silently).
---
### 26. `medication-profiles/last-intakes` — missing family ownership check
**Problem:** Accepted any `babyId` without verifying it belongs to the session user's family. Cross-family medication data leak.
**Fix:** Added `prisma.baby.findFirst({ where: { id: babyId, familyId } })` check.
---
## MEDIUM — Fixed (pass 3)
### 27. Host header injection in invite email URL
**Affected:** `invite/send-email/route.ts`
**Problem:** Used `req.headers.get("origin")` to build the invite URL, allowing an attacker to craft a request with `Origin: https://evil.com` and send phishing links to invited users.
**Fix:** Now uses `process.env.NEXTAUTH_URL` exclusively; request Origin header ignored.
---
### 28. `baby/route.ts` POST — birthDate not validated
**Problem:** `new Date(birthDate)` accepted invalid strings silently; also no required-fields check.
**Fix:** Added `!name || !birthDate` check + `isNaN` date validation.
---
### 29. `milk/route.ts` POST — storedAt not validated
### 30. `journal/route.ts` GET — date filter not validated
**Fix:** Added `isNaN(new Date(x).getTime())` guards on both.
---
## MEDIUM — Not applicable (already handled)
### Upload route — extension from MIME type, not filename