diff --git a/AGENTS.md b/AGENTS.md index 4238b02..c7ada46 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -142,11 +142,13 @@ this. When unsure whether a change is contract-affecting, assume it is and check - **Full independent evaluation run 2026-06-13** (evaluator + security-auditor + exerciser + doc-auditor + start9-spec-checker). Report committed at `EVALUATION.md` (`b08e836`); it's overwritten in place each run so re-running gives a reviewable diff. 47/47 tests still pass; server boots clean. Findings triaged into the three buckets below. - **Post-hoc speaker tools remain live**: `meeting-speaker-edits.js` (merge / recluster / repolish + backfill) + the `PATCH/POST /admin/internal-meetings/:id/{merge-speakers,recluster,repolish}` routes; dashboard exposes the controls. -### Work queue — P0/P1 (fix first) +### Work queue — P0/P1 — DONE (2026-06-13, commits `8ad7c54`/`d2caa98`/`3a601e1`) -1. **SSRF on `/relay/transcribe-url` + `/relay/summarize-url`** — `downloadDirect` (`server/routes/transcribe-url.js:99-159`) fetches any caller `media_url` with redirect-follow and no scheme/host allowlist; reachable via a self-chosen `X-Recap-Install-Id` (effectively unauthenticated). Reject non-http(s) + private/loopback/link-local hosts; re-validate per redirect hop. *(evaluator + security-auditor + exerciser — headline risk)* -2. **Billing money-leak: early/mid-cycle renewal resets `monthly_consumed = 0`** — `extendUserTier` → `setUserTier` (`server/credits.js:770→641`) hands back a full monthly allotment for free; a webhook double-fire across restart compounds it. Preserve `monthly_consumed`/`last_renewal_at`, roll only on a true period boundary; add a money-path test (`tier-expiry.test.js:63` asserts only the date today). *(evaluator)* -3. **`multer@1.4.5-lts.2` DoS CVEs** (CVE-2025-47944/47935/48997/7338) — malformed multipart can crash the process. Upgrade to `^2.0.1`, re-test the `file` upload field (`server/package.json:15`). *(security-auditor + exerciser)* +All three P1 items are fixed and committed; suite is green at 57 tests (was 47). None touch the `../recap` client contract (a blocked URL is just a failed-download job; credit accounting is internal; multer is a dep-only bump). + +1. ✅ **SSRF on `/relay/transcribe-url` + `/relay/summarize-url`** — added `server/safe-url.js` (`assertPublicHttpUrl` rejects non-http(s) + private/loopback/link-local/reserved hosts; `safeFetch` follows redirects manually and re-validates each hop). `downloadDirect` routes through it — covers transcribe-url, summarize-url, admin-test-run. Tests: `server/test/safe-url.test.js`. *Residual:* DNS-rebinding TOCTOU (resolve-then-connect) not closed — acceptable for the private box; revisit if exposed. +2. ✅ **Billing money-leak: renewal reset `monthly_consumed = 0`** — `setUserTier` gained a `resetCycle` flag (default true = operator-grant behavior); `extendUserTier` passes `resetCycle:false` for an in-force sub (preserve counter), `true` only for new/lapsed. Monthly resets still happen via `ensureRenewalRollover`. Regression tests added. +3. ✅ **`multer` DoS CVEs** — bumped `^1.4.5-lts.1` → `^2.0.1` (installed 2.1.1); smoke-tested that a malformed multipart now yields a clean 4xx instead of crashing. Committed `server/package-lock.json` so the Dockerfile `npm ci` path pins it. ### Known debt — P2 (accepted for now; fix opportunistically)