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

46 lines
2.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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