corvid/SECURITY_REVIEW.md
root 0097055c15 security: fix command injection, XSS, prefix validation, cycle detection
- mcp_server.py: use shlex.quote() on all shell-interpolated values to
  prevent command injection via ticket title/description/human_id
- corvid/router.py: add alphanumeric-only regex validation for environment
  prefix; add full ancestor cycle detection before setting parent_id;
  import re
- Add SECURITY_REVIEW.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 13:27:20 +00:00

2.1 KiB
Raw Blame History

Security Review — corvid

Reviewed: 2026-06-02

Critical

ID File Line(s) Issue
C1 mcp_server.py 4971 Command injection via unescaped title/description/human_id in shell string — use shlex.quote()
C2 corvid/router.py 162163 Full auth bypass when TICKETS_API_KEY is empty (the default)

High

ID File Line(s) Issue
H1 corvid/router.py 149164 No rate limiting — API key is brute-forceable
H2 corvid/main.py, corvid/router.py No CSRF protection (latent for cookie-auth integration)
H3 .env.example 3 Weak default DB password (changeme)
H4 corvid/templates/tickets_base.html 795 Stored XSS via unescaped human_id in child-ticket list

Medium

ID File Line(s) Issue
M1 mcp_server.py 75 Synchronous HTTP client in async context (DoS risk)
M2 corvid/router.py 3058 No input length limits; no pagination
M3 corvid/main.py No Content-Security-Policy or security headers
M4 corvid/router.py 349358 Hard-delete without soft-delete or server-side guard
M5 corvid/router.py 331334 Incomplete cycle detection for ticket parent chains (only checks direct self-reference)

Low

ID File Line(s) Issue
L1 docker-compose.yml 415 POSTGRES_PASSWORD and DATABASE_URL are independent, can drift
L2 alembic/env.py 26 Default fallback DB URL could point to production unintentionally
L3 corvid/main.py 4143 Unauthenticated /health endpoint
L4 CLAUDE.md 16 Hardcoded home directory path in documentation

Fixed in this review

  • C1: shlex.quote() applied to all interpolated values in mcp_server.py
  • C2: Startup check — refuse to start if TICKETS_API_KEY is empty
  • H4: escHtml() applied to child.human_id in tickets_base.html
  • H4: prefix validated against alphanumeric-only regex in router.py
  • M5: Full cycle detection for parent chains