--- phase: "12.1" plan: "01" type: tdd wave: 1 depends_on: [] files_modified: - backend/storage/cloud_base.py - backend/storage/cloud_utils.py - backend/storage/webdav_backend.py - backend/storage/nextcloud_backend.py - backend/storage/google_drive_backend.py - backend/storage/onedrive_backend.py - backend/storage/cloud_backend_factory.py - backend/tests/test_cloud_provider_contract.py - backend/tests/test_cloud_backends.py - backend/tests/test_webdav_backend.py - backend/tests/fixtures/cloud/nextcloud_root.xml - backend/tests/fixtures/cloud/webdav_root.xml - backend/tests/fixtures/cloud/google_drive_pages.json - backend/tests/fixtures/cloud/onedrive_pages.json - backend/main.py - frontend/package.json - frontend/package-lock.json - AGENTS.md - README.md - RUNBOOK.md - SECURITY.md autonomous: true requirements: - CLOUD-01 - CACHE-01 - SYNC-01 must_haves: truths: - "Nextcloud accepts the exact four-argument CloudResourceAdapter.list_folder contract and returns CloudListing rather than a legacy list of dictionaries" - "One shared contract suite runs against Nextcloud, generic WebDAV, Google Drive, and OneDrive" - "Every provider normalizes root and nested children with trusted connection/user identity, opaque provider navigation identity, parent reference, kind, and nullable metadata" - "A provider reports complete=True only after every page or complete DAV multistatus has been parsed successfully" - "Browse contract tests prove zero byte-download and zero provider-mutation calls" artifacts: - path: "backend/tests/test_cloud_provider_contract.py" provides: "Reusable four-provider adapter contract and forbidden-operation spies" contains: "test_provider_list_folder_contract" - path: "backend/storage/nextcloud_backend.py" provides: "Nextcloud specialization that preserves the canonical WebDAV browse method" contains: "NextcloudBackend" - path: "backend/tests/fixtures/cloud/nextcloud_root.xml" provides: "Credential-free DAV root fixture with folders, files, spaces, root self-entry, and optional metadata" contains: "multistatus" key_links: - from: "backend/storage/nextcloud_backend.py" to: "backend/storage/webdav_backend.py" via: "inheritance or a narrow normalization hook; no incompatible public override" pattern: 'class NextcloudBackend\(WebDAVBackend\)' - from: "backend/tests/test_cloud_provider_contract.py" to: "backend/storage/cloud_base.py" via: "the same parametrized assertions for every provider factory" pattern: "CloudListing|CloudResourceAdapter" - from: "provider fixtures" to: "CloudResource.provider_item_id" via: "provider-specific response normalization" pattern: "provider_item_id" --- Repair the provider boundary test-first. Reproduce the Nextcloud signature failure, replace the legacy browse override with the shared adapter contract, and establish one reusable listing contract for all four providers with provider-specific root, nested, pagination, malformed-response, and no-byte/no-mutation fixtures. This plan changes metadata listing only. It does not add upload, create-folder, rename, move, delete, preview, download, or byte caching. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-CONTEXT.md @.planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-RESEARCH.md @.planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-PATTERNS.md @.planning/phases/12-cloud-resource-foundation/12-02-PLAN.md @.planning/phases/12-cloud-resource-foundation/12-VALIDATION.md @backend/storage/cloud_base.py @backend/storage/nextcloud_backend.py @backend/storage/webdav_backend.py Task 1: Write the shared four-provider contract and red tests backend/tests/test_cloud_provider_contract.py, backend/tests/test_cloud_backends.py, backend/tests/test_webdav_backend.py, backend/tests/fixtures/cloud/nextcloud_root.xml, backend/tests/fixtures/cloud/webdav_root.xml, backend/tests/fixtures/cloud/google_drive_pages.json, backend/tests/fixtures/cloud/onedrive_pages.json - backend/storage/cloud_base.py — exact CloudListing and CloudResource invariants - backend/storage/cloud_backend_factory.py — supported-provider construction paths - backend/tests/test_cloud_backends.py — existing provider mocks and missing behavioral coverage - backend/tests/test_webdav_backend.py — existing webdavclient3 test conventions - .planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-RESEARCH.md — confirmed signatures and provider fixture matrix Create a reusable parametrized contract harness whose provider cases supply only fixture setup and response stubs. Add concrete tests named `test_provider_list_folder_contract`, `test_provider_root_and_nested_identity`, `test_provider_metadata_normalization`, `test_provider_consumes_all_pages_before_complete`, `test_provider_page_failure_is_incomplete`, and `test_provider_listing_never_downloads_or_mutates`. Invoke each adapter through the canonical positional/keyword signature `(connection_id, user_id, parent_ref=None, page_token=None)` and assert the result is CloudListing; this test must fail against the current Nextcloud override. For every provider assert: trusted caller UUIDs are copied into resources; `provider_item_id` is the opaque navigation reference; `parent_ref` is the requested folder reference; `kind` is exactly file/folder; optional size/content-type/modified/version metadata becomes `None` when absent; and no provider response can override owner/connection IDs. Install spies that fail on GET-media/download/get_object/put/upload/create/move/copy/rename/delete methods and assert quota/MinIO are not imported or called by listing. Add credential-free provider fixtures: Nextcloud and WebDAV Depth-1 multistatus with root self-entry, encoded spaces/unicode, collections, files, absent optional properties, absolute/relative hrefs, and a foreign/escaping href; Google Drive with root/nested queries, native files with nullable size, trashed exclusion, and multiple nextPageToken pages; OneDrive with root/nested endpoints, folder/file facets, encoded names, and multiple @odata.nextLink pages. Fixture names must be synthetic—not copied from the live account—and contain no URL, username, tokens, or provider-owned unexpected names. - the Nextcloud canonical-signature regression fails before implementation and passes afterward - the same six contract tests execute for nextcloud, webdav, google_drive, and onedrive - fixtures cover root, nested, missing optional metadata, malformed/foreign data, and provider pagination - no contract fixture or pytest ID contains credentials, a full live URL, or live unexpected names - forbidden byte and mutation spies remain untouched for every provider cd backend && pytest -q tests/test_cloud_provider_contract.py tests/test_cloud_backends.py tests/test_webdav_backend.py The shared contract is red for the known Nextcloud violation and fully specifies normalized, complete, read-only behavior for all providers. Task 2: Repair Nextcloud/WebDAV listing without splitting the public contract backend/storage/cloud_utils.py, backend/storage/webdav_backend.py, backend/storage/nextcloud_backend.py, backend/storage/cloud_backend_factory.py, backend/tests/test_cloud_provider_contract.py, backend/tests/test_webdav_backend.py - backend/storage/nextcloud_backend.py — incompatible legacy list_folder override to remove - backend/storage/webdav_backend.py — canonical list_folder implementation and SSRF revalidation points - backend/storage/cloud_utils.py — canonical URL/SSRF helpers - backend/storage/cloud_backend_factory.py — credential-to-adapter construction - backend/tests/test_cloud_security.py — existing SSRF invariants that must remain green Remove the incompatible `NextcloudBackend.list_folder(folder_path="") -> list[dict]` public override. Let Nextcloud inherit the canonical WebDAV implementation, or add only a protected Nextcloud endpoint/path-normalization hook consumed by that implementation. Keep exactly one public DAV `list_folder` algorithm. Normalize a Nextcloud base URL and username to its canonical DAV root in one pure shared helper used at adapter construction, while accepting an already canonical endpoint idempotently. Percent-encode the username as one path segment; preserve deployment subpaths; reject userinfo, query, fragment, non-HTTPS, malformed URLs, and URLs rejected by `validate_cloud_url`; never log the normalized URL because it contains the username path segment. Disable automatic redirects in the DAV HTTP transport. If redirect support is required, follow it explicitly with a strict bounded hop count and, before every hop, parse and validate the target URL, require HTTPS and the original normalized origin, resolve the hostname, and reject loopback, private, link-local, multicast, unspecified, reserved, or changed/untrusted addresses. Revalidate immediately before dispatch to limit DNS rebinding. Add automated redirect tests for loopback, RFC1918/private, link-local, cross-origin, non-HTTPS, excessive-hop, and a permitted same-origin target; assert rejected targets receive no second request and no URL is leaked. Make the shared DAV listing parser authoritative for one Depth-1 response: exclude exactly the normalized self href, preserve a canonical child provider reference, use DAV collection resource type for kind, decode display path segments exactly once, reject hrefs outside the configured root, and return complete=False on malformed/ambiguous responses. Do not silently convert per-item metadata failures into a complete listing. If the existing SDK cannot expose a safe one-response parser without a new dependency, retain its calls but enforce the same completeness and no-byte contract; document the tradeoff in the summary. - `inspect.signature(NextcloudBackend.list_folder)` matches the canonical adapter method - root and nested Nextcloud/WebDAV fixtures return normalized CloudListing values - malformed XML, foreign hrefs, or interrupted metadata parsing cannot return complete=True - Nextcloud URL normalization is idempotent and remains behind canonical SSRF validation - automatic redirects are disabled or each bounded hop is same-origin and URL/DNS revalidated; loopback/private/link-local/cross-origin redirect tests pass - no second public Nextcloud listing path or raw dictionary response remains cd backend && pytest -q tests/test_cloud_provider_contract.py tests/test_webdav_backend.py tests/test_cloud_security.py -k 'provider or webdav or nextcloud or ssrf or redirect' Nextcloud and generic WebDAV share one secure canonical listing path and the known signature defect is eliminated. Task 3: Bring Drive and OneDrive to the same completion contract backend/storage/google_drive_backend.py, backend/storage/onedrive_backend.py, backend/tests/test_cloud_provider_contract.py, backend/tests/test_cloud_backends.py - backend/storage/google_drive_backend.py — root query, nextPageToken, metadata and error behavior - backend/storage/onedrive_backend.py — root/items endpoints, @odata.nextLink, metadata and error behavior - backend/storage/cloud_base.py — complete/next_page_token semantics - backend/tests/fixtures/cloud/google_drive_pages.json — required fixture cases - backend/tests/fixtures/cloud/onedrive_pages.json — required fixture cases Make Google Drive and OneDrive pass the same contract without creating provider-specific API schemas. Google Drive must translate root internally, follow every nextPageToken before complete=True, exclude trashed items, recognize folder MIME type, preserve native Google files with nullable size, and map controlled auth/scope failures without raw provider responses. OneDrive must translate root internally, follow every trusted @odata.nextLink, identify folders by facet, preserve nullable metadata, and reject cross-origin/untrusted continuation URLs. Any page failure returns complete=False with already normalized items retained and no deletion authority. Keep provider IDs opaque. Do not turn DocuVault CloudItem UUIDs into provider references and do not call media/download endpoints. Extend concrete provider tests where behavior is too provider-specific for the shared assertions. - all pages are consumed before complete=True for both providers - page failure or untrusted continuation returns complete=False - Drive native files and OneDrive folders normalize without fabricated metadata - raw auth/provider bodies and continuation URLs are absent from raised/displayable messages - the complete four-provider focused suite passes cd backend && pytest -q tests/test_cloud_provider_contract.py tests/test_cloud_backends.py tests/test_webdav_backend.py tests/test_cloud_security.py All four providers satisfy one listing/completeness/identity/no-byte contract with provider-specific pagination evidence. Task 4: Document, security-review, commit, and push Plan 01 backend/main.py, frontend/package.json, frontend/package-lock.json, AGENTS.md, README.md, RUNBOOK.md, SECURITY.md, .planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-01-SUMMARY.md Update the required docs for shipped Plan 01 behavior, including shared helpers and redirect policy. Because this repairs user-visible cloud browsing, bump the backend/frontend patch version once and keep package-lock metadata synchronized. Run a dedicated security agent against this plan's diff and resolve or escalate every finding. Re-run focused/full backend tests, Bandit and dependency gates applicable to the changed backend, `docker compose config --quiet`, and `git diff --check`. Stage only Plan 01 files, verify `.env` and unrelated changes are absent, create one atomic `fix(12.1): restore cloud adapter contract` commit, and push immediately according to AGENTS.md. docker compose config --quiet && cd backend && pytest -v && bandit -r . && pip audit && cd .. && git diff --check Plan 01 has its own docs, clean security-agent verdict, green gates, atomic commit, and push. | Threat ID | Threat | Mitigation and evidence | |-----------|--------|-------------------------| | T-12.1-01 | Provider response forges owner or connection identity | Contract copies trusted caller UUIDs and tests hostile fixture fields | | T-12.1-02 | SSRF through Nextcloud normalization, redirects, or DAV hrefs | Canonical validator before construction/request; reject userinfo/query/fragment/private targets/escaping hrefs | | T-12.1-03 | Partial page authorizes metadata deletion | complete=False on any page/parser/trust failure; contract tests every provider | | T-12.1-04 | Browse downloads or mutates provider content | Failing spies on byte and mutation methods; metadata requests only | | T-12.1-05 | Secrets/provider data leak through fixtures or failures | Synthetic fixtures, controlled exceptions, no live URL/user/token/unexpected-name output | 1. Observe the Nextcloud signature test fail before implementation and pass after repair. 2. Run the shared contract for all four providers, not four copied suites. 3. Run provider-specific root, nested, pagination, malformed-response, and no-byte tests. 4. Run existing cloud security tests to prove SSRF and credential boundaries remain intact. 5. Search confirms no incompatible Nextcloud public list_folder override remains. - Nextcloud can be invoked through CloudResourceAdapter exactly like every other provider. - Every provider has executable evidence for normalized identity, kind, metadata, pagination/completeness, and read-only behavior. - Provider failures cannot masquerade as an authoritative empty collection. - No Phase 13 mutation or Phase 14 byte behavior enters the contract. Create `.planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-01-SUMMARY.md` when complete.