docs(phase-08): add security threat verification — all 45 threats closed
Register sourced from 8 PLAN.md threat_model blocks (register_authored_at_plan_time: true). 36 mitigate threats verified CLOSED by gsd-security-auditor. 9 accepted risks documented. threats_open: 0 — phase advancement unblocked. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
ace8899211
commit
cd4f372e46
+79
@@ -247,3 +247,82 @@ The VERIFICATION.md for Phase 7.2 identified one blocker gap: `password_reset_co
|
||||
- Added `await request.app.state.redis.set(f"user_nbf:{user.id}", ...)` before `session.commit()`
|
||||
- Added test `test_password_reset_confirm_writes_user_nbf_to_redis` — PASSED
|
||||
- All 5 security-event handlers now write `user_nbf` consistently
|
||||
|
||||
---
|
||||
|
||||
## Phase 08 Threat Verification
|
||||
|
||||
**Audit date:** 2026-06-12
|
||||
**Phase:** 8 — Stack Upgrade & Backend Decomposition (plans 08-01 through 08-08)
|
||||
**ASVS Level:** L2
|
||||
**Auditor:** gsd-security-auditor (claude-sonnet-4-6)
|
||||
**Threats closed:** 36/36
|
||||
**Open threats (blockers):** 0
|
||||
|
||||
### Threat Verification
|
||||
|
||||
| Threat ID | Category | Disposition | Status | Evidence |
|
||||
|-----------|----------|-------------|--------|----------|
|
||||
| T-08-01-01 | Tampering | mitigate | CLOSED | `backend/tests/test_auth.py:73-107` — `revoke_client` fixture creates its own `FakeRedis` and uses `db_session` dependency override; each CR test registers and logs in its own user via `_register_user()` / `_login_session()` helpers; no cross-test state |
|
||||
| T-08-01-02 | Repudiation | mitigate | CLOSED | `backend/tests/test_auth.py:1-8` — file-level docstring confirms `strict=False` was Plan 08-01 Wave 0; no `@pytest.mark.xfail` decorator present on any of the three CR test functions (lines 172, 222, 273) |
|
||||
| T-08-01-SC | Supply Chain | accept | CLOSED | `pytest==9.0.3`, `pytest-asyncio==1.4.0`, `pyotp==2.9.0` all pinned in `backend/requirements.txt:14-15,26` — no new packages added by this plan |
|
||||
| T-08-02-01 | Information Disclosure | mitigate | CLOSED | `backend/api/schemas.py:14-42` — `CloudConnectionOut` field list: `id, provider, display_name, status, connected_at, server_url, connection_username`; `credentials_enc` absent; whitelist comment on line 15 explicitly documents the exclusion |
|
||||
| T-08-02-02 | Tampering | mitigate | CLOSED | `backend/api/schemas.py:38-42` — `@field_validator("id", mode="before") def coerce_id_to_str` coerces UUID ORM values to `str` before validation |
|
||||
| T-08-02-03 | Denial of Service | mitigate | CLOSED | `backend/api/cloud.py:35` — `from api.schemas import CloudConnectionOut`; no local `class CloudConnectionOut` definition in cloud.py (single definition in schemas.py) |
|
||||
| T-08-02-SC | Supply Chain | accept | CLOSED | No new packages added in Plan 08-02 — documented in Accepted Risks Log |
|
||||
| T-08-03-01 | Spoofing | mitigate | CLOSED | `frontend/src/components/settings/SettingsAccountTab.vue:204,240` — toast message is the string literal `'Other sessions have been terminated.'`; `frontend/src/components/auth/TotpEnrollment.vue:156` — same literal string; no user-controlled content in any toast message |
|
||||
| T-08-03-02 | Repudiation | mitigate | CLOSED | `backend/api/auth/password.py:87-89` — `write_audit_log(session, event_type="auth.password_changed", metadata_={"sessions_revoked": revoked})`; `backend/api/auth/totp.py:94-103` — `auth.totp_enrolled` with `sessions_revoked`; `backend/api/auth/totp.py:141-148` — `auth.totp_revoked` with `sessions_revoked` |
|
||||
| T-08-03-03 | Information Disclosure | accept | CLOSED | Toast shows `'Other sessions have been terminated.'` (boolean trigger via `sessions_revoked > 0`); count not disclosed to UI — documented in Accepted Risks Log |
|
||||
| T-08-03-04 | Tampering | mitigate | CLOSED | `backend/tests/test_auth.py:172,222,273` — `@pytest.mark.asyncio` only; no `@pytest.mark.xfail` decorator on any CR test function |
|
||||
| T-08-03-SC | Supply Chain | accept | CLOSED | No new packages added in Plan 08-03 — documented in Accepted Risks Log |
|
||||
| T-08-04-01 | Elevation of Privilege | mitigate | CLOSED | `backend/api/admin/users.py:80,99,173,238,272,314,415` — every handler has `_admin: User = Depends(get_current_admin)`; `backend/api/admin/quotas.py:45,71`; `backend/api/admin/ai.py:103,166,224,266` — all confirmed |
|
||||
| T-08-04-02 | Tampering | mitigate | CLOSED | `backend/api/admin/__init__.py:20` — `router = APIRouter(prefix="/api/admin", tags=["admin"])` is the ONLY prefix; `backend/api/admin/users.py:30`, `quotas.py:23`, `ai.py:28` — all `router = APIRouter()` with no prefix |
|
||||
| T-08-04-03 | Information Disclosure | mitigate | CLOSED | `backend/api/admin/shared.py:13-28` — `_user_to_dict()` returns exactly: `id, handle, email, role, is_active, totp_enabled, ai_provider, ai_model, password_must_change, created_at`; `password_hash`, `credentials_enc`, `totp_secret` absent from dict |
|
||||
| T-08-04-04 | Denial of Service | mitigate | CLOSED | `backend/api/admin/__init__.py:1-23` — file contains ONLY `APIRouter` creation and three `include_router` calls; no helpers, models, or logic |
|
||||
| T-08-04-05 | Tampering | mitigate | CLOSED | `backend/api/admin/ai.py:25` — `from services.ai_config import ... validate_provider_id`; `ai.py:72-73` and `ai.py:93-94` — `provider_must_be_known` delegates entirely to `validate_provider_id(v)`; no local duplicate |
|
||||
| T-08-04-06 | Information Disclosure | mitigate | CLOSED | `backend/api/admin.py` — does not exist (confirmed absent); `backend/api/documents.py` — does not exist; `backend/api/auth.py` — does not exist; all three old monoliths deleted |
|
||||
| T-08-04-SC | Supply Chain | accept | CLOSED | No new packages added in Plan 08-04 — documented in Accepted Risks Log |
|
||||
| T-08-05-01 | Elevation of Privilege | mitigate | CLOSED | `backend/api/documents/crud.py:199-201,250-251,306-307,379-380` — ownership assertion `if doc is None or doc.user_id != current_user.id: raise HTTPException(404, ...)` on get, patch, delete, classify; `upload.py:289-291` — same on confirm; `content.py:89` — `if doc.user_id != current_user.id` |
|
||||
| T-08-05-02 | Tampering | mitigate | CLOSED | `backend/api/documents/__init__.py:21` — `router = APIRouter(prefix="/api/documents", tags=["documents"])`; `crud.py:46`, `upload.py:53`, `content.py` — all `router = APIRouter()` with no prefix |
|
||||
| T-08-05-03 | Information Disclosure | mitigate | CLOSED | `backend/api/documents/shared.py:40-45` — `@field_validator("filename") def filename_no_path_separators` raises `ValueError` if `"/" in v or "\\" in v` |
|
||||
| T-08-05-04 | Tampering | mitigate | CLOSED | `backend/api/documents/upload.py:310-316` — atomic `UPDATE quotas SET used_bytes = used_bytes + :delta WHERE user_id = :uid AND (used_bytes + :delta) <= limit_bytes RETURNING used_bytes, limit_bytes`; decrement at `backend/services/storage.py:179-186` |
|
||||
| T-08-05-05 | Denial of Service | mitigate | CLOSED | `backend/api/documents/__init__.py` — ONLY aggregation (router creation + 4 include/add_api_route calls); no helpers, models, or logic |
|
||||
| T-08-05-SC | Supply Chain | accept | CLOSED | No new packages added in Plan 08-05 — documented in Accepted Risks Log |
|
||||
| T-08-06-01 | Spoofing | mitigate | CLOSED | `backend/api/auth/__init__.py:13` — `from api.auth.shared import limiter`; `backend/api/auth/shared.py:22` — single `Limiter(key_func=get_client_ip)` instance; re-export is the same object, not a new instantiation |
|
||||
| T-08-06-02 | Repudiation | mitigate | CLOSED | `backend/api/auth/password.py:82-89` — `write_audit_log` with `auth.password_changed` + `sessions_revoked`; `totp.py:93-102` — `auth.totp_enrolled` + `sessions_revoked`; `totp.py:141-148` — `auth.totp_revoked` + `sessions_revoked` |
|
||||
| T-08-06-03 | Tampering | mitigate | CLOSED | `backend/services/auth.py:207-239` — `rotate_refresh_token` with JTI (`uuid.uuid4()` at line 114), `fgp` fingerprint (line 115), family revocation on reuse (line 233-239), Redis NBF write |
|
||||
| T-08-06-04 | Information Disclosure | mitigate | CLOSED | `backend/api/auth/__init__.py:15` — `router = APIRouter(prefix="/api/auth", tags=["auth"])`; `tokens.py:42`, `totp.py:27`, `password.py:34` — all `router = APIRouter()` with no prefix |
|
||||
| T-08-06-05 | Denial of Service | mitigate | CLOSED | `backend/api/auth/__init__.py` — ONLY `APIRouter` creation, three `include_router` calls, and one re-export; no helpers, models, or logic |
|
||||
| T-08-06-06 | Elevation of Privilege | mitigate | CLOSED | `backend/api/auth/password.py:79-80` — `skip_hash = hashlib.sha256(raw_cookie.encode()).hexdigest() if raw_cookie else None; revoke_all_refresh_tokens(..., skip_token_hash=skip_hash)`; same pattern in `totp.py:89-90` (enable) and `totp.py:136-137` (disable) |
|
||||
| T-08-06-SC | Supply Chain | accept | CLOSED | No new packages added in Plan 08-06 — documented in Accepted Risks Log |
|
||||
| T-08-07-01 | Spoofing | mitigate | CLOSED | `frontend/src/api/utils.js:31-37` — lazy `import('../stores/auth.js')`, reads `authStore.accessToken`, sets `Authorization: Bearer ${authStore.accessToken}` header; same pattern in `fetchWithRetry` at lines 97-102 |
|
||||
| T-08-07-02 | Tampering | mitigate | CLOSED | `frontend/src/api/documents.js:8`, `auth.js:8` — `import { request ... } from './utils.js'`; no domain module imports from `client.js`; circular dependency structurally eliminated |
|
||||
| T-08-07-03 | Repudiation | mitigate | CLOSED | `frontend/src/api/utils.js:45` — `_retry` flag in `request()`; `utils.js:107` — `_retry` parameter in `fetchWithRetry()`; both guard against infinite 401-refresh loops |
|
||||
| T-08-07-04 | Information Disclosure | mitigate | CLOSED | `frontend/src/api/utils.js:14-16` (comment) — "Token is NEVER read from localStorage or sessionStorage"; code reads `authStore.accessToken` (Pinia memory); no `localStorage`/`sessionStorage` references in utils.js or client.js |
|
||||
| T-08-07-05 | Tampering | mitigate | CLOSED | Export name deduplication check across all 7 domain modules (documents.js, auth.js, admin.js, folders.js, shares.js, cloud.js, topics.js) returned 0 duplicate names |
|
||||
| T-08-07-06 | Denial of Service | mitigate | CLOSED | `frontend/src/api/client.js:13-20` — `export * from` each domain module + `export { fetchWithRetry, request } from './utils.js'`; all named exports preserved |
|
||||
| T-08-07-SC | Supply Chain | accept | CLOSED | No new npm packages added in Plan 08-07 — documented in Accepted Risks Log |
|
||||
| T-08-08-01 | Supply Chain | mitigate | CLOSED | `frontend/package.json:12-19` — `@tailwindcss/forms: ^0.5.11`, `@vueuse/core: ^14.3.0`, `@vueuse/integrations: ^14.3.0`, `qrcode: ^1.5.4`, `sortablejs: ^1.15.7` present; `tailwind.config.js:2` — `import forms from '@tailwindcss/forms'`; `plugins: [forms]` wires the plugin |
|
||||
| T-08-08-02 | Tampering | mitigate | CLOSED | `frontend/package.json:30` — `"vite": "^6.4.3"`; human checkpoint (task 1 of plan 08-08) required and passed per SUMMARY.md; test suite passes |
|
||||
| T-08-08-03 | Information Disclosure | accept | CLOSED | `backend/requirements.txt` version pinning accepted — documented in Accepted Risks Log |
|
||||
| T-08-08-04 | Denial of Service | mitigate | CLOSED | `backend/requirements.txt:18-19` — `# alembic pinned to currently-installed 1.16.5 (was >=1.18.4); D-17 mandates pinning to installed` followed by `alembic==1.16.5`; discrepancy documented inline |
|
||||
| T-08-08-05 | Tampering | mitigate | CLOSED | `frontend/package.json:23` — `"@vitejs/plugin-vue": "^6.0.7"`; `frontend/package.json:30` — `"vite": "^6.4.3"`; both major versions aligned at v6 |
|
||||
| T-08-08-SC | Supply Chain | mitigate | CLOSED | Human checkpoint is the gate per plan 08-08 task 1; packages verified by human before install; SUMMARY.md confirms pass |
|
||||
|
||||
### Phase 08 Unregistered Flags
|
||||
|
||||
None. SUMMARY.md files for plans 08-01 through 08-08 report no new attack surface beyond the registered threat register.
|
||||
|
||||
### Phase 08 Accepted Risks
|
||||
|
||||
| Risk ID | Component | Accepted Risk | Rationale |
|
||||
|---------|-----------|---------------|-----------|
|
||||
| T-08-01-SC | pytest, pyotp | Already-pinned supply-chain packages used for session revocation tests | `pytest==9.0.3`, `pyotp==2.9.0` pinned in requirements.txt; no new packages introduced |
|
||||
| T-08-02-SC | No new packages | Plan 08-02 adds no new pip or npm packages | Shared schemas.py uses only stdlib + pydantic, already in requirements.txt |
|
||||
| T-08-03-03 | Toast message | Toast shows `'Other sessions have been terminated.'` without exposing the numeric session count | Generic wording is sufficient for user awareness; exact count leaks no actionable data for an attacker |
|
||||
| T-08-03-SC | No new packages | Plan 08-03 adds no new pip or npm packages | Frontend changes use existing Vue/Pinia stack |
|
||||
| T-08-04-SC | No new packages | Plan 08-04 adds no new pip or npm packages | Admin decomposition is a refactor of existing code |
|
||||
| T-08-05-SC | No new packages | Plan 08-05 adds no new pip or npm packages | Documents decomposition is a refactor of existing code |
|
||||
| T-08-06-SC | No new packages | Plan 08-06 adds no new pip or npm packages | Auth decomposition is a refactor of existing code |
|
||||
| T-08-07-SC | No new npm packages | Plan 08-07 adds no new npm packages | API client decomposition uses existing fetch/Pinia stack |
|
||||
| T-08-08-03 | requirements.txt version pinning | Pinned versions are visible in requirements.txt | Reproducibility benefit (D-17) outweighs version disclosure; versions are not secrets; no credentials |
|
||||
|
||||
Reference in New Issue
Block a user