--- title: 'Cross-Project Health Audit' type: 'chore' created: '2026-06-20' status: 'done' baseline_commit: '40f30155c2a0f118ca41c8226992035bcab55a46' context: - '{project-root}/AGENTS.md' - '{project-root}/CLAUDE.md' - '{project-root}/memento-note/docs/admin-billing-quotas-guide.md' --- ## Intent **Problem:** The Momento workspace spans multiple deployable and reference projects (memento-note, mcp-server, extension, mobile, monitoring, CI/CD, prototypes) without a consolidated security and quality audit; several P0 issues were found (open uploads, MCP auth bypass, fail-open quotas). **Approach:** Deliver a prioritized cross-project audit report (bugs + improvements) and, upon approval, remediate Phase 1 P0 security items first — app uploads/auth, MCP tenant isolation, extension Store readiness — before UX/i18n/prototype parity work. ## Boundaries & Constraints **Always:** No destructive DB commands; backup via `dump-db.sh` before any schema change; i18n via 15 locales when touching UI; follow prototype `architectural-grid` for UX gaps; fail-closed for billing quotas in production. **Ask First:** Whether to fail-open vs fail-closed quotas during Redis outage; whether MCP `x-user-id` should be removed entirely or restricted to internal network; Chrome Web Store host_permissions scope; archiving `architectural-grid1` vs keeping both prototypes. **Never:** `prisma migrate reset`, `db push --force-reset`, `pg_dump --clean` in deploy scripts; exposing API keys in client bundles; shipping extension with dev `host_permissions`; implementing all audit items in one PR. ## Code Map - `memento-note/app/api/uploads/[...path]/route.ts` -- uploads served without session check - `memento-note/app/api/image-proxy/route.ts` -- open SSRF proxy - `memento-note/lib/entitlements.ts` -- Redis fail-open on AI quotas - `memento-note/auth.config.ts` -- `/insights`, `/graph`, `/revision`, `/support` not gated - `memento-note/context/notebooks-context.tsx` -- broken AI toast note move (no home refresh) - `mcp-server/index-sse.js` -- `x-user-id` header auth bypass - `mcp-server/tools.js` -- missing userId filter + `ensureUserId()` first-user fallback - `memento-note/extension/dist-chrome-store/` -- missing PNG icons, wide host_permissions - `memento-mobile/app.json` -- references missing `assets/` - `monitoring/metrics-token` -- placeholder secret committed - `scripts/deploy-prod.sh` -- `pg_dump --clean`, full Docker build fallback - `docker-compose.yml` -- MCP/Ollama ports bound 0.0.0.0 ## Tasks & Acceptance **Execution (Phase 1 — P0 security only, if approved):** - [x] `memento-note/app/api/uploads/[...path]/route.ts` -- require auth + verify note ownership for file path -- prevent private attachment leak - [x] `memento-note/app/api/image-proxy/route.ts` -- require auth + domain allowlist -- close SSRF/abuse - [x] `memento-note/lib/entitlements.ts` -- fail-closed when Redis unavailable in production -- align with billing policy - [x] `memento-note/auth.config.ts` -- add `/insights`, `/graph`, `/revision`, `/support` to protected routes -- match API 401 behavior - [x] `mcp-server/index-sse.js` -- remove or restrict `x-user-id`; require API key on all tool paths -- close identity spoofing - [x] `mcp-server/tools.js` -- always scope queries to authenticated userId; remove `ensureUserId()` fallback -- multi-tenant isolation - [x] `docker-compose.yml` -- bind MCP `127.0.0.1:3001:3001` -- reduce network exposure **Acceptance Criteria:** - Given an unauthenticated request, when GET `/api/uploads/notes/{uuid}.png`, then response is 401/403 - Given Redis down in production, when AI quota check runs, then request is denied (not unlimited) - Given unauthenticated browser, when navigating to `/insights`, then redirect to login - Given MCP request with only `x-user-id` header and no API key, when tool invoked, then 401 - Given authenticated user A, when MCP lists notes, then only user A notes returned ## Spec Change Log ## Design Notes Full audit by project (2026-06-20): **memento-note CRITICAL:** open uploads; image-proxy SSRF; fail-open quotas; middleware gaps; XSS via `dangerouslySetInnerHTML` (published pages, peek, SVG gallery); weak mobile HMAC; broken AI toast note move. **mcp-server CRITICAL:** `x-user-id` spoofing; null userId returns all users' data; port 3001 public; Prisma schema drift vs main app. **extension CRITICAL:** Store build missing PNG icons; `host_permissions` too broad for CWS. **mobile CRITICAL:** missing assets folder; NativeWind configured but not installed. **monitoring CRITICAL:** placeholder metrics token in git; Grafana on 0.0.0.0:3002. **CI/CD CRITICAL:** manual deploy triggers full Docker build (fails on prod); `pg_dump --clean` in deploy script. **prototypes MEDIUM:** Gemini API key in Vite client bundle; port 3000 conflict; grid vs grid1 divergence. **Deferred phases:** P1 insights i18n + GraphKnowledgeMap; P2 editor drag-handle migration; P3 mobile EAS/CI; P4 monitoring alert cleanup. ## Verification **Commands:** - `cd memento-note && npm run test:unit` -- expected: pass (no regressions on touched modules) - Manual curl unauthenticated upload URL -- expected: 401/403 after Phase 1 **Manual checks:** - Logged-out visit to `/insights` redirects to login - MCP tool call without API key returns 401 ## Suggested Review Order **Upload access control** - Ownership check before serving note attachments; published notes stay public [`route.ts:30`](../../memento-note/app/api/uploads/[...path]/route.ts#L30) - Prisma lookup tying filename to note content/images [`upload-access.ts:4`](../../memento-note/lib/upload-access.ts#L4) **SSRF / image proxy** - Session gate + blocked private hosts on server-side fetch [`route.ts:6`](../../memento-note/app/api/image-proxy/route.ts#L6) - Shared SSRF hostname denylist [`ssrf-guard.ts:2`](../../memento-note/lib/ssrf-guard.ts#L2) **Billing quotas fail-closed** - Production denies when Redis is unavailable [`entitlements.ts:88`](../../memento-note/lib/entitlements.ts#L88) - Routes return 503 instead of silent fail-open [`route.ts:81`](../../memento-note/app/api/chat/route.ts#L81) **Auth middleware** - Protect insights, graph, revision, support routes [`auth.config.ts:30`](../../memento-note/auth.config.ts#L30) **MCP multi-tenant isolation** - API key only; x-user-id removed [`index-sse.js:291`](../../mcp-server/index-sse.js#L291) - Mandatory userId on all tool handlers [`tools.js:490`](../../mcp-server/tools.js#L490) **Infrastructure** - MCP port bound to localhost only [`docker-compose.yml:130`](../../docker-compose.yml#L130) **Tests** - Fail-open dev vs fail-closed prod entitlements [`entitlements.test.ts:145`](../../memento-note/tests/unit/entitlements.test.ts#L145)