chore(13-11): bump to v0.3.0, update docs, roadmap, and Phase 13 security gate evidence
- Version bump: 0.2.6 → 0.3.0 (Phase 13 complete — full cloud mutation surface shipped) - CLAUDE.md: update current-state, shared module map, and Phase 13 non-negotiable rules - README.md: add cloud file management, connection health, and authorized preview features; add Phase 13 mutation API table; mark Phase 13 complete - ROADMAP.md: mark Phase 13 11/11 plans complete - SECURITY.md: add Phase 13 threat register (T-13-01 through T-13-34), gate evidence (766 backend + 429 frontend tests pass, bandit 0 HIGH, npm audit 0 high/critical, gitleaks 3 pre-existing)
This commit is contained in:
@@ -24,7 +24,7 @@ Before any phase is marked complete:
|
||||
| Phase | Name | Goal | Requirements |
|
||||
|------:|------|------|--------------|
|
||||
| 12 | 6/6 | Complete | 2026-06-21 |
|
||||
| 13 | 10/11 | In Progress| |
|
||||
| 13 | 11/11 | Complete | 2026-06-23 |
|
||||
| 14 | Selective Analysis and Byte Cache | Analyze selected cloud scopes through cancellable jobs backed by bounded, private, on-demand byte caching | ANALYZE-01..07, CACHE-03..05 |
|
||||
| 15 | Unified Smart Search | Search local and analyzed cloud documents by exact text or semantic ideas without downloading files during queries | SEARCH-01..07 |
|
||||
| 16 | Change Tracking and Reliability | Detect external provider changes, mark stale indexes, remove deleted items, and harden refresh behavior | SYNC-02..04 |
|
||||
@@ -80,7 +80,7 @@ Plans:
|
||||
|
||||
**Depends on:** Phase 12
|
||||
**Requirements:** CONN-01, CONN-02, CONN-03, CLOUD-02, CLOUD-03, CLOUD-04, CLOUD-05, CLOUD-06, CLOUD-07, CLOUD-09
|
||||
**Plans:** 10/11 plans executed
|
||||
**Plans:** 11/11 plans complete
|
||||
**Execution waves:** Wave 0: 13-01 and 13-02 in parallel; Wave 1: 13-03 after 13-01; Wave 2: 13-04 after 13-01 and 13-03; Wave 3: 13-05 after 13-01, 13-03, and 13-04; Wave 4: 13-06 after 13-05; Wave 5: 13-07 and 13-08 in parallel (13-07 after 13-02, 13-04, and 13-06; 13-08 after 13-03, 13-04, and 13-06); Wave 6: 13-09 after 13-08; Wave 7: 13-10 after 13-02, 13-04, 13-07, and 13-09; Wave 8: 13-11 after 13-03 through 13-10.
|
||||
|
||||
Plans:
|
||||
@@ -95,7 +95,7 @@ Plans:
|
||||
- [x] 13-08-PLAN.md — Implement backend create-folder and rename semantics with collision, stale, and stable-identity safeguards.
|
||||
- [x] 13-09-PLAN.md — Implement backend move and delete semantics with same-connection, disclosure, security, and audit safeguards.
|
||||
- [x] 13-10-PLAN.md — Finish shared-browser mutation UX, store-backed health behavior, reconnect copy, and the no-probe invariant.
|
||||
- [ ] 13-11-PLAN.md — Run closeout-only docs, versions, full gates, explicit secret scan, and ship-readiness checks.
|
||||
- [x] 13-11-PLAN.md — Run closeout-only docs, versions, full gates, explicit secret scan, and ship-readiness checks.
|
||||
|
||||
**Success Criteria:**
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
DocuVault is a multi-user SaaS document management platform built on FastAPI (Python) + Vue 3. It handles document upload, text extraction (PDF/DOCX/image/text), AI-based topic classification, per-user isolated storage, folder organization, document sharing, and pluggable cloud storage backends (OneDrive, Google Drive, Nextcloud, WebDAV).
|
||||
|
||||
**Current state:** v0.2.6 — Phase 13 Plan 05 complete 2026-06-22. Upload mechanics slice: `keep_both_name()` helper in `services/cloud_operations.py`; typed upload conflict/retry/reauth results across all 4 providers; 37 upload-specific tests pass. Not cleared for public internet deployment.
|
||||
**Current state:** v0.3.0 — Phase 13 complete 2026-06-23. Full virtual-local cloud operations: connection health/reconnect/disconnect, authorized open/preview, sequential upload queue with typed conflict resolution, create-folder/rename with collision retry and stale guard, move with descendant safety, delete with disclosure confirmation, and metadata-only audit events. 766 backend + 429 frontend tests pass. Not cleared for public internet deployment.
|
||||
|
||||
## Stack
|
||||
|
||||
@@ -43,8 +43,10 @@ Before adding a helper, check if it belongs in an existing shared module:
|
||||
| `backend/storage/cloud_base.py` | `CloudResourceAdapter`, `CloudListing`, `CloudResource`, `CloudCapability` — Phase 12 browse contract; all 4 providers implement this |
|
||||
| `backend/storage/cloud_utils.py` | `validate_cloud_url`, `normalize_nextcloud_url`, `encrypt_credentials`, `decrypt_credentials` — Phase 12.1 adds `normalize_nextcloud_url` |
|
||||
| `backend/services/cloud_items.py` | `resolve_owned_connection`, `upsert_cloud_item`, `reconcile_cloud_listing`, `get_or_create_folder_state`, `apply_listing_and_finalize`, `ListingResult` — owner-scoped cloud metadata service; `apply_listing_and_finalize` is the single shared freshness gate |
|
||||
| `backend/services/cloud_operations.py` | `keep_both_name(filename, counter)` — canonical D-03/D-05 keep-both collision naming (counter before first extension); upload queue uses this for conflict resolution |
|
||||
| `backend/api/cloud/schemas.py` | `CloudBrowseResponse`, `CloudItemOut`, `FolderFreshnessOut`, `ConnectionRenameRequest` — credential-free cloud response schemas |
|
||||
| `backend/services/cloud_operations.py` | `keep_both_name(filename, counter)` — canonical D-03/D-05 keep-both collision naming (counter before first extension); `run_health_check`, `run_reconnect`, `run_upload`, `run_create_folder`, `run_rename`, `run_move`, `run_delete` — all mutation orchestration with stale guard, reconciliation, and metadata-only audit |
|
||||
| `backend/api/cloud/schemas.py` | `CloudBrowseResponse`, `CloudItemOut`, `FolderFreshnessOut`, `ConnectionRenameRequest`, `CloudMutationResult`, `CloudConflictResult`, `CloudUploadResult` — credential-free cloud response schemas |
|
||||
| `backend/storage/cloud_base.py` | Phase 13 adds `CloudMutableAdapter` extending `CloudResourceAdapter` — normalized mutation contract (upload, create_folder, rename, move, delete) with `CloudMutationResult` typed returns; all 4 providers implement this |
|
||||
| `backend/api/cloud/operations.py` | Owner-scoped cloud mutation routes: `POST /upload`, `POST /folders`, `PATCH /rename`, `POST /move`, `DELETE /items/{id}`, `POST /connections/{id}/test`, `POST /connections/{id}/reconnect`, `GET /connections/{id}/open/{item_id}`, `GET /connections/{id}/download/{item_id}` |
|
||||
|
||||
**Rules:**
|
||||
- No router may define `_ip()`, `_get_ip()`, or any other local variant of `get_client_ip`. Import from `deps.utils`.
|
||||
@@ -54,6 +56,10 @@ Before adding a helper, check if it belongs in an existing shared module:
|
||||
- No API file may define `_validate_password_strength`. Import from `services.auth`.
|
||||
- Service layer raises `ValueError` (or domain exceptions), never `HTTPException`. Only the router layer raises `HTTPException`.
|
||||
- No caller of `adapter.list_folder` may independently call `update_folder_state(refresh_state="fresh")`. Callers must use `apply_listing_and_finalize` from `services.cloud_items`.
|
||||
- Phase 13 mutation responses use `JSONResponse` (not `HTTPException`) so `kind`/`reason` appear at the top level of the response body, not nested under `detail`.
|
||||
- All cloud mutation orchestration (health, reconnect, upload, create-folder, rename, move, delete) routes through `services.cloud_operations` — never inline in the router.
|
||||
- No mutation route may independently manage folder state after a mutation. Reconciliation (upsert_cloud_item + update_folder_state) is called only via `services.cloud_operations` mutation helpers.
|
||||
- `testCloudConnection` is an explicit user-initiated action only — never called as a side effect of folder navigation (D-13).
|
||||
|
||||
### Frontend: shared module map
|
||||
|
||||
@@ -123,9 +129,9 @@ This project uses the GSD (Get Shit Done) planning workflow. Planning artifacts
|
||||
/gsd:progress — check status and advance workflow
|
||||
```
|
||||
|
||||
### Current state: v0.2.1 — Phase 12 gap-closure complete (2026-06-20)
|
||||
### Current state: v0.3.0 — Phase 13 complete (2026-06-23)
|
||||
|
||||
All phases are marked complete in `.planning/ROADMAP.md`. The app is functional but alpha-quality — not cleared for public internet deployment. The next milestone has not been started.
|
||||
Phase 13 (virtual-local cloud operations) complete. Cloud browse, health/reconnect/disconnect, authorized open/preview, upload queue, create-folder, rename, move, and delete are all shipped. Phase 14 (selective analysis and byte cache) is next. Not cleared for public internet deployment.
|
||||
|
||||
## Development Setup
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# DocuVault
|
||||
|
||||
**Version 0.2.6 — Alpha**
|
||||
**Version 0.3.0 — Alpha**
|
||||
|
||||
> **Not production-ready.** DocuVault is functional for local and self-hosted use but has not been audited or hardened for public internet exposure. APIs, environment variables, and the database schema may change without notice until a stable 1.0 release is declared.
|
||||
|
||||
@@ -19,6 +19,9 @@ A self-hosted, multi-user document management platform with AI-powered topic cla
|
||||
- **Cloud storage backends** — connect OneDrive, Google Drive, Nextcloud, or any WebDAV server as a personal storage backend; credentials encrypted with HKDF per-user keys
|
||||
- **Unified connection-root browsing** — each connected account appears as a distinct top-level root navigable by connection UUID; duplicate same-provider accounts are disambiguated automatically; local and cloud files share one row and action implementation
|
||||
- **Capability-aware actions** — unsupported cloud actions render gray with accessible explanations; temporarily unavailable actions render amber; all remain keyboard-focusable
|
||||
- **Cloud file management** — upload files into cloud folders via a sequential queue with typed conflict resolution (keep-both/replace/skip/cancel-all); create folders with bounded collision-retry naming; rename items; move items within a connection; delete with trash-or-permanent disclosure
|
||||
- **Connection health and reconnect** — per-connection health badge and error diagnostics in Settings; auto-test after connect/reconnect; Reconnect and requires-reauth banners in the shared browser; no health probe on folder navigation (D-13)
|
||||
- **Authorized cloud preview** — open and preview supported binary formats (PDF/images) through DocuVault's authorized proxy; Office/Workspace formats fall back to an ownership-checked authorized download endpoint; provider credentials and raw provider URLs are never exposed
|
||||
- **Auth** — email/password registration with HIBP breach check, optional TOTP 2FA, 8–10 backup codes, password reset by email, sign-out-all-devices
|
||||
- **Admin panel** — user CRUD, quota assignment, per-user AI provider override, AI provider system configuration, audit log viewer with CSV export
|
||||
- **Observability** — structured JSON logging via structlog, per-request correlation IDs, Loki + Promtail + Grafana stack included in Docker Compose
|
||||
@@ -321,6 +324,20 @@ Each connected account is independently addressable by its connection UUID:
|
||||
| `PATCH /api/cloud/connections/{id}` | Rename connection display name |
|
||||
| `DELETE /api/cloud/connections/{id}` | Remove connection and credentials |
|
||||
|
||||
### Cloud Mutation API (Phase 13)
|
||||
|
||||
| Endpoint | Purpose |
|
||||
|----------|---------|
|
||||
| `POST /api/cloud/connections/{id}/test` | Explicit connection health test (never called on navigate) |
|
||||
| `POST /api/cloud/connections/{id}/reconnect` | Reconnect with credentials refresh; preserves cached metadata |
|
||||
| `GET /api/cloud/connections/{id}/open/{item_id}` | Authorized open/preview proxy — never exposes raw provider URL |
|
||||
| `GET /api/cloud/connections/{id}/download/{item_id}` | Authorized download fallback for unsupported preview formats |
|
||||
| `POST /api/cloud/connections/{id}/upload` | Upload file; returns typed result (success/conflict/offline/reauth_required) |
|
||||
| `POST /api/cloud/connections/{id}/folders` | Create folder with bounded collision-retry naming |
|
||||
| `PATCH /api/cloud/connections/{id}/items/{item_id}/rename` | Rename; stale guard stops on external change |
|
||||
| `POST /api/cloud/connections/{id}/items/{item_id}/move` | Move within same connection; descendant check rejects self-moves |
|
||||
| `DELETE /api/cloud/connections/{id}/items/{item_id}` | Delete (trash or permanent depending on provider capability) |
|
||||
|
||||
Browsing returns durable cached rows immediately and schedules a background `refresh_cloud_folder` Celery task to reconcile provider changes. First-visit fetches are synchronous and bounded. All browse responses are credential-free — `credentials_enc` is never serialized in the response.
|
||||
|
||||
**Freshness states:** Browse responses include `freshness.refresh_state` (`fresh` | `warning` | `refreshing` | `stale`). `warning` means the last reconciliation was incomplete — cached rows remain visible. The frontend maps server freshness verbatim; it never infers `fresh` from an HTTP 200 alone.
|
||||
@@ -339,7 +356,7 @@ cd backend && pytest -m live_nextcloud tests/test_nextcloud_live.py
|
||||
|
||||
The live suite is excluded from ordinary CI runs. It is read-only — PROPFIND metadata requests only. No bytes are downloaded and no provider mutations are made. Missing variables produce a safe skip.
|
||||
|
||||
**Phase 13/14 not yet implemented:** Cloud file upload, rename, move, delete, and create-folder (Phase 13) and byte download/cache (Phase 14) are future work. Current browse is read-only.
|
||||
**Phase 13 complete (v0.3.0):** Cloud file upload, rename, move, delete, and create-folder are all implemented. See Cloud file management feature above. Phase 14 (byte download/cache for offline preview and selective analysis) remains future work.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+106
@@ -493,3 +493,109 @@ Phase 12.1 scopes only read-only browse, freshness truthfulness, and cross-provi
|
||||
- **Phase 13 mutations excluded:** upload, rename, move, delete, create-folder are not implemented
|
||||
- **Phase 14 byte cache excluded:** no document content download, local copy, or cache layer
|
||||
- No threat for cloud mutations (T-13-xx) or byte cache (T-14-xx) is evaluated in Phase 12.1
|
||||
|
||||
---
|
||||
|
||||
## Phase 13 — Virtual-Local Cloud Operations (2026-06-23)
|
||||
|
||||
**Audit date:** 2026-06-23
|
||||
**Phase:** 13 — Virtual-Local Cloud Operations (plans 13-01 through 13-11)
|
||||
**ASVS Level:** L2
|
||||
**Auditor:** gsd-security-auditor (claude-sonnet-4-6)
|
||||
**Phase scope:** connection health/reconnect/disconnect, authorized open/preview, sequential upload queue with typed conflict resolution, create-folder and rename with collision retry and stale guard, move with descendant safety and cross-connection block, delete with typed disclosure, metadata-only audit events across all 4 providers.
|
||||
|
||||
### Phase 13 Threat Register
|
||||
|
||||
| Threat ID | Threat | Disposition | Status | Evidence |
|
||||
|-----------|--------|-------------|--------|----------|
|
||||
| T-13-01 | IDOR — cloud mutation crosses user boundary | mitigate | CLOSED | `resolve_owned_connection` in `services/cloud_operations.py` — ownership asserted before any mutation; `test_foreign_user_cannot_browse_cloud_item`, `test_admin_cannot_browse_cloud_connection` still pass |
|
||||
| T-13-02 | Credential exposure in mutation response | mitigate | CLOSED | All mutation endpoints return `JSONResponse` with `CloudMutationResult`-shaped body; `credentials_enc`, `access_token`, `refresh_token` absent; `test_cloud_security.py::test_browse_response_excludes_credentials_and_raw_fields` still passes |
|
||||
| T-13-03 | SSRF via reconnect or upload URL | mitigate | CLOSED | WebDAV/Nextcloud upload and reconnect paths reuse `validate_cloud_url` from `storage.cloud_utils`; `test_ssrf_url_validation_invariants` still passes |
|
||||
| T-13-04 | Audit log contains provider bytes or credentials | mitigate | CLOSED | `write_audit_log` in `services/cloud_operations.py` writes only `metadata_`-level fields (`kind`, `provider_item_id`, `display_name`, `byte_size`); `test_cloud_audit.py::test_upload_success_writes_metadata_only_audit_row`, `test_move_audit_contains_no_credentials`, `test_delete_audit_contains_no_bytes` |
|
||||
| T-13-11 | Credential plaintext at provider boundary | mitigate | CLOSED | Credentials decrypted inside `run_*` helpers only; decrypted value not returned in any `CloudMutationResult`; `test_cloud_mutations.py` asserts typed results contain no token fields |
|
||||
| T-13-13 | Health check exposes connection status to wrong user | mitigate | CLOSED | `POST /connections/{id}/test` asserts ownership via `resolve_owned_connection`; returns `{"state": ..., "error_code": ..., "error_message": ...}` only (no credentials); `test_cloud_reconnect.py::test_health_check_wrong_owner_403` |
|
||||
| T-13-14 | Reconnect stores tokens in browser/response | mitigate | CLOSED | `run_reconnect` persists refreshed credentials via `encrypt_credentials`; response is `{"status": "active"}` only; `test_cloud_reconnect.py::test_reconnect_persists_token_not_exposes_it` |
|
||||
| T-13-15 | Drive broader-scope consent missing from UI | mitigate | CLOSED | `SettingsCloudTab.vue` contains `data-test="gdrive-scope-notice"` with "all files in your Google Drive" copy (D-17); `SettingsCloudTab.health.test.js::test_gdrive_scope_notice` |
|
||||
| T-13-16 | Silent overwrite on name conflict | mitigate | CLOSED | All 4 provider upload implementations return `conflict` kind with `reason: "name_conflict"` when a file exists at the target name; StorageBrowser pauses queue for user resolution; `test_cloud_backends.py::*_upload_conflict_*` |
|
||||
| T-13-17 | Raw provider URL returned from open/download | mitigate | CLOSED | `GET /connections/{id}/open/{item_id}` and `/download/{item_id}` proxy bytes through DocuVault and return `application/octet-stream` or `application/pdf`; no `Location` header with provider URL; `test_cloud_mutations.py::test_open_file_never_returns_provider_url` |
|
||||
| T-13-18 | WebDAV SSRF bypass through upload path | mitigate | CLOSED | `WebDAVBackend.upload` calls `validate_cloud_url` on the resolved upload target before submission; `test_webdav_backend.py::test_upload_rejects_ssrf_url` |
|
||||
| T-13-19 | Upload reconciliation bypass | mitigate | CLOSED | `run_upload` in `services/cloud_operations.py` calls `upsert_cloud_item` then `update_folder_state` only on `MUT_KIND_UPLOADED`; non-success branches bypass reconciliation; `test_cloud_mutations.py::test_upload_conflict_skips_reconcile` |
|
||||
| T-13-20 | Audit payload secrecy (upload) | mitigate | CLOSED | `metadata_` dict contains only `kind`, `provider_item_id`, `display_name`, `byte_size`; `test_cloud_audit.py::test_upload_success_writes_metadata_only_audit_row` asserts no token/url/content |
|
||||
| T-13-21 | False success audit event on failure path | mitigate | CLOSED | Conflict, offline, and reauth_required branches in `run_upload` return before `write_audit_log`; `test_cloud_audit.py::test_upload_conflict_writes_no_audit_row` |
|
||||
| T-13-22 | Queue resume flow implicit | mitigate | CLOSED | StorageBrowser requires explicit `upload-queue-resolve` event for all five resolution actions; no implicit or silent paths; `StorageBrowser.capabilities.test.js::test_queue_requires_explicit_resolution` |
|
||||
| T-13-23 | Preview/download UI exposes provider URL | mitigate | CLOSED | `onFileOpen` calls `api.openCloudFile` (DocuVault-authorized endpoint); `window.open()` to provider URL is forbidden by rule (D-02/T-13-07); `CloudFolderRenderedFlow.test.js::test_open_uses_open_cloud_file_api` |
|
||||
| T-13-24 | Collision naming unbounded | mitigate | CLOSED | `run_create_folder` and `run_rename` use bounded retry loop (≤5 attempts) with `keep_both_name()`; collision exhaustion returns typed error; `test_cloud_mutations.py::test_create_folder_collision_exhaustion` |
|
||||
| T-13-25 | Stale mutation creates duplicate on external change | mitigate | CLOSED | Stale guard in `run_create_folder`, `run_rename` calls `update_folder_state(refresh_state="warning")` and returns `stale` kind before provider submission; `test_cloud_mutations.py::test_create_folder_stale_guard_*`, `test_rename_stale_guard_*` |
|
||||
| T-13-26 | Identity reconciliation missing after create/rename | mitigate | CLOSED | `run_create_folder` and `run_rename` call `upsert_cloud_item` with returned provider_item_id before returning success; `test_cloud_mutations.py::test_create_folder_reconciles_item`, `test_rename_reconciles_item` |
|
||||
| T-13-27 | Move to descendant or cross-connection | mitigate | CLOSED | `run_move` checks descendant chain + self-move + connection-id equality before provider submission; `test_cloud_mutations.py::test_move_rejects_descendant_destination`, `test_move_rejects_cross_connection` |
|
||||
| T-13-28 | Stale move operates on changed item | mitigate | CLOSED | Stale guard in `run_move` stops mutation, refreshes source folder state, returns `stale` kind; `test_cloud_mutations.py::test_move_stale_guard_*` |
|
||||
| T-13-29 | Delete audit leaks bytes or credentials | mitigate | CLOSED | Typed delete results carry `is_folder`/`item_kind`; audit metadata contains no token or byte; `test_cloud_audit.py::test_delete_audit_contains_no_bytes` |
|
||||
| T-13-30 | Health UX auto-probes on navigate | mitigate | CLOSED | `testConnection` is a store action never called from navigation; `CloudFolderRenderedFlow.test.js::no_health_probe_on_folder_navigate_D13` asserts `testCloudConnection` never called during browse |
|
||||
| T-13-31 | Destructive cloud action without disclosure | mitigate | CLOSED | Delete requires `is_folder`/`supports_trash` disclosure to UI before confirmation; StorageBrowser renders permanent-delete warning for non-trash providers; `CloudFolderRenderedFlow.test.js::test_delete_shows_folder_warning` |
|
||||
| T-13-32 | Google Drive broader scope no consent copy | mitigate | CLOSED | `data-test="gdrive-scope-notice"` with "all files in your Google Drive" copy confirmed in SettingsCloudTab; `SettingsCloudTab.health.test.js::test_gdrive_scope_notice` |
|
||||
| T-13-33 | Closeout docs claim unshipped features | mitigate | CLOSED | Phase 13 closeout plan is isolated to documentation/version/gate tasks only; no implementation work in plan 13-11 |
|
||||
| T-13-34 | Secret scan skipped at release | mitigate | CLOSED | Secret scan via `gitleaks detect --redact` run locally; 3 pre-existing findings (all from before Phase 13, none from Phase 13 files); no hardcoded API keys, tokens, or credentials in any Phase 13 file |
|
||||
|
||||
### Phase 13 Security Gate Evidence
|
||||
|
||||
**Gate 1 — Full backend test suite:**
|
||||
```
|
||||
docker compose run --rm backend pytest -q --tb=no
|
||||
766 passed, 17 skipped, 4 deselected, 10 xfailed
|
||||
(1 pre-existing failure: test_extract_docx — ModuleNotFoundError: No module named 'docx', libmagic/python-docx not installed in container; unrelated to Phase 13)
|
||||
```
|
||||
|
||||
**Gate 2 — Frontend test suite:**
|
||||
```
|
||||
cd frontend && npm run test
|
||||
429 passed (48 test files)
|
||||
```
|
||||
|
||||
**Gate 3 — Bandit (backend static analysis):**
|
||||
```
|
||||
python3 -m bandit -r backend/ --exclude backend/tests
|
||||
Total issues (by severity): Undefined: 0, Low: 11, Medium: 0, High: 0
|
||||
Total issues (by confidence): Undefined: 0, Low: 0, Medium: 2, High: 9
|
||||
(All High-confidence findings are Low-severity — pre-existing pattern; 0 HIGH severity findings)
|
||||
```
|
||||
|
||||
**Gate 4 — npm audit (frontend dependency scan):**
|
||||
```
|
||||
cd frontend && npm audit --audit-level=high
|
||||
found 0 vulnerabilities
|
||||
```
|
||||
|
||||
**Gate 5 — pip-audit (backend dependency scan):**
|
||||
pip-audit not installable via local Python 3.9 (requirements.txt targets Python 3.12). No new Python packages added in Phase 13 plans 03 through 11. Existing packages audited in Phase 8 (0 critical/high CVEs). Security-critical packages remain pinned: PyJWT 2.12+, pwdlib[argon2], cryptography, pyotp, slowapi. This is the same local tooling gap documented in Phase 12.
|
||||
|
||||
**Gate 6 — Hardcoded-secret scan:**
|
||||
```
|
||||
gitleaks detect --redact
|
||||
3 findings — all pre-existing in commits from before Phase 13 (auth.test.js 2026-05-31, test_cloud_utils.py 2026-05-28, test_auth_deps.py 2026-05-22). No findings in any Phase 13 file.
|
||||
```
|
||||
Additionally: `git ls-files '.env' '.env.*'` returns only `.env.example` (no `.env` tracked).
|
||||
|
||||
**Gate 7 — Owner/admin/credential-secrecy invariants:**
|
||||
- `test_cloud_security.py::test_foreign_user_cannot_browse_cloud_item` — PASS (IDOR block)
|
||||
- `test_cloud_security.py::test_admin_cannot_browse_cloud_connection` — PASS (admin block)
|
||||
- `test_cloud_security.py::test_browse_response_excludes_credentials_and_raw_fields` — PASS (no credentials in response)
|
||||
- `test_cloud_security.py::test_ssrf_url_validation_invariants` — PASS (SSRF protection)
|
||||
- `test_cloud_audit.py::test_upload_success_writes_metadata_only_audit_row` — PASS (audit secrecy)
|
||||
- `CloudFolderRenderedFlow.test.js::no_health_probe_on_folder_navigate_D13` — PASS (no-probe invariant)
|
||||
|
||||
**Gate 8 — docker compose config:**
|
||||
```
|
||||
docker compose config --quiet
|
||||
(no output — all required environment variables resolve without error)
|
||||
```
|
||||
|
||||
### Phase 13 Accepted Risks
|
||||
|
||||
| Risk ID | Component | Accepted Risk | Rationale |
|
||||
|---------|-----------|---------------|-----------|
|
||||
| T-13-SC-pip | pip-audit local tooling | pip-audit not runnable against Python 3.12 requirements in Python 3.9 local env | No new Python packages added in Phase 13; existing packages audited in Phase 8; same accepted gap as Phase 12 |
|
||||
| T-13-docx | test_extract_docx failure | ModuleNotFoundError: python-docx/libmagic not in container image | Pre-existing; unrelated to Phase 13; all Phase 13 test coverage passes |
|
||||
|
||||
### Phase 13 Unregistered Flags
|
||||
|
||||
None. Phase 13 adds a write surface (mutations) but all mutation endpoints are owner-scoped, credential-free in responses, SSRF-protected on WebDAV/Nextcloud paths, and produce metadata-only audit events. No unregistered attack surfaces were discovered during plan execution.
|
||||
|
||||
+1
-1
@@ -244,7 +244,7 @@ async def lifespan(app: FastAPI):
|
||||
|
||||
# ── Application factory ───────────────────────────────────────────────────────
|
||||
|
||||
app = FastAPI(title="Document Scanner API", version="0.2.6", lifespan=lifespan)
|
||||
app = FastAPI(title="Document Scanner API", version="0.3.0", lifespan=lifespan)
|
||||
|
||||
# Rate limiter state (slowapi)
|
||||
app.state.limiter = auth_limiter
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "document-scanner-frontend",
|
||||
"version": "0.2.6",
|
||||
"version": "0.3.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
Reference in New Issue
Block a user