Files
kite/.planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-01-PLAN.md
T
curo1305andClaude Sonnet 4.6 73328eee0b docs(12.1): add phase 12.1 planning artifacts — Nextcloud root listing fix
Plans 01–04, CONTEXT, PATTERNS, RESEARCH, UAT, and .gitkeep for
the fix-nextcloud-root-listing-and-sync-visibility sub-phase.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 20:34:47 +02:00

17 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
12.1 01 tdd 1
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
true
CLOUD-01
CACHE-01
SYNC-01
truths artifacts key_links
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
path provides contains
backend/tests/test_cloud_provider_contract.py Reusable four-provider adapter contract and forbidden-operation spies test_provider_list_folder_contract
path provides contains
backend/storage/nextcloud_backend.py Nextcloud specialization that preserves the canonical WebDAV browse method NextcloudBackend
path provides contains
backend/tests/fixtures/cloud/nextcloud_root.xml Credential-free DAV root fixture with folders, files, spaces, root self-entry, and optional metadata multistatus
from to via pattern
backend/storage/nextcloud_backend.py backend/storage/webdav_backend.py inheritance or a narrow normalization hook; no incompatible public override class NextcloudBackend(WebDAVBackend)
from to via pattern
backend/tests/test_cloud_provider_contract.py backend/storage/cloud_base.py the same parametrized assertions for every provider factory CloudListing|CloudResourceAdapter
from to via pattern
provider fixtures CloudResource.provider_item_id provider-specific response normalization 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.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.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_model>

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
</threat_model>
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.

<success_criteria>

  • 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. </success_criteria>

Create .planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-01-SUMMARY.md when complete.