.cairn-state/ must be invisible and unreachable from every user surface #155

Closed
opened 2026-08-09 03:17:09 +00:00 by Cordy · 1 comment
Owner

Depends on #152 (prefix decided). The reserved prefix holds ciphertext credentials; no user-facing surface may list, read, write, share, or deliver into it — including admins, whose unscoped view addresses the whole backend.

Surfaces to gate, each with a test:

  • REST file API (list/download/upload/mkdir/rename/copy/delete) — .cairn-state filtered from root listings and rejected as a path component.
  • WebDAV — same paths via the DAV handler.
  • Trash/restore — cannot restore into it.
  • Share links — creation on a .cairn-state path refused; existing-share resolution cannot traverse into it.
  • Peering delivery — the recipient path is already constrained to /home/…, add the explicit test anyway.
  • Spaces — a space named .cairn-state (or the prefix appearing under /spaces) cannot be created.

Implementation choice to make: one guard at the storage-driver boundary (a reserved decorator innermost, mirroring how scope works outermost) versus per-surface checks. The decorator is one place to be right; record the choice. Note the guard must NOT block the statestore itself — it goes through the raw driver below the decorator.

Also: root listings for unscoped admins hide the prefix (like dotfiles are handled today, but enforced server-side, not cosmetically).

Depends on #152 (prefix decided). The reserved prefix holds ciphertext credentials; no user-facing surface may list, read, write, share, or deliver into it — including admins, whose unscoped view addresses the whole backend. **Surfaces to gate, each with a test:** - REST file API (list/download/upload/mkdir/rename/copy/delete) — `.cairn-state` filtered from root listings and rejected as a path component. - WebDAV — same paths via the DAV handler. - Trash/restore — cannot restore into it. - Share links — creation on a `.cairn-state` path refused; existing-share resolution cannot traverse into it. - Peering delivery — the recipient path is already constrained to `/home/…`, add the explicit test anyway. - Spaces — a space named `.cairn-state` (or the prefix appearing under `/spaces`) cannot be created. Implementation choice to make: one guard at the storage-driver boundary (a `reserved` decorator innermost, mirroring how scope works outermost) versus per-surface checks. The decorator is one place to be right; record the choice. Note the guard must NOT block the statestore itself — it goes through the raw driver below the decorator. Also: root listings for unscoped admins hide the prefix (like dotfiles are handled today, but enforced server-side, not cosmetically).
Author
Owner

Shipped in v0.5.5 (PR #165), live on the dogfood.

Implementation choice, recorded: one guard at the storage-driver boundary — internal/storage/reserved, an INNERMOST decorator mirroring scope's outermost position — rather than per-surface checks. Every surface this issue lists (REST file API, WebDAV, trash/restore, share resolution, spaces, peering delivery) addresses storage through the decorated stack, so one guard covers them all, unscoped admins included, enforced server-side. The statestore holds the raw driver captured before the wrap, so state operations pass underneath by construction (proven by test TestRawDriverBelowGuardStillReachesState).

Scope decisions: /.cairn-state AND /.cairn are blocked as root subtrees — the posix state directory holds the same secret class (auth verifiers, peering credentials) and was previously admin-downloadable. The .cairn-state name is additionally blocked as a segment anywhere, so a space or folder named after the prefix cannot be created on any surface (covers the spaces requirement without touching spaces code). A deep directory legitimately named .cairn remains ordinary content. Blocked paths answer ErrNotFound, never "forbidden": confirming existence would leak what the guard hides. Like scope, the decorator does NOT embed storage.Driver — every method explicit, so an interface addition can never silently bypass it. Usage forwarded for quota.

Tests: root listing hides both entries; all eight ops (List/Stat/Open/Write/Delete/Mkdir/Rename/Copy, both directions for the two-path ops) return ErrNotFound on six representative reserved paths; the sentinel still satisfies fs.ErrNotExist (the #153 lesson, now regression-tested); nested .cairn allowed; ordinary content untouched.

Shipped in **v0.5.5** (PR #165), live on the dogfood. **Implementation choice, recorded:** one guard at the storage-driver boundary — `internal/storage/reserved`, an INNERMOST decorator mirroring scope's outermost position — rather than per-surface checks. Every surface this issue lists (REST file API, WebDAV, trash/restore, share resolution, spaces, peering delivery) addresses storage through the decorated stack, so one guard covers them all, unscoped admins included, enforced server-side. The statestore holds the raw driver captured *before* the wrap, so state operations pass underneath by construction (proven by test `TestRawDriverBelowGuardStillReachesState`). Scope decisions: `/.cairn-state` AND `/.cairn` are blocked as root subtrees — the posix state directory holds the same secret class (auth verifiers, peering credentials) and was previously admin-downloadable. The `.cairn-state` *name* is additionally blocked as a segment anywhere, so a space or folder named after the prefix cannot be created on any surface (covers the spaces requirement without touching spaces code). A deep directory legitimately named `.cairn` remains ordinary content. Blocked paths answer `ErrNotFound`, never "forbidden": confirming existence would leak what the guard hides. Like scope, the decorator does NOT embed `storage.Driver` — every method explicit, so an interface addition can never silently bypass it. `Usage` forwarded for quota. Tests: root listing hides both entries; all eight ops (List/Stat/Open/Write/Delete/Mkdir/Rename/Copy, both directions for the two-path ops) return ErrNotFound on six representative reserved paths; the sentinel still satisfies `fs.ErrNotExist` (the #153 lesson, now regression-tested); nested `.cairn` allowed; ordinary content untouched.
Cordy closed this issue 2026-08-09 07:02:30 +00:00
Sign in to join this conversation.
No labels
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#155
No description provided.