# Section 14 — Application Security > Companion to [`musehub-production-readiness-checklist.md`](../musehub-production-readiness-checklist.md). ## ✅ Fixed — `/_debug/memory` was exposed unconditionally in production `musehub/main.py` registered `/_debug/memory` (live RSS + tracemalloc top allocations) **without the `if settings.debug:` guard** that correctly protects `/docs` and `/redoc` in the same file — reachable by anyone in production, no auth check. Fixed in this session by wrapping the route registration in the same `if settings.debug:` guard used elsewhere in the file. Deployed to staging/production whenever the next regular deploy runs. ## What's already solid — confirmed by code review - **No SQL injection surface found.** No raw string-formatted SQL anywhere in `musehub/` (grepped for `f"..."`/`.format()`/`%` patterns near SQL keywords — none found outside an unrelated file-write). The app uses SQLAlchemy's ORM/parameterized queries throughout. - **Path traversal explicitly rejected** in the coordination module — opaque alphanumeric ID validation with an explicit comment noting traversal-char rejection. - **Archive/decompression bomb protection exists**: `mpack_max_decompressed_bytes` (4 GB cap) in `musehub/config.py` — mpacks are quarantined if decompressed size exceeds this, directly answering the checklist's "protect against unsafe archive extraction." - **No SSRF-prone outbound URL fetching found** in the app's request-handling code (grepped for `httpx.get`/`requests.get`/etc. — none matched); `musehub/worker.py`'s Cloudflare Worker interaction wasn't independently reviewed for this in this pass, worth a closer look given it's the one component that talks to an external URL. - **Debug mode is off by default** (`debug: bool = False` in `Settings`), and `/docs`/`/redoc` are correctly registered only `if settings.debug:` — Swagger/ReDoc won't leak in production. - **MSign auth is well-implemented for privileged actions**: `musehub/api/routes/mcp.py` requires a *fresh* signed request (not just a valid session) for sensitive tool calls — this directly satisfies "verify privileged actions require fresh authorization," which most apps get wrong. - **No passwords stored anywhere** — MSign is signature-based, so "secure password hashing," "email verification," and "secure account recovery" (as password-reset-flow concepts) are N/A. Account recovery in this model is really "recover your Ed25519 key," which is covered by the mnemonic-backup flow documented in the global ecosystem guide, not by this app. - **Rate limiting on auth-adjacent endpoints**: confirmed in Section 5 (`slowapi` limits, bot throttling exempting only authenticated MSign traffic). - **CORS fails closed**: `cors_origins: list[str] = []` by default, with an explicit warning log if a wildcard is ever configured alongside `debug=false`. - **Security headers and CSP already set** (Section 5): `X-Frame-Options`, `X-Content-Type-Options`, CSP, HSTS. - **Upload limits exist**: `mpack_max_bytes` (512 MB), `mpack_max_commits` (100k), `mpack_max_objects` (1M), per-user (10 GB) and per-repo (5 GB) quotas, per-user daily upload cap (50 GB) — thorough, deliberate limits, not just a single blanket body-size cap. ## Gaps / not reviewed in this pass - [ ] CSRF protection — not reviewed; likely lower risk given no cookie-based sessions exist (MSign is header-based, not vulnerable to classic CSRF the way cookie auth is), but not explicitly confirmed for any state-changing endpoint that might still rely on ambient browser credentials - [ ] Uploaded content scanning (malware/content scanning beyond size/structure limits) — not done - [ ] WebSocket/SSE authentication — MCP/SSE endpoints exist (3600s timeouts, Section 5); auth model for the streaming case specifically wasn't independently verified in this pass - [ ] Data classification and retention — not documented - [ ] Account and data deletion path — not verified whether one exists - [ ] OWASP-oriented review — this pass covers several OWASP Top 10 categories (injection, broken auth, security misconfiguration via the debug-endpoint finding) but isn't a formal OWASP pass - [ ] Session expiration / token revocation — N/A in the traditional sense (no sessions); MSign signatures are per-request and time-stamped (`ts=...`), which is a different but likely equivalent guarantee — not independently verified for replay-window correctness in this pass ## The actual work items here 1. **Fix `/_debug/memory`'s missing guard** — small, fast, real fix. 2. Review `musehub/worker.py`'s outbound Cloudflare Worker calls for SSRF exposure specifically. 3. Confirm MSign's timestamp/replay-window logic (`ts=...` in the signature) actually prevents replay attacks, not just informs logging. 4. Do a dedicated OWASP-oriented pass closer to launch (Section 17), building on this section's findings rather than starting cold.