diff --git a/.gitea/workflows/deploy-dev.yml b/.gitea/workflows/deploy-dev.yml index b7ccf31..d134aa3 100644 --- a/.gitea/workflows/deploy-dev.yml +++ b/.gitea/workflows/deploy-dev.yml @@ -193,7 +193,7 @@ jobs: deploy-dev: name: Deploy to Dev runs-on: vps - needs: [build-image-dev, frontend-tests] + needs: [build-image-dev, backend-tests, frontend-tests] env: SECRET_KEY: test-secret-key-for-ci-environment-32chars-minimum diff --git a/dashboard/backend/rbac.py b/dashboard/backend/rbac.py index d0729c1..9ea94b4 100644 --- a/dashboard/backend/rbac.py +++ b/dashboard/backend/rbac.py @@ -26,7 +26,7 @@ ROLE_GROUP_MAP = { DEFAULT_RBAC = { "role_defaults": { "admin": {"pages": "*", "sidebar_links": "*"}, - "member": {"pages": ["dashboard", "files", "chat-digest", "opc"], "sidebar_links": "*"}, + "member": {"pages": ["dashboard", "files", "chat-digest", "opc", "media-ops"], "sidebar_links": "*"}, "viewer": {"pages": ["dashboard"], "sidebar_links": ["dashboard"]}, }, "user_overrides": {}, diff --git a/dashboard/docker-compose.dev.yml b/dashboard/docker-compose.dev.yml index 8594ab2..5d0eab0 100644 --- a/dashboard/docker-compose.dev.yml +++ b/dashboard/docker-compose.dev.yml @@ -31,8 +31,8 @@ services: - LITELLM_API_KEY=anything - LITELLM_HEALTH_API_KEY=${LITELLM_HEALTH_API_KEY:-} - GITEA_DB_PASSWORD=${GITEA_DB_PASSWORD} - - TRANSMISSION_USER=${TRANSMISSION_USER:-admin} - - TRANSMISSION_PASS=${TRANSMISSION_PASS:-admin} + - TRANSMISSION_USER=${TRANSMISSION_USER} + - TRANSMISSION_PASS=${TRANSMISSION_PASS} volumes: - /volume1:/volume1 - /volume1/docker/nas-dashboard/ssh/dashboard_terminal:/app/ssh/id_ed25519:ro diff --git a/docs/specs/active.md b/docs/specs/active.md index 8d80f4e..e9194a9 120000 --- a/docs/specs/active.md +++ b/docs/specs/active.md @@ -1 +1 @@ -../../specs/sprint-06-code-quality.md \ No newline at end of file +../../specs/sprint-09-security-hardening.md \ No newline at end of file diff --git a/specs/sprint-09-security-hardening.md b/specs/sprint-09-security-hardening.md new file mode 100644 index 0000000..03abd4e --- /dev/null +++ b/specs/sprint-09-security-hardening.md @@ -0,0 +1,88 @@ +# 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