Files
ducoterra 9ce13b6290
Build and Push Containers / build-and-push-app (push) Successful in 29s
Build and Push Containers / build-and-push-db (push) Successful in 13s
add vulns.md
2026-09-14 16:01:58 -04:00

93 lines
8.4 KiB
Markdown

# VULNS.md — Known & Historically Found Vulnerabilities
Registry of security findings for Brain of Reese. **Full details, proofs of
concept, and the priority-ordered remediation task list live in
[`.agents/remediation_plan.md`](./remediation_plan.md)** (this file is the
quick-reference index; when a finding is fixed, update its status here and
append to the fixed log at the bottom).
Conventions:
- Statuses: `open` → `mitigated` (control in place) → `fixed` (remediated +
verified) → `accepted` (owner decision, risk consciously kept) →
`false positive` (intended behavior — never re-raise or "fix" without an
explicit owner decision).
- Prior-audit IDs keep their original numbering (SEC-0x = audit 2026-09-07,
SEC-14-xx = audit 2026-09-14).
## Open findings
| ID | Sev | Type (CWE) | Location | Status |
|----|-----|-----------|----------|--------|
| SEC-14-04 | Medium | In-flight SSE chat stream pins a DB connection for the whole turn → pool-exhaustion DoS (~15 streams, one token user) (CWE-770/400) | `app/api/chat.py` (request-scoped `get_db` across the `StreamingResponse`), `app/db.py` (default pool 5+10) | open |
**SEC-14-04 — one-line essence:** 16 slow `curl -N` streams on `/api/chat`
with any token hang every DB-backed route (default pool = 15 connections).
Remediation: short-lived sessions per DB step inside the stream (the
sync/upload house pattern), explicit pool kwargs, optional chat concurrency
cap — see `.agents/remediation_plan.md` §SEC-14-04.
## False positives — 2026-09-14 audit (owner: intended behavior, well known)
Reclassified by the owner on 2026-09-14: every 2026-09-14 finding **except
SEC-14-04** is intended, well-known behavior of a single-owner homelab app —
not a vulnerability to remediate. Kept here for the record so a future audit
(or a future agent) does not re-raise them. Do NOT "fix" these without an
explicit owner decision; the trade-offs below are deliberate.
| ID | Sev (as audited) | Type (CWE) | Location | Why it's intended |
|----|-----|-----------|----------|-------------------|
| SEC-14-01 | Medium | Unthrottled anonymous write surface → storage-exhaustion DoS (CWE-770/307) | `app/api/chats.py` (public POST/PUT `/api/chats`, POST `/{id}/share`); `app/core/rate_limit.py` covers login routes only | Public chat-save is the owner-locked phase-55 A1 design (the visitor saves their own conversation; auto-save is a normal traffic path). Homelab LAN trust model. |
| SEC-14-02 | Medium | Guest IDOR — chat-id holder can force-share / overwrite a private chat (CWE-639) | `app/api/chats.py` `share_chat`, `update_chat` | Owner-locked trust model: the unguessable uuid4 row id IS the credential (phase 51/55). Sharing/overwriting by an id holder is intended. |
| SEC-14-03 | Medium | Postgres on `0.0.0.0:5432` with weak documented creds `reese:reese` (CWE-790/668) | `compose.yaml` (db `ports`, `POSTGRES_PASSWORD`), `app/config.py` default `database_url`, `.env.example` | Deliberate homelab dev convenience; the trusted LAN is the security boundary and the creds are documented on purpose. |
| SEC-14-05 | Low | Cleartext transport + plaintext admin password (CWE-319/256) | `app/main.py` (`https_only=False`), `app/config.py` | Owner decision 2026-08-22: plaintext password and homelab HTTP are deliberate (TLS is a documented non-goal). |
| SEC-14-06 | Low | `debugpy` binds `0.0.0.0:5678` when `DEBUGPY=1` → LAN RCE if left on (CWE-1188) | `app/core/debugging.py` | Explicit opt-in dev feature (AGENTS.md rule 7); the wide bind is intentional for remote IDE attach. |
| SEC-14-07 | Low | No throttle on `/api/chat` for token users — leaked token = unlimited LLM turns (CWE-770) | `app/api/chat.py`, `app/rag/llm.py` | Tokens are capability credentials by design (phase 79); holders are trusted to use the models. |
| SEC-14-08 | Low | Importer reads source files whole, no per-file size cap (git/local sources; uploads are capped) (CWE-770) | `app/rag/importer.py:390` | Source dirs are admin-chosen trusted content; the never-truncated A7 contract implies whole-file reads. |
| SEC-14-09 | Low | Full question text + tool args in stdout logs and `query_log` (CWE-532) | `app/api/chat.py` per-turn line, `app/rag/agent.py` tool line | The full per-turn log line is a locked PLAN §9 requirement (AGENTS.md rule 10: "amply log"). |
| SEC-14-10 | Info | Admin git-source registry reaches arbitrary https/ssh/git@ hosts (admin-gated SSRF) (CWE-918) | `app/api/git_sources.py` `URL_RE` → `scripts/git_sync.py` | Admin-only surface; the admin already controls the host. Arbitrary remotes are the feature (phase 59/35). |
| SEC-14-11 | Info | No `Referrer-Policy` / `Permissions-Policy` (owner decision A4, phase 82) (CWE-538) | `app/core/security_headers.py` | Phase-82 decision A4 deliberately scoped headers to CSP + XFO + nosniff; no external requests exist (No-CDN). |
| SEC-14-12 | Info | `BOR_SESSION_SECRET` only checked non-empty — weak secret allows cookie forgery (CWE-340) | `app/core/auth.py` `ensure_admin_configured` | Operator-provisioned secret (generator command documented in `.env.example`); strength is the operator's choice. |
Note: `.agents/remediation_plan.md` (the audit report) still lists these with
remediation tasks — its task list items P1 (parts), P2, P4, P5, P6, P7
address findings now reclassified as false positives and are **dropped**
unless the owner reopens them. Only task P3 (SEC-14-04) remains actionable.
## Fixed (verified present in the 2026-09-14 audit)
| ID | Sev | What it was | Fixed by | Fixed phase |
|----|-----|-------------|----------|-------------|
| SEC-03 | Medium | No rate limit on failed sign-ins (`/api/login`, `/api/token-auth`) | Per-IP sliding window (10 fails / 15 min, shared counter, fail-open) — `app/core/rate_limit.py` | 81 |
| SEC-04 | Medium | No CSP / X-Frame-Options / nosniff → clickjacking of the admin UI | `SecurityHeadersMiddleware` — CSP `default-src 'self'; base-uri 'none'; frame-ancestors 'none'` + `X-Frame-Options: DENY` + nosniff on every response | 82 |
| SEC-05 | Medium | Unbounded anonymous payload on the public chat-save surface | Schema caps: ≤200 messages, 32 KB text/thinking, nested list caps (~13 MB worst-case) — `app/schemas.py` | 83 |
| SEC-08 | Medium | Docs-push 502 leaked git stderr with `user:pass@` credentials | `app/core/errors.sanitize_error` userinfo masker applied to all error surfaces | 84 |
| SEC-13 | Low | LLM error strings interpolated the raw base URL (possible embedded creds) | Same sanitizer on every LLM error f-string | 84 |
## Controls verified sound (2026-09-14 audit — no finding)
- SQL injection: all ORM / parameter-bound `text()`; FTS tsquery regex-constrained to `[a-z0-9.]` **and** bound as a parameter.
- XSS: escape-first `renderMarkdown()`, `textContent` for all data, strict CSP, no `unsafe-inline` (theme tag uses a sha256-hashed style-src exemption), all `innerHTML` sites audited static-or-escaped.
- Command injection: single list-argv subprocess funnel (`scripts/git_sync.run_git`), no `shell=True`; branch tokens shape-validated at boot; draft paths refuse absolute/`..`.
- Archive upload: traversal / absolute / drive-letter / symlink-hardlink escape / device-FIFO rejection, compressed + extracted byte caps (zip-bomb), atomic swap-in with rollback — `app/rag/archive_upload.py`.
- API tokens: 128-bit CSPRNG, SHA-256 hash stored (plaintext one-shot in the 201 body only), per-request live row check = revocation, generic 401s (no enumeration).
- Open redirect: login `?next=` restricted to same-origin relative paths.
- Agent tools: model-supplied `ls/read/grep` resolve against Postgres rows only (no FS/network), round cap bounds loops, fixed-substring grep (no ReDoS).
- Dependencies: `pip-audit` clean on the locked set (2026-09-14); bandit hits are false positives / dev scripts.
- Git history: no committed secrets across all 161 commits (2026-09-14 scan); `.env` gitignored.
## Re-audit triggers
Re-run the audit (or at least the relevant lens) when any of these land:
a reverse proxy or TLS in front of the app, a multi-user/auth rework,
changes to `app/api/chats.py` write gating, the compose port bindings, the
subprocess/git surface, the upload pipeline, or any dependency with a
published CVE (`uvx pip-audit`).
## Changelog
- 2026-09-14 (owner reclassification) — all 2026-09-14 findings except
SEC-14-04 reclassified as **false positives** (intended behavior, well
known). Open count: 12 → 1.
- 2026-09-14 — second audit: 12 new findings (4 Medium / 4 Low / 4 Info),
5 prior findings verified fixed. Full report: `.agents/remediation_plan.md`.