2.8 KiB
2.8 KiB
Security Review — corvid
Generated: 2026-07-13 (automated authorized audit)
Supersedes the prior manual review dated 2026-06-02.
Scope
All tracked files (git ls-files) of corvid — a FastAPI ticketing service with an
MCP server (corvid/main.py, corvid/router.py, corvid/models.py,
corvid/database.py, mcp_server.py), Alembic migrations, Jinja templates, and the
container/compose definitions.
Methodology
Manual source inspection of the auth layer, all query construction, template rendering, and config; plus greps for committed secrets and unsafe sinks. No automated scanners were available.
Summary
| Severity | Count |
|---|---|
| Critical | 0 |
| High | 0 |
| Medium | 1 |
| Low | 1 |
| Info | 1 |
Findings
M1 — Auth fails open when TICKETS_API_KEY is unset (Medium)
- Location:
corvid/router.py:164-165,corvid/config.py:4 - Description:
TICKETS_API_KEYdefaults to an empty string (config.py:4). Inrequire_ticket_auth, when no API key is configured the handler returns a synthetic{"username": "local", "email": "local@localhost"}identity instead of raising 401 (router.py:164-165) — i.e. the service runs fully open. The intent is a "local mode", but it is the default state and there is no startup warning or refuse-to-serve guard. - Impact: A deployment that forgets to set
TICKETS_API_KEYexposes all ticket read/write endpoints unauthenticated to anything that can reach the port. The web UI never sendsX-Api-Key, so it relies on this open mode unless fronted by an auth proxy. - Recommendation: Fail closed — refuse to start (or 401 all requests) when both
TICKETS_API_KEYis empty and norequire_userdependency is wired, unless an explicitCORVID_ALLOW_ANONYMOUS=1opt-in is set.
L1 — No rate limiting / lockout on the API-key check (Low)
- Location:
corvid/router.py:151-154 - Description: The key comparison itself is constant-time (
hmac.compare_digest— good), but there is no throttling on repeated failed attempts. - Recommendation: Add basic rate limiting at the proxy or app layer.
Info — Internal DB DSN via environment
corvid/config.py/database.py read the database URL from the environment; .env is
gitignored and .env.example holds only placeholders. No secrets committed.
Clean (verified, no findings)
- Secrets: none committed —
.envis gitignored;.env.exampleis placeholders only. - SQL injection: all DB access is via SQLAlchemy ORM / bound parameters; no string-built SQL.
- XSS: Jinja templates auto-escape; no
| safeon user-controlled data observed. - Command injection / unsafe deserialization: no
subprocess,os.system,eval,exec,pickle, oryaml.loadin tracked code. - Auth comparison: timing-safe via
hmac.compare_digest.