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.
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:105setsENV.worktreeRootfromFARM_WORKTREE_ROOT, defaulting to.farm/worktrees.plugins/ca/tools/farm.ts:1325definesSAFE_TASK_ID = /^[A-Za-z0-9._-]{1,64}$/.plugins/ca/tools/farm.ts:1378-1379validates task ids only against that regex.plugins/ca/tools/farm.ts:1069-1070deriveswt = path.resolve(ENV.worktreeRoot, t.id).plugins/ca/tools/farm.ts:885-891callsrm(wt, { recursive: true, force: true })beforegit worktree add.There is no check that the resolved
wtis inside the repo's intended.farm/worktreesdirectory, nor thatFARM_WORKTREE_ROOTis 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/aliceand a plan contains a task idDesktop, the dispatcher computes:Then
prepareWorktree()reaches:before Git can reject or create anything.
A less dramatic variant exists with
FARM_WORKTREE_ROOT=/tmpand a task id matching an unrelated important directory under/tmp.Impact
A malformed or malicious plan plus a broad/misconfigured
FARM_WORKTREE_ROOTcan 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:
FARM_WORKTREE_ROOTunless an explicit unsafe override is added..and.., and consider using the stricter schema id pattern at runtime.rm(..., recursive: true), assertisInside(allowedFarmRoot, wt)andwt !== allowedFarmRoot.FARM_WORKTREE_ROOT=/Users/aliceplus task idDesktop,.and..ids, and a normal.farm/worktrees/task-apath.Duplicate check
Checked existing issues with:
No matching issue was returned.