Skip to content

[P1] farm can recursively delete outside its workspace via FARM_WORKTREE_ROOT plus task id #163

Description

@tg12

Severity: P1 critical data loss

Problem

The farm dispatcher resolves task worktree paths from an environment-controlled root and a plan-controlled task id, then recursively deletes that path before Git validates whether it is a real farm worktree.

Source evidence:

  • plugins/ca/tools/farm.ts:105 sets ENV.worktreeRoot from FARM_WORKTREE_ROOT, defaulting to .farm/worktrees.
  • plugins/ca/tools/farm.ts:1325 defines SAFE_TASK_ID = /^[A-Za-z0-9._-]{1,64}$/.
  • plugins/ca/tools/farm.ts:1378-1379 validates task ids only against that regex.
  • plugins/ca/tools/farm.ts:1069-1070 derives wt = path.resolve(ENV.worktreeRoot, t.id).
  • plugins/ca/tools/farm.ts:885-891 calls rm(wt, { recursive: true, force: true }) before git worktree add.

There is no check that the resolved wt is inside the repo's intended .farm/worktrees directory, nor that FARM_WORKTREE_ROOT is safe to delete beneath. The task id is plan-controlled and can name any child of the configured root that matches the regex.

Reproduction shape

Do not run this against real data.

If an operator has FARM_WORKTREE_ROOT=/Users/alice and a plan contains a task id Desktop, the dispatcher computes:

path.resolve('/Users/alice', 'Desktop') == '/Users/alice/Desktop'

Then prepareWorktree() reaches:

rm('/Users/alice/Desktop', { recursive: true, force: true })

before Git can reject or create anything.

A less dramatic variant exists with FARM_WORKTREE_ROOT=/tmp and a task id matching an unrelated important directory under /tmp.

Impact

A malformed or malicious plan plus a broad/misconfigured FARM_WORKTREE_ROOT can delete arbitrary directories under that root. The delete is recursive and forced. This is local data loss on the developer machine running the plugin.

The default root limits normal blast radius, but the env knob is documented configuration and the code does not enforce containment at the destructive operation.

Smallest credible fix

Make deletion containment explicit and fail closed:

  • Resolve the repo root and the allowed farm root once, then require every task worktree path to be inside that allowed root.
  • Reject absolute or outside-repo FARM_WORKTREE_ROOT unless an explicit unsafe override is added.
  • Reject task ids . and .., and consider using the stricter schema id pattern at runtime.
  • Before any rm(..., recursive: true), assert isInside(allowedFarmRoot, wt) and wt !== allowedFarmRoot.
  • Add unit tests for FARM_WORKTREE_ROOT=/Users/alice plus task id Desktop, . and .. ids, and a normal .farm/worktrees/task-a path.

Duplicate check

Checked existing issues with:

gh issue list --repo arbiterForge/codeArbiter --state all --search "FARM_WORKTREE_ROOT OR worktreeRoot OR SAFE_TASK_ID OR task id path traversal OR rm worktree"
gh issue list --repo arbiterForge/codeArbiter --state all --search "farm data loss OR worktree remove OR rm -rf OR path traversal"

No matching issue was returned.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions