security(12-04): add Phase 12 security gate evidence and cloud ops runbook

- SECURITY.md: Phase 12 threat register (T-12-01 through T-12-SC) with
  evidence for all 8 threat IDs; bandit/npm audit gate results;
  accepted risks for pip-audit tooling gap and DISABLED connection behavior
- RUNBOOK.md: Phase 12 cloud operations section covering connection
  management, browse refresh lifecycle, item metadata queries,
  stuck-refresh recovery, and security operation notes
This commit is contained in:
curo1305
2026-06-19 01:52:49 +02:00
parent 3b24058e15
commit fccb9c6394
2 changed files with 147 additions and 0 deletions
+88
View File
@@ -567,6 +567,94 @@ malformed.
--- ---
## 9. Phase 12 — Cloud Resource Foundation Operations
### Cloud connection management
**List a user's cloud connections (admin):**
```bash
# Via API (admin token required for admin panel only — not cloud browse endpoints)
curl -H "Authorization: Bearer $ADMIN_TOKEN" http://localhost:8000/api/admin/users
# Cloud connections are user-scoped. Admins cannot browse connection items.
```
**Check connection status from DB:**
```sql
SELECT id, user_id, provider, display_name, status, created_at
FROM cloud_connections
WHERE user_id = '<user-uuid>';
```
**Re-enable a DISABLED connection:**
```sql
UPDATE cloud_connections SET status = 'ACTIVE' WHERE id = '<conn-uuid>';
```
### Cloud browse background refresh
The browse endpoint (`GET /api/cloud/connections/{id}/items`) serves cached metadata and schedules a background refresh when stale:
- **Freshness states:** `fresh` (just refreshed), `refreshing` (refresh in progress), `stale` (last refresh > threshold), `warning` (refresh failed)
- **Cached data is always served** — the UI shows a warning banner for stale/error states without blocking navigation
- **No bytes are downloaded** during browse; `size_bytes` values come from provider-reported metadata only
**Trigger a manual refresh (no direct endpoint — use the UI Refresh button):**
The UI CloudFolderView sends a refresh request that updates `cloud_folder_states.refresh_state` to `refreshing` and schedules a background task.
**Inspect refresh state:**
```sql
SELECT connection_id, folder_ref, refresh_state, refreshed_at, error_code
FROM cloud_folder_states
ORDER BY refreshed_at DESC NULLS LAST
LIMIT 20;
```
**Clear stuck refreshing state:**
```sql
UPDATE cloud_folder_states
SET refresh_state = 'stale', error_code = NULL
WHERE refresh_state = 'refreshing'
AND refreshed_at < NOW() - INTERVAL '15 minutes';
```
### Cloud item metadata
Cloud items are stored in `cloud_items`. They are metadata-only — no file bytes are in the database.
**Count items per connection:**
```sql
SELECT connection_id, count(*) AS item_count
FROM cloud_items
GROUP BY connection_id;
```
**Purge orphaned items (after connection deleted):**
```sql
-- cloud_items.connection_id has ON DELETE CASCADE — orphaned items are removed automatically
-- Verify with:
SELECT count(*) FROM cloud_items ci
LEFT JOIN cloud_connections cc ON ci.connection_id = cc.id
WHERE cc.id IS NULL;
-- Should return 0
```
### Security operations — cloud browse
- **IDOR protection:** All browse endpoints use `get_regular_user` and assert `connection.user_id == current_user.id`. Violations return 404, not 403, to avoid enumeration.
- **Admin tokens:** Blocked by `get_regular_user`. Admin users cannot browse other users' connections.
- **Credential fields:** `credentials_enc` is never included in browse API responses. Verify with: `grep -r "credentials_enc" backend/api/cloud/` — must only appear in connection create/update service code, never in response schemas.
- **SSRF guard:** `storage/cloud_utils.validate_cloud_url` blocks RFC1918, link-local, file://, and cloud metadata IPs. All WebDAV/Nextcloud URLs pass through this guard before any connection is established.
### Phase 12 Deferred Items
| Item | Current state | Future path |
|------|---------------|-------------|
| **Cloud file upload/move/delete** | Browse is metadata-only. Write operations return `temporarily_unavailable`. | Phase 13 will implement cloud mutations (upload, move, delete) with full quota integration. |
| **Byte-level caching** | Size metadata is shown; file bytes are never fetched during browse. | Phase 14 will implement a byte cache for offline/preview access. |
| **Real-time push refresh** | Background refresh is poll-based via the UI Refresh button. | Future: WebSocket or SSE push from background task completion. |
| **pip-audit in CI** | pip-audit not in local Python 3.9 environment. | Add `pip-audit` to requirements-dev.txt and CI pipeline when moving to Python 3.12. |
---
## 8. Phase 6 Deferred Items ## 8. Phase 6 Deferred Items
The following improvements are out of scope for Phase 6. They are documented here so the The following improvements are out of scope for Phase 6. They are documented here so the
+59
View File
@@ -326,3 +326,62 @@ None. SUMMARY.md files for plans 08-01 through 08-08 report no new attack surfac
| 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-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-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 | | 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 |
---
## Phase 12 — Cloud Resource Foundation (2026-06-19)
### Phase 12 Threat Register
| Threat ID | Threat | Disposition | Status | Evidence |
|-----------|--------|-------------|--------|----------|
| T-12-01 | IDOR / admin content access to cloud items | mitigate | CLOSED | `backend/tests/test_cloud_security.py` — `test_foreign_user_cannot_browse_cloud_item` (404 IDOR block), `test_admin_cannot_browse_cloud_connection` (403/404 admin block); `api/cloud/browse.py` — `get_regular_user` dep blocks all admin tokens |
| T-12-03 | Credential disclosure in browse response | mitigate | CLOSED | `backend/tests/test_cloud_security.py` — `test_browse_response_excludes_credentials_and_raw_fields` asserts `credentials_enc`, `access_token`, `refresh_token`, `client_secret` absent; `backend/tests/test_cloud.py` — `test_credentials_enc_not_exposed` for connection list endpoint |
| T-12-04 | SSRF via user-supplied WebDAV/Nextcloud URL | mitigate | CLOSED | `backend/tests/test_cloud_security.py` — `test_ssrf_url_validation_invariants` covers link-local, RFC1918, file://, metadata endpoints; `storage/cloud_utils.py:validate_cloud_url` — unchanged from Phase 5 |
| T-12-07 | Metadata loss on failed provider refresh | mitigate | CLOSED | `backend/tests/test_cloud_items.py` — `test_refresh_cloud_folder_failed_refresh_retains_cached_rows` asserts cached rows survive failed refresh |
| T-12-08 | XSS / raw provider error leakage | mitigate | CLOSED | `api/cloud/browse.py` — controlled error messages (`"Provider temporarily unavailable. Retrying in background."`) never raw exceptions; Vue template auto-escaping active in all components |
| T-12-09 | Quota corruption from browse (no-byte invariant) | mitigate | CLOSED | `backend/tests/test_cloud_security.py` — `test_browse_no_quota_mutation`, `test_browse_no_minio_calls`, `test_no_byte_download_during_browse`; `backend/tests/test_cloud_items.py` — `test_refresh_cloud_folder_no_byte_calls` |
| T-12-10 | Disabled control action executed by pointer/keyboard/touch | mitigate | CLOSED | `frontend/src/components/storage/__tests__/StorageBrowser.capabilities.test.js` — aria-disabled click, Enter, Space, and touchend all suppressed for unsupported/temporarily_unavailable states |
| T-12-SC | Supply-chain and committed secrets | mitigate | CLOSED | `bandit -r backend/ --severity-level medium`: 0 HIGH, 0 MEDIUM findings. `npm audit --audit-level=high`: 0 vulnerabilities. No new packages introduced in Phase 12. |
### Phase 12 Security Gate Evidence
**Gate 1 — Bandit (backend static analysis):**
```
bandit -r backend/ --severity-level medium --confidence-level medium -x tests/
No issues identified.
High: 0, Medium: 0
```
**Gate 2 — npm audit (frontend dependency scan):**
```
npm audit --audit-level=high
found 0 vulnerabilities
```
**Gate 3 — pip-audit:**
pip-audit was not installed in the local Python 3.9 environment. No new Python dependencies were added in Phase 12 (no new entries in `requirements.txt`). All existing dependencies were audited in Phase 8 with 0 critical/high CVEs. This is a local tooling gap, not a project-level vulnerability.
**Gate 4 — Security-invariant tests:**
```
backend/tests/test_cloud_security.py: 16 passed
backend/tests/test_cloud.py (browse security subset): all passing
Full backend suite: 509 passed, 1 pre-existing failure (test_extract_docx — missing libmagic, not Phase 12)
```
**Gate 5 — Admin content access:**
Admin tokens are blocked by `get_regular_user` dependency on all cloud browse and connection endpoints. Verified by `test_admin_cannot_browse_cloud_connection` (returns 403/404). No admin endpoint returns `credentials_enc`, cloud file bytes, or document content.
**Gate 6 — No committed secrets:**
Phase 12 adds no `.env` files, no hardcoded tokens, and no API keys. Credential encryption uses `CLOUD_CREDENTIAL_KEY` from env only. Session storage stores folder path strings only (verified by `test_session_storage_value_is_not_json_object` in cloudConnections tests).
### Phase 12 Unregistered Flags
None. Phase 12 adds a read-only cloud browse surface (no MinIO writes, no quota mutations). The session-storage folder path flag was captured in Phase 03 SUMMARY.md with evidence.
### Phase 12 Accepted Risks
| Risk ID | Component | Accepted Risk | Rationale |
|---------|-----------|---------------|-----------|
| T-12-SC | pip-audit tooling | pip-audit not installed in local Python 3.9 environment | No new Python packages added in Phase 12; existing packages audited in Phase 8. Operator should run pip-audit in CI or with Python 3.12 target environment. |
| T-12-disabled | DISABLED connection returns 200 with unavailable capabilities | Browse endpoint is accessible but all operations are unavailable | This is correct behavior: ownership is still asserted (user owns the connection), and capabilities convey the unavailability. No cloud bytes or credentials are served. |