Skip to content

fix(worktrees): ownership check is forgeable by directory name and lock-reason prefix #785

Description

@euxaristia

Summary

verifyZeroOwnedWorktree in internal/worktrees/worktrees.go proves a worktree is Zero-owned using two signals that a user can reproduce by hand: a predictable zero-worktree-<repoKey> ancestor directory name, and a lock reason starting with the zero: active task worktree prefix. A worktree created under that directory and manually locked with a matching reason passes the ownership check, so zero worktrees release will clear that lock, and a subsequent zero worktrees clean can then force-remove the checkout.

Raised by jatmn's review on PR #632 (#632 (comment)); not fixed there because it needs a migration decision first (see below).

Affected code

  • internal/worktrees/worktrees.go (verifyZeroOwnedWorktree, the strings.HasPrefix(entry.lockReason, leaseReasonPrefix) check)

Current behavior

Ownership is inferred entirely from a directory-name convention and a lock-reason string prefix, both fully known and reproducible by anyone reading the source or the lock reason on an existing Zero worktree.

Expected behavior

release and clean should only be able to act on worktrees Zero itself created, verified through something a user can't trivially reproduce by hand.

Why this wasn't just fixed alongside the other findings

internal/worktrees has been on main since #70, well before the PR that raised this. Any fix that makes a new ownership marker mandatory risks rejecting worktrees an already-installed zero created before the marker existed, breaking release/clean for them on upgrade. This needs a migration story, not just the check itself.

Suggested implementation

  • Write a durable ownership marker when Prepare creates a worktree, e.g. a file inside the worktree's own git administrative directory (not the visible working tree, so git worktree remove cleans it up automatically and it never appears as an untracked file).
  • Decide the migration path for worktrees created before the marker existed: a grace period that accepts the current prefix-only check with a deprecation warning, a versioned marker, or similar. Don't make the marker mandatory on day one.
  • Update verifyZeroOwnedWorktree to check the marker once the migration window has been decided.

Suggested tests

  • A worktree with a valid marker releases successfully.
  • A worktree under the predictable directory with only a matching lock-reason prefix but no marker is handled per whatever migration behavior is chosen (accepted with warning, or rejected, depending on the decision above).
  • A worktree with a mismatched/corrupted marker is rejected.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions