Files
Momento/_bmad-output/implementation-artifacts/spec-cross-project-audit.md
Antigravity 96e7902f01
Some checks failed
CI / Lint, Unit Tests & Build (push) Failing after 1m22s
CI / Deploy production (on server) (push) Has been skipped
feat: publication IA (magazine/brief/essay) + fixes critique
Publication IA:
- 4 templates (magazine, brief, essay, simple) avec CSS riche
- Rewrite IA (article/exercises/tutorial/reference/mixed)
- Modération avec timeout 12s + fallback safe
- Quotas publish_enhance par tier (basic=2, pro=15, business=100)
- Détection contenu stale (hash)
- Migration DB publishedContent/publishedTemplate/publishedSourceHash

Fixes:
- cheerio v1.2: Element -> AnyNode (domhandler), decodeEntities cast
- _isShared ajouté au type Note (champ virtuel serveur)
- callout colors PDF export: extraction fonction pure testable
- admin/published: guard note.userId null
- Cmd+S fonctionne en mode dialog (pas seulement fullPage)

i18n:
- 23 clés publish* traduites dans les 15 locales
- Extension Web Clipper: 13 locales mise à jour

Tests:
- callout-colors.test.ts (6 tests)
- note-visible-in-view.test.ts (5 tests)
- entitlements.test.ts + byok-entitlements.test.ts: mock usageLog + unstubAllEnvs
- 199/199 tests passent

Tracker: user-stories.md sync avec sprint-status.yaml
2026-06-28 07:32:57 +00:00

145 lines
6.8 KiB
Markdown

---
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'
---
<frozen-after-approval reason="human-owned intent — do not modify unless human renegotiates">
## Intent
**Problem:** The Memento 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.
</frozen-after-approval>
## 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)