Skip to content

Make RecoveryV1 filesystem authority descriptor-backed #113

Description

@lynnswap

Context

RecoveryV1 path validation failed the same ownership invariant across four implementation rounds. On the current APFS volume, casing-only and firmlink spellings can identify the same directory while Foundation preserves the raw spelling for a missing leaf. String-based standardized/resolved URL overlap checks therefore cannot authorize creation or deletion.

This issue supersedes the rejected Wave 0 RecoveryV1 implementation on branch codex/recovery-wave-0-1-pr at c83e499. That branch remains evidence only and must not be merged.

Parent: #96
Prerequisite: reviewed Wave 3 runtime/process close authority (#106)
Blocks: first Wave 4 GRDB database open (#102)
Consumed by: Wave 5 login and account recovery (#97, #107, #108)

Required owner model

  • RecoveryEnvironmentPlan prepares one PreparedRecoveryEnvironment.
  • DirectoryCapability walks each component from a trusted descriptor using openat with O_DIRECTORY, O_NOFOLLOW, and O_CLOEXEC.
  • Identity and ancestry use fstat volume/device/inode facts; canonical URLs are diagnostics only.
  • Creation, atomic replacement, reads, and recursive removal are descriptor-relative.
  • Managed subtrees reject mount crossing, unknown permissions, shared ownership, inherited ACL policy violations, and unexpected identity changes.
  • Existing explicit homes are never chmod-mutated; unsafe homes fail closed.
  • Process launch and GRDB open perform last-moment capability revalidation. The threat model must state the residual limitation of path-only external consumers and whether same-UID adversaries are excluded.

Login staging follow-up

LoginStagingLease belongs to Wave 5 after throwing runtime close and LoginSession exist. It owns prepared staging, runtime/client/writer tasks, one joined close result, and descriptor-safe removal. RegistryV2 auth persistence owns pending authentication and cleanup debt through a versioned atomic JSON manifest; RecoveryEnvironment must not create a second manifest source of truth.

Acceptance criteria

  • Case-only aliases, Unicode aliases, firmlink spellings, reverse ancestor overlap, symlink components, mount crossing, wrong UID, shared mode, and ACL violations fail before unsafe mutation.
  • Missing-leaf creation is parent-descriptor anchored and reopens/revalidates the created leaf.
  • Identity swaps between prepare and Process/GRDB handoff are rejected.
  • Cleanup removes only a manifested, identity-matching, unreferenced owned lease.
  • No arbitrary source/destination URL remains in registry or cleanup APIs.
  • The public makeLiveStore declarations remain source-compatible.
  • Tests use temporary/injected filesystem seams and never mutation-capable global paths such as /Library.

Sequencing

Land the descriptor capability core after Wave 3 and before Wave 4 opens review-history.sqlite. Add LoginStagingLease, RegistryV2 pending/debt, and startup reconciliation in Wave 5.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions