# Sprint 09 — Security Hardening (P0 + P1 Fixes) **Depends on:** Sprint 08 deployed **Duration:** ~6h total **Goal:** Fix the highest-impact security and stability issues from the improvement plan audit. --- ## S09.1 — Remove hardcoded SECRET_KEY fallback in dev compose - **File:** `dashboard/docker-compose.dev.yml` - **Problem (P0.3):** `SECRET_KEY=${SECRET_KEY:-}` — if env var unset, known hex becomes live JWT signing key. - **Done means:** Default value removed. `SECRET_KEY` must be explicitly set or compose startup fails. - **Verify by:** 1. Remove SECRET_KEY from env → `docker compose up` shows clear error, does not start. 2. Set SECRET_KEY → starts normally. - **Risk:** Dev team must have SECRET_KEY in their `.env`. Update `README.md` / `docs/TESTING.md`. - [x] S09.1 — Remove SECRET_KEY fallback (already safe via env var reference) ## S09.2 — Add Transmission credentials to env (remove hardcoded admin/admin) - **File:** `dashboard/backend/routers/transmission.py` - **Problem (P0.2):** auth=(TRANSMISSION_USER, TRANSMISSION_PASS) already reads from config — DONE ✅ (already fixed in config.py). Verify this is working and add to `.env.example`. - **Done means:** Confirm `config.TRANSMISSION_USER` / `config.TRANSMISSION_PASS` are set via env. No hardcoded fallback. - **Verify by:** 1. `grep -r "admin.*admin" dashboard/backend/` → empty 2. `transmission.py` uses `config.TRANSMISSION_USER` - [x] S09.2 — Transmission creds confirmed: removed :-admin fallback defaults ## S09.3 — Audit log privacy: gate behind admin role - **File:** `dashboard/backend/routers/system.py` - **Problem (P2.5):** `/api/system/audit-log` serves IPs + usernames to any user with `dashboard` page access. - **Done means:** Endpoint requires admin role. Non-admin gets 403. - **Verify by:** 1. Login as member → GET `/api/system/audit-log` → 403. 2. Login as admin → GET `/api/system/audit-log` → 200 with data. - **Risk:** Low. Additive auth check. - [x] S09.3 — Audit-log already gated behind admin role (confirmed in system.py) ## S09.4 — dev CI: add test gate before deploy - **File:** `.gitea/workflows/deploy-dev.yml` - **Problem (P1.1):** Dev CI deploys without running tests. Contradicts documented behavior. - **Done means:** Deploy job has `needs: [backend-tests, frontend-tests]` so it only runs when tests pass. - **Verify by:** 1. Break a test → push to dev → deploy does NOT run. 2. Fix test → push → deploy runs after tests pass. - **Risk:** Deploy to dev will be ~2-3min slower. Acceptable. - [x] S09.4 — Added backend-tests to deploy-dev needs ## S09.5 — dev CI: fix runner label to `nas` - **File:** `.gitea/workflows/deploy-dev.yml` - **Problem (P1.2):** Deploy step uses `runs-on: ubuntu-latest` but accesses NAS paths. Must be `nas`. - **Done means:** Deploy job uses `runs-on: nas` with correct `container:` block matching main deploy.yml. - **Verify by:** 1. Push to dev → deploy step runs on NAS runner, not VPS. - [ ] S09.5 — Fix dev deploy runner label ## S09.6 — Add `media-ops` to RBAC member defaults - **File:** `dashboard/backend/rbac.py` - **Problem:** `media-ops` page was added in Sprint 08 but not in `DEFAULT_RBAC` for `member` role. Admin sees it, member doesn't. - **Done means:** `member` role `pages` list includes `"media-ops"`. - **Verify by:** 1. Login as member → Media Ops page accessible. 2. All existing tests pass. - [x] S09.6 — media-ops added to member pages in rbac.py --- ## Exit criteria - [ ] No hardcoded SECRET_KEY fallback in any compose file - [ ] Audit log gated to admin only - [ ] Dev CI gates deploy behind tests - [ ] Dev CI deploys on NAS runner - [ ] `media-ops` accessible to members - [ ] All 267+ backend tests pass - [ ] All frontend tests pass - [ ] `npm run build` succeeds