Section 14 — Application Security
Companion to
musehub-production-readiness-checklist.md.
⚠️ Finding — /_debug/memory is exposed unconditionally in production
musehub/main.py:411 registers /_debug/memory (returns live RSS + tracemalloc top allocations)
without the if settings.debug: guard that correctly protects /docs and /redoc just below
it in the same file. Anyone who can reach the instance can hit this endpoint in production today —
no auth check, no debug-mode gate. It leaks internal memory layout information, which is a minor
info-disclosure risk on its own, but the pattern (one debug endpoint correctly gated, another
not) suggests it was added later and missed the convention. Recommend gating it behind
settings.debug the same way /docs//redoc are, or behind an MSign-authenticated admin check
if it needs to stay available in production for real debugging.
What's already solid — confirmed by code review
- No SQL injection surface found. No raw string-formatted SQL anywhere in
musehub/(grepped forf"..."/.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) inmusehub/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 = FalseinSettings), and/docs//redocare correctly registered onlyif settings.debug:— Swagger/ReDoc won't leak in production. - MSign auth is well-implemented for privileged actions:
musehub/api/routes/mcp.pyrequires 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 (
slowapilimits, 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 alongsidedebug=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
- Fix
/_debug/memory's missing guard — small, fast, real fix. - Review
musehub/worker.py's outbound Cloudflare Worker calls for SSRF exposure specifically. - Confirm MSign's timestamp/replay-window logic (
ts=...in the signature) actually prevents replay attacks, not just informs logging. - Do a dedicated OWASP-oriented pass closer to launch (Section 17), building on this section's findings rather than starting cold.