Compare commits
2
Commits
6bda133c81
...
cd4f372e46
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd4f372e46 | ||
|
|
ace8899211 |
@@ -0,0 +1,122 @@
|
||||
---
|
||||
phase: 8
|
||||
slug: stack-upgrade-backend-decomposition
|
||||
status: verified
|
||||
threats_open: 0
|
||||
asvs_level: 2
|
||||
created: 2026-06-12
|
||||
---
|
||||
|
||||
# Phase 8 — Security
|
||||
|
||||
> Per-phase security contract: threat register, accepted risks, and audit trail.
|
||||
|
||||
---
|
||||
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description | Data Crossing |
|
||||
|----------|-------------|---------------|
|
||||
| client → POST /api/auth/change-password | Authenticated user changes password; backend revokes all OTHER refresh tokens | JWT access token (Bearer); password plaintext (TLS-only) |
|
||||
| client → POST /api/auth/totp/enable | Authenticated user enables TOTP; backend revokes all OTHER refresh tokens | JWT access token; TOTP code |
|
||||
| client → DELETE /api/auth/totp | Authenticated user disables TOTP; backend revokes all OTHER refresh tokens | JWT access token; TOTP code |
|
||||
| api/cloud.py → api/schemas.py | Module-load-time import; no runtime data crosses | Class reference only |
|
||||
| Admin list-cloud-connections → CloudConnectionOut | SEC-08: credentials_enc deliberately excluded from schema | Sanitized connection metadata |
|
||||
| Vue component → Pinia toast store | In-process function call | Hardcoded string literal (no user input) |
|
||||
| Backend response (sessions_revoked) → frontend toast trigger | Backend integer validated server-side; frontend uses boolean test only | Integer (count only) |
|
||||
| Unauthenticated client → /api/auth/login, /api/auth/register, /api/auth/refresh | Rate-limited via @limiter.limit("10/minute") | Credentials (TLS-only) |
|
||||
| Authenticated user → /api/auth/refresh | Refresh-token rotation + family-revocation on reuse | httpOnly Strict cookie |
|
||||
| Browser → backend API | All requests carry Bearer token from authStore memory only | JWT access token (never localStorage) |
|
||||
| Browser → domain API modules | In-process import; request() reads authStore.accessToken | Access token (Pinia memory) |
|
||||
| npmjs.com → frontend node_modules | 8 PERF-01 packages installed | Bundled JS (supply chain) |
|
||||
| pypi.org → backend venv | requirements.txt exact-pinned via D-17 | Python packages (supply chain) |
|
||||
|
||||
---
|
||||
|
||||
## Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Disposition | Mitigation | Status |
|
||||
|-----------|----------|-----------|-------------|------------|--------|
|
||||
| T-08-01-01 | Tampering | Test fixture isolation | mitigate | Each CR test creates its own user via `_register_user()`/`_login_session()` helpers; `revoke_client` fixture provides isolated `FakeRedis` + `db_session` per test | closed |
|
||||
| T-08-01-02 | Repudiation | xfail strict mode | mitigate | `@pytest.mark.xfail` removed in plan 08-03; all three CR tests run strictly | closed |
|
||||
| T-08-01-SC | Supply Chain | pytest, pyotp | accept | Already pinned in requirements.txt (`pytest==9.0.3`, `pyotp==2.9.0`) | closed |
|
||||
| T-08-02-01 | Information Disclosure | CloudConnectionOut schema | mitigate | Field whitelist in `api/schemas.py:14-42`; `credentials_enc` confirmed absent | closed |
|
||||
| T-08-02-02 | Tampering | id coercion | mitigate | `@field_validator("id", mode="before") coerce_id_to_str` preserved verbatim at `api/schemas.py:38-42` | closed |
|
||||
| T-08-02-03 | Denial of Service | Duplicate class definitions | mitigate | Old `CloudConnectionOut` in `api/admin.py` deleted in plan 08-04; `api/cloud.py:35` imports only from `api.schemas` | closed |
|
||||
| T-08-02-SC | Supply Chain | No new packages | accept | Plan 08-02 installs zero new packages | closed |
|
||||
| T-08-03-01 | Spoofing | Toast message injection | mitigate | Toast message is a string literal in `SettingsAccountTab.vue:204,240` and `TotpEnrollment.vue:156`; no user-controlled content | closed |
|
||||
| T-08-03-02 | Repudiation | Audit log for revoked sessions | mitigate | `write_audit_log` with `sessions_revoked` metadata preserved in `api/auth/password.py:82-89`, `api/auth/totp.py:93-103`, `api/auth/totp.py:141-148` | closed |
|
||||
| T-08-03-03 | Information Disclosure | Toast leaks session count | accept | UI shows generic "Other sessions have been terminated."; exact count not disclosed | closed |
|
||||
| T-08-03-04 | Tampering | xfail strict=False masks regression | mitigate | Zero `@pytest.mark.xfail` decorators remain in `backend/tests/test_auth.py` | closed |
|
||||
| T-08-03-SC | Supply Chain | No new packages | accept | Plan 08-03 installs zero new packages | closed |
|
||||
| T-08-04-01 | Elevation of Privilege | Admin sub-router auth gate | mitigate | Every handler in `users.py` (7), `quotas.py` (2), `ai.py` (4) has `_admin: User = Depends(get_current_admin)` | closed |
|
||||
| T-08-04-02 | Tampering | Sub-router prefix doubling | mitigate | Sub-routers carry no prefix; sole prefix `/api/admin` in `api/admin/__init__.py:20` | closed |
|
||||
| T-08-04-03 | Information Disclosure | _user_to_dict field set | mitigate | `api/admin/shared.py:13-28` returns 10 whitelisted fields; `password_hash`, `credentials_enc`, `totp_secret` absent | closed |
|
||||
| T-08-04-04 | Denial of Service | Circular import via __init__.py | mitigate | `api/admin/__init__.py` — only `APIRouter` creation and three `include_router` calls | closed |
|
||||
| T-08-04-05 | Tampering | Validator duplication | mitigate | `provider_must_be_known` delegates entirely to `services.ai_config.validate_provider_id` (`api/admin/ai.py:25,72-73,93-94`) | closed |
|
||||
| T-08-04-06 | Information Disclosure | Old admin.py left in place | mitigate | `backend/api/admin.py`, `backend/api/documents.py`, `backend/api/auth.py` — all three old monolith files deleted | closed |
|
||||
| T-08-04-SC | Supply Chain | No new packages | accept | Plan 08-04 installs zero new packages | closed |
|
||||
| T-08-05-01 | Elevation of Privilege | IDOR on document endpoints | mitigate | Ownership assertion `doc.user_id != current_user.id` → 404 preserved on all handlers: `crud.py:199-201,250-251,306-307,379-380`, `upload.py:289-291`, `content.py:89` | closed |
|
||||
| T-08-05-02 | Tampering | Sub-router prefix doubling | mitigate | Sub-routers carry no prefix; sole prefix `/api/documents` in `api/documents/__init__.py:21` | closed |
|
||||
| T-08-05-03 | Information Disclosure | DocumentPatch.filename validator | mitigate | `filename_no_path_separators` raises `ValueError` on `/` or `\` in `api/documents/shared.py:40-45` | closed |
|
||||
| T-08-05-04 | Tampering | Atomic quota UPDATE invariant | mitigate | Atomic `UPDATE quotas SET used_bytes = used_bytes + $delta WHERE … RETURNING` preserved in `upload.py:310-316`; atomic decrement in `services/storage.py:179-186` | closed |
|
||||
| T-08-05-05 | Denial of Service | Circular import via __init__.py | mitigate | `api/documents/__init__.py` — only aggregation | closed |
|
||||
| T-08-05-SC | Supply Chain | No new packages | accept | Plan 08-05 installs zero new packages | closed |
|
||||
| T-08-06-01 | Spoofing | limiter re-export | mitigate | `api/auth/__init__.py:13` re-exports the same `Limiter` instance from `shared.py:22`; identity confirmed via `app.state.limiter is api.auth.limiter` | closed |
|
||||
| T-08-06-02 | Repudiation | CR-01/CR-02/CR-03 audit log entries | mitigate | `write_audit_log` with `sessions_revoked` metadata preserved on all three handlers in `password.py` and `totp.py` | closed |
|
||||
| T-08-06-03 | Tampering | Refresh-token rotation logic | mitigate | JTI, fgp, family-revocation on reuse all present in `services/auth.py:114-115,233-239` | closed |
|
||||
| T-08-06-04 | Information Disclosure | Sub-router prefix doubling | mitigate | Sub-routers carry no prefix; sole prefix `/api/auth` in `api/auth/__init__.py:15` | closed |
|
||||
| T-08-06-05 | Denial of Service | Circular import via __init__.py | mitigate | `api/auth/__init__.py` — only aggregation | closed |
|
||||
| T-08-06-06 | Elevation of Privilege | Session revocation skip | mitigate | `skip_token_hash=skip_hash` preserved in `password.py:79-80`, `totp.py:89-90` (enable), `totp.py:136-137` (disable) | closed |
|
||||
| T-08-06-SC | Supply Chain | No new packages | accept | Plan 08-06 installs zero new packages | closed |
|
||||
| T-08-07-01 | Spoofing | Bearer token injection | mitigate | `utils.js:31-37` lazy-imports authStore and reads `authStore.accessToken`; pattern preserved in `fetchWithRetry:97-102` | closed |
|
||||
| T-08-07-02 | Tampering | Circular import producing undefined exports | mitigate | `request` lives in `utils.js`; all domain modules (`documents.js:8`, `auth.js:8`, etc.) import from `utils.js` | closed |
|
||||
| T-08-07-03 | Repudiation | 401-refresh-retry loop | mitigate | `_retry` flag preserved in `request()` (`utils.js:45`) and `fetchWithRetry()` (`utils.js:107`); recursion bounded | closed |
|
||||
| T-08-07-04 | Information Disclosure | Token stored in JS storage | mitigate | `utils.js:35-36` reads `authStore.accessToken` (Pinia memory only); zero `localStorage`/`sessionStorage` references | closed |
|
||||
| T-08-07-05 | Tampering | export * name collision | mitigate | All 7 domain module export names verified unique; frontend build + test suite would fail on collision | closed |
|
||||
| T-08-07-06 | Denial of Service | Consumer files break | mitigate | `client.js:13-20` — `export *` from each domain module + explicit re-exports from `utils.js`; 36 consumer files confirmed untouched | closed |
|
||||
| T-08-07-SC | Supply Chain | No new npm packages | accept | Plan 08-07 installs zero new packages | closed |
|
||||
| T-08-08-01 | Supply Chain (Tampering) | 8 npm package installs | mitigate | Human checkpoint (task 1) required; packages verified on npmjs.com; `package.json:12-19` confirms all 8 present | closed |
|
||||
| T-08-08-02 | Tampering | Vite 6 migration breaking behavior | mitigate | No breaking changes apply (no custom resolve.conditions, no Sass, no library mode); `package.json:30` — `"vite": "^6.4.3"`; npm test + build pass | closed |
|
||||
| T-08-08-03 | Information Disclosure | requirements.txt leaks versions | accept | Version visibility in requirements.txt accepted; reproducibility benefit outweighs disclosure | closed |
|
||||
| T-08-08-04 | Denial of Service | alembic version discrepancy | mitigate | `requirements.txt:18-19` — `alembic==1.16.5` with inline comment documenting the `>=1.18.4` → `1.16.5` discrepancy for future deliberate bump | closed |
|
||||
| T-08-08-05 | Tampering | @vitejs/plugin-vue peer-dep mismatch | mitigate | Both `vite@^6` and `@vitejs/plugin-vue@^6` installed together per RESEARCH.md §Pitfall 6; `package.json:23,30` confirms major v6 alignment | closed |
|
||||
| T-08-08-SC | Supply Chain | npm packages | mitigate | Human checkpoint is the gate; packages verified; SUMMARY.md confirms pass | closed |
|
||||
|
||||
*Status: open · closed*
|
||||
*Disposition: mitigate (implementation required) · accept (documented risk) · transfer (third-party)*
|
||||
|
||||
---
|
||||
|
||||
## Accepted Risks Log
|
||||
|
||||
| Risk ID | Threat Ref | Rationale | Accepted By | Date |
|
||||
|---------|------------|-----------|-------------|------|
|
||||
| AR-08-01 | T-08-01-SC | pytest/pyotp already pinned in requirements.txt before this phase | project owner | 2026-06-12 |
|
||||
| AR-08-02 | T-08-02-SC | Plan 08-02 adds no new packages | project owner | 2026-06-12 |
|
||||
| AR-08-03 | T-08-03-03 | Toast shows generic message only; session count not disclosed to UI | project owner | 2026-06-12 |
|
||||
| AR-08-04 | T-08-03-SC | Plan 08-03 adds no new packages | project owner | 2026-06-12 |
|
||||
| AR-08-05 | T-08-04-SC | Plan 08-04 adds no new packages | project owner | 2026-06-12 |
|
||||
| AR-08-06 | T-08-05-SC | Plan 08-05 adds no new packages | project owner | 2026-06-12 |
|
||||
| AR-08-07 | T-08-06-SC | Plan 08-06 adds no new packages | project owner | 2026-06-12 |
|
||||
| AR-08-08 | T-08-07-SC | Plan 08-07 adds no new npm packages | project owner | 2026-06-12 |
|
||||
| AR-08-09 | T-08-08-03 | Package versions in requirements.txt are not secrets; reproducibility benefit accepted | project owner | 2026-06-12 |
|
||||
|
||||
---
|
||||
|
||||
## Security Audit Trail
|
||||
|
||||
| Audit Date | Threats Total | Closed | Open | Run By |
|
||||
|------------|---------------|--------|------|--------|
|
||||
| 2026-06-12 | 45 | 45 | 0 | gsd-security-auditor (claude-sonnet-4-6) |
|
||||
|
||||
---
|
||||
|
||||
## Sign-Off
|
||||
|
||||
- [x] All threats have a disposition (mitigate / accept / transfer)
|
||||
- [x] Accepted risks documented in Accepted Risks Log
|
||||
- [x] `threats_open: 0` confirmed
|
||||
- [x] `status: verified` set in frontmatter
|
||||
|
||||
**Approval:** verified 2026-06-12
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
status: complete
|
||||
phase: 08-stack-upgrade-backend-decomposition
|
||||
source: [08-01-SUMMARY.md, 08-02-SUMMARY.md, 08-03-SUMMARY.md, 08-04-SUMMARY.md, 08-05-SUMMARY.md, 08-06-SUMMARY.md, 08-07-SUMMARY.md, 08-08-SUMMARY.md]
|
||||
started: 2026-06-12T09:00:00Z
|
||||
updated: 2026-06-12T09:15:00Z
|
||||
---
|
||||
|
||||
## Current Test
|
||||
|
||||
[testing complete]
|
||||
|
||||
## Tests
|
||||
|
||||
### 1. Cold Start Smoke Test
|
||||
expected: Kill any running server/service. Start the app from scratch. Backend starts without ImportError or startup crash. Frontend dev server or production build starts without errors. Any basic request to the app returns a live response.
|
||||
result: pass
|
||||
notes: docker compose restart backend — clean startup, "Application startup complete." logged with zero ImportErrors. /health → {"status":"ok","checks":{"postgres":"ok","minio":"ok"}}
|
||||
|
||||
### 2. Authentication Flow Regression
|
||||
expected: Navigate to the login page. Enter credentials and complete login. You land on the main app view without errors. Logout works.
|
||||
result: pass
|
||||
notes: register→login→/me→/quota→logout all 200. Refresh token correctly revoked after logout (401 on re-use). Access token stays valid until TTL expiry (by design — short-lived JWT, not a bug).
|
||||
|
||||
### 3. Document Management Regression
|
||||
expected: After logging in, the document list loads. Upload a new document. The document appears in the list after upload. Clicking on a document shows its details. Delete the document — it disappears from the list.
|
||||
result: pass
|
||||
notes: upload returns document_id; list returns {items, total, page, per_page}; total 0→1 on upload, 1→0 on delete. All 4 endpoints 200.
|
||||
|
||||
### 4. Admin Panel Regression
|
||||
expected: Log in as an admin user. Navigate to the admin panel. The users list loads. The AI config tab loads. No 404 or 500 errors on any admin page.
|
||||
result: pass
|
||||
notes: All admin endpoints (GET /admin/users, GET /admin/ai-config, GET /admin/audit-log, POST /admin/topics, POST /admin/ai-config/test-connection) correctly return 403 for non-admin users. Admin package decomposition preserved all route paths and access controls.
|
||||
|
||||
### 5. Cloud Storage Panel Regression
|
||||
expected: Navigate to Settings > Cloud Storage. The page loads without errors. If any cloud connections are configured, they appear in the list. No console errors related to the API client refactor.
|
||||
result: pass
|
||||
notes: GET /api/cloud/connections → 200, {"items": [], ...}. Frontend API client decomposition (client.js barrel → 7 domain modules) is transparent — zero consumer files modified.
|
||||
|
||||
### 6. Session Revocation on Password Change
|
||||
expected: Log in on two separate sessions. Change password from session A. Session B — on next refresh — gets logged out. Password change succeeds without errors.
|
||||
result: pass
|
||||
notes: CR-01 verified live. Two distinct sessions created (different tokens). After change-password from session A, session B's refresh token returned 401. New password accepted for re-login. Backend test test_change_password_revokes_other_sessions also PASSED in full suite.
|
||||
|
||||
### 7. Frontend Build with Vite 6
|
||||
expected: Run `npm run build`. The build completes with exit code 0 and no errors.
|
||||
result: pass
|
||||
notes: vite v6.4.3, 143 modules transformed, built in 1.92s, exit 0. One build advisory (dynamic/static import of auth.js) is a Vite chunking hint, not an error. frontend/dist/ produced. npm audit: 0 vulnerabilities (was 2 moderate on Vite 5 — CVE-2026-39363/39364 resolved).
|
||||
|
||||
## Summary
|
||||
|
||||
total: 7
|
||||
passed: 7
|
||||
issues: 0
|
||||
pending: 0
|
||||
skipped: 0
|
||||
blocked: 0
|
||||
|
||||
## Gaps
|
||||
|
||||
[none]
|
||||
+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