| T-05-01-01 |
Tampering |
requirements.txt package names |
mitigate |
All 6 packages verified via slopcheck [OK] in RESEARCH.md — backend/requirements.txt lines 29-34 confirm cryptography>=41.0.0, google-auth-oauthlib>=1.3.1, google-api-python-client>=2.196.0, msal>=1.36.0, webdavclient3>=3.14.7, cachetools>=5.3.0 |
CLOSED |
| T-05-01-02 |
Information Disclosure |
config.py cloud_creds_key default |
mitigate |
Default "CHANGEME-32-bytes-padded!!" is clearly a placeholder — config.py:61 |
CLOSED |
| T-05-01-SC |
Tampering |
npm/pip/cargo installs |
mitigate |
All 6 new packages verified [OK] per RESEARCH.md slopcheck audit |
CLOSED |
| T-05-02-01 |
Tampering |
validate_cloud_url — DNS resolution |
mitigate |
socket.getaddrinfo resolves hostname to IP before blocked network check — cloud_utils.py:94-95; called before every request — webdav_backend.py:110,137,153,203,218 |
CLOSED |
| T-05-02-02 |
Information Disclosure |
_derive_fernet_key — HKDF instance reuse |
mitigate |
New HKDF(...) instance created on every _derive_fernet_key call — cloud_utils.py:133-141; AlreadyFinalized pitfall avoided by construction |
CLOSED |
| T-05-02-03 |
Information Disclosure |
cloud_creds_key default value |
mitigate |
Default "CHANGEME-32-bytes-padded!!" is clearly a placeholder — config.py:61 |
CLOSED |
| T-05-02-04 |
Elevation of Privilege |
get_storage_backend_for_document — cross-user |
mitigate |
CloudConnection query includes CloudConnection.user_id == user.id filter — storage/__init__.py:93 |
CLOSED |
| T-05-02-SC |
Tampering |
cachetools package install |
mitigate |
cachetools>=5.3.0 verified [OK] — requirements.txt:34 |
CLOSED |
| T-05-03-01 |
Elevation of Privilege |
GoogleDriveBackend — token in credentials dict |
mitigate |
Credentials dict never logged; decryption only in factory; tokens only in memory — google_drive_backend.py:69-81; no serialization path back to API response |
CLOSED |
| T-05-03-02 |
Spoofing |
OneDriveBackend — invalid_grant detection |
mitigate |
result.get("error") == "invalid_grant" raises CloudConnectionError — onedrive_backend.py:118; propagated to API layer _call_cloud_op — cloud.py:162-177 |
CLOSED |
| T-05-03-03 |
Denial of Service |
OneDriveBackend — 10MB chunked upload |
accept |
10 MB chunks within Microsoft Graph recommended range; no larger chunks causing memory pressure |
CLOSED |
| T-05-03-04 |
Information Disclosure |
GoogleDriveBackend — file names in Drive |
accept |
Drive file named {document_id}{extension} — no human filename in provider storage |
CLOSED |
| T-05-03-05 |
Tampering |
cache_discovery=False in Google Drive build() |
mitigate |
cache_discovery=False on all build() calls — google_drive_backend.py:104; cloud.py:843 |
CLOSED |
| T-05-04-01 |
Tampering |
WebDAVBackend — SSRF via server_url |
mitigate |
validate_cloud_url(server_url) in init — webdav_backend.py:63; AND before every asyncio.to_thread call — lines 110,137,153,203,218 |
CLOSED |
| T-05-04-02 |
Tampering |
DNS rebinding on WebDAV requests |
mitigate |
validate_cloud_url called before each request (not only at connect-time) — webdav_backend.py:110,137,153,203,218; nextcloud_backend.py:91,113 |
CLOSED |
| T-05-04-03 |
Information Disclosure |
WebDAV path includes user_id/document_id |
accept |
object_key = "docuvault/{user_id}/{document_id}{ext}" — no human filename |
CLOSED |
| T-05-04-04 |
Denial of Service |
Nextcloud list_folder fetching info per item |
accept |
TTLCache (cloud_cache.py:31) prevents repeated list_folder calls within 60s |
CLOSED |
| T-05-04-05 |
Tampering |
webdavclient3 path traversal via object_key |
mitigate |
put_object constructs object_key from UUID user_id/document_id via _make_path — webdav_backend.py:89-91; get/delete receive object_key from DB, not user input |
CLOSED |
| T-05-05-01 |
Tampering |
OAuth callback CSRF |
mitigate |
secrets.token_urlsafe(32) state token stored in Redis — cloud.py:358-360; validated at callback line 442; deleted single-use at line 452 |
CLOSED |
| T-05-05-02 |
Elevation of Privilege |
OAuth callback state token leak |
mitigate |
Redis TTL 1800s — cloud.py:360 (setex with TTL); key deleted after single use line 452; never returned to browser |
CLOSED |
| T-05-05-03 |
Information Disclosure |
CloudConnectionOut in API responses |
mitigate |
CloudConnectionOut imported from api.admin — same whitelist enforced everywhere — cloud.py:35; admin.py:149-173 (credentials_enc absent by design) |
CLOSED |
| T-05-05-04 |
Information Disclosure |
Cloud connection ID enumeration |
mitigate |
DELETE /connections/{id} returns 404 for wrong-owner — cloud.py:744-745 |
CLOSED |
| T-05-05-05 |
Tampering |
WebDAV server_url SSRF |
mitigate |
validate_cloud_url called before WebDAV backend instantiation — cloud.py:577; also in init and before each request — webdav_backend.py |
CLOSED |
| T-05-05-06 |
Spoofing |
Admin access to cloud endpoints |
mitigate |
get_regular_user raises 403 for admin role — deps/auth.py:104-108; used on all cloud endpoints — cloud.py:318,557,646,680,732,777,931 |
CLOSED |
| T-05-05-07 |
Information Disclosure |
OAuth error message in redirect URL |
accept |
Error only shown to authenticated user; no PII/secrets in the error string |
CLOSED |
| T-05-05-08 |
Information Disclosure |
write_audit_log metadata for cloud.connected |
mitigate |
Audit metadata_ = {"provider": provider} only — cloud.py:532,632,762 — no credentials, no tokens |
CLOSED |
| T-05-06-01 |
Spoofing |
target_backend form field tampering |
mitigate |
target_backend validated against _CLOUD_PROVIDERS frozenset — documents.py:60,187-190; invalid → 422 |
CLOSED |
| T-05-06-02 |
Information Disclosure |
CloudConnectionError message in 503 |
mitigate |
503 detail = static safe string — documents.py:252-254; 756; no provider error detail |
CLOSED |
| T-05-06-03 |
Denial of Service |
Cloud upload quota bypass |
accept |
Cloud uploads do not consume MinIO quota (D-11: separate backends); cloud storage quotas are provider-side |
CLOSED |
| T-05-06-04 |
Tampering |
Test mocks hiding real failures |
mitigate |
Tests mock at SDK boundary, not function level — confirmed in 05-06-SUMMARY key-decisions |
CLOSED |
| T-05-07-01 |
Information Disclosure |
OAuth tokens in browser JavaScript |
mitigate |
OAuth initiation now uses authenticated fetch() + window.location.href = data.url — SettingsCloudTab.vue:262-263; tokens never land in frontend JS |
CLOSED |
| T-05-07-02 |
XSS |
?cloud_error= decoded and displayed |
mitigate |
Vue template auto-escaping {{ oauthError }} — SettingsView.vue:64; no v-html used |
CLOSED |
| T-05-07-03 |
Information Disclosure |
WebDAV password in component state |
accept |
Password in ref() only during modal interaction; cleared on close/submit; never persisted in localStorage |
CLOSED |
| T-05-07-04 |
Information Disclosure |
connection.credentials_enc in store |
mitigate |
CloudConnectionOut API never includes credentials_enc; store.connections holds only safe fields — admin.py:149-173 |
CLOSED |
| T-05-08-01 |
Information Disclosure |
CloudProviderTreeItem — folder names in DOM |
accept |
Folder names are user's own content; displayed only to authenticated user |
CLOSED |
| T-05-08-02 |
Denial of Service |
Sidebar fetch on mount |
mitigate |
fetchConnections called once on AppSidebar mount — AppSidebar.vue:281; TTLCache on server prevents repeated API calls within 60s |
CLOSED |
| T-05-08-03 |
Spoofing |
CloudFolderTreeItem folder navigation URL |
accept |
folder_id from API response, never user-typed input |
CLOSED |
| T-05-08-04 |
Information Disclosure |
AppSidebar shows ACTIVE connections |
mitigate |
Only ACTIVE connections shown — AppSidebar.vue:261-262 filters status === 'ACTIVE' |
CLOSED |
| T-05-09-01 |
Spoofing |
PATCH /api/documents/{id} |
mitigate |
get_regular_user enforced on PATCH endpoint; admin → 403; wrong owner → 404 — documents.py (ownership guard) |
CLOSED |
| T-05-09-02 |
Information Disclosure |
PATCH response |
mitigate |
storage.get_metadata() whitelist used for response — documents.py PATCH handler |
CLOSED |
| T-05-09-03 |
Tampering |
Celery task cloud credentials |
mitigate |
Credentials loaded from DB inside task; no credentials in broker message — document_tasks.py uses get_storage_backend_for_document which decrypts from DB |
CLOSED |
| T-05-09-04 |
Information Disclosure |
fetchDocumentContent Blob URL |
accept |
Blob URL is same-origin, revoked on unmount |
CLOSED |
| T-05-09-SC |
Tampering |
npm/pip installs |
mitigate |
No new packages installed in Plan 09 |
CLOSED |
| T-05-10-01 |
Spoofing |
oauth_initiate auth |
mitigate |
get_regular_user enforced on OAuth initiation — cloud.py:318 |
CLOSED |
| T-05-10-02 |
Information Disclosure |
OAuth URL in JSON response |
accept |
Standard OAuth URL with CSRF state token; no credentials in URL |
CLOSED |
| T-05-10-03 |
Tampering |
OAuth state token |
mitigate |
State token server-side via secrets.token_urlsafe(32) — cloud.py:358; Redis TTL 1800 — line 360; single-use deletion — line 452 |
CLOSED |
| T-05-10-04 |
Spoofing |
Nextcloud custom endpoint re-edit |
accept |
Pre-populated from encrypted DB credentials via /config endpoint; password not returned |
CLOSED |
| T-05-10-SC |
Tampering |
npm/pip installs |
mitigate |
No new packages installed in Plan 10 |
CLOSED |
| T-05-11-01 |
Elevation of Privilege |
DELETE /api/admin/users/{id} |
mitigate |
Requires get_current_admin AND correct admin password via pwdlib Argon2 — admin.py:499 verify_password called before any destructive action |
CLOSED |
| T-05-11-02 |
Information Disclosure |
Wrong password error message |
mitigate |
403 "Invalid admin password" regardless of user existence — admin.py:500-503; password check is fail-fast before user lookup |
CLOSED |
| T-05-11-03 |
Tampering |
admin_password in request body |
mitigate |
Pydantic UserDeleteConfirm validates presence — admin.py:141-144; constant-time Argon2 comparison via verify_password — admin.py:499 |
CLOSED |
| T-05-11-04 |
Repudiation |
User deletion audit trail |
mitigate |
write_audit_log("admin.user_deleted") written before session.delete — admin.py:568-575 |
CLOSED |
| T-05-11-05 |
Denial of Service |
Repeated wrong-password delete attempts |
accept |
Admin endpoints rate-limited; admin accounts are trusted actors |
CLOSED |
| T-05-11-SC |
Tampering |
npm/pip installs |
mitigate |
No new packages installed in Plan 11 |
CLOSED |
| T-05-12-01 |
Information Disclosure |
400 error message for missing creds |
mitigate |
Message names env vars (server config) not user data — cloud.py:343-347 (Google), 349-356 (OneDrive) |
CLOSED |
| T-05-12-02 |
Information Disclosure |
502 error message |
mitigate |
Static string "Cloud backend unreachable" — documents.py:763; no stack trace leaked |
CLOSED |
| T-05-12-03 |
Tampering |
celery-worker volume mount |
accept |
Bind mount = developer-controlled source files; production uses image builds |
CLOSED |
| T-05-12-SC |
Tampering |
npm/pip installs |
mitigate |
No new packages installed in Plan 12 |
CLOSED |