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
The following improvements are out of scope for Phase 6. They are documented here so the