[GH-ISSUE #6828] [Bug] IDA workspaceRoot selects the wrong parent after a directory symlink and .. #4172

Closed
opened 2026-10-31 00:47:24 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @coygeek on GitHub (Oct 23, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/6828

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Setup, packaging, or upgrade

Observed behavior

Headless IDA workspace allocation silently uses a different parent directory when the configured POSIX workspaceRoot contains a directory symlink followed by ...

The production resolveIdaWorkspaceParent and IdaWorkspace.allocate resolve /tmp/rea-repro/parent/ida-workspace-case/link/../workspace to the lexical sibling workspace, while the filesystem resolves that same selection to physical/workspace. Allocation succeeds in the wrong parent. A canonical-path control allocates under the intended parent.

This executes the real private-directory allocator on Linux. No IDA engine, upstream MCP connection, or binary copy was started.

Reproduction

Use source revision 7fd722e15bea35255f5eadbd406948e28538d25f with locked dependencies installed at /tmp/rea-repro/rea. Save this as /tmp/rea-repro/parent/ida-workspace-parent.mts in a fresh scratch tree:

import { mkdir, symlink, realpath, readdir, rm } from 'node:fs/promises';
import { join, dirname } from 'node:path';
import { IdaWorkspace, resolveIdaWorkspaceParent } from '../rea/src/ida/IdaWorkspace.ts';
import { createIdaTarget } from '../rea/tests/fixtures/idaMcp.ts';
const root='/tmp/rea-repro/parent/ida-workspace-case';
await mkdir(join(root,'physical/inner'),{recursive:true});
await mkdir(join(root,'physical/workspace'),{recursive:true});
await mkdir(join(root,'workspace'),{recursive:true});
await symlink('physical/inner',join(root,'link'));
const selected=root+'/link/../workspace';
const expected=await realpath(selected);
const resolved=await resolveIdaWorkspaceParent(selected);
const {root:targetRoot,target}=await createIdaTarget();
try {
 for(const [label,parent]of [['selected',selected],['canonical-control',expected]] as const){
  const workspace=await IdaWorkspace.allocate(target,parent);
  try{
   console.log(JSON.stringify({label,selected,expected,resolved,actual_parent:dirname(workspace.directory),expected_parent:dirname(workspace.directory)===expected,selected_parent_entries:await readdir(expected),lexical_parent_entries:await readdir(join(root,'workspace'))}));
  }finally{await workspace.remove();}
 }
}finally{await rm(targetRoot,{recursive:true,force:true});}

Run:

mkdir -p /tmp/rea-repro/parent
TMPDIR=/tmp/rea-repro/parent node --import /tmp/rea-repro/rea/node_modules/tsx/dist/loader.mjs /tmp/rea-repro/parent/ida-workspace-parent.mts

One selected-path allocation and one canonical control ran before drafting. The script removes each allocated workspace and its inert target fixture. Only the disposable directory prefix was normalized above; the executed operations are unchanged. The fixture target is generated by the repository helper and is never executed.

Expected behavior

Canonicalize the filesystem-selected parent without collapsing link/.. before filesystem resolution.

The IDA provider guide says workspaceRoot selects an existing absolute temporary-directory parent and tells callers to ensure it has space for input copies and working files. The allocator's own contract is to resolve the exact existing parent used by readiness and allocation.

Of the plausible interpretations—lexical path normalization, rejection of this spelling, or following ordinary POSIX filesystem semantics—the last preserves the configured directory and agrees with the successful canonical-path control.

Impact and scope

P2 configuration correctness defect: a caller's existing absolute workspace-parent selection is ignored for this path form. In a full headless session, the same allocator is the path used for workspace creation; no real IDA session, disk-exhaustion outcome, or data-loss outcome was tested.

The demonstrated effect is limited to POSIX symlink/parent path spellings. Windows and macOS were not tested. Ordinary canonical paths work. The proof only creates and removes private scratch workspaces; it does not copy or modify target bytes.

REA version

Manifest 6.7.0; main source 7fd722e15bea35255f5eadbd406948e28538d25f. Latest published release confirmed as rea-agents-6.7.0; its packaged runtime was not separately exercised.

Runtime and platform

Node.js v26.12.1; Linux x86_64; repository-locked tsx. No IDA installation or provider process was used.

Target and artifact context

Self-authored scratch directory tree with a relative directory symlink. The repository's inert ELF target fixture supplies the allocator's target basename; its analysis or execution is irrelevant and was not performed.

Interface

Other

Earliest failure stage

Setup or provider admission

Relevant output or evidence

Reduced output from the executed production-allocation proof, with only the scratch prefix and random workspace suffix normalized:

filesystem-selected parent: .../ida-workspace-case/physical/workspace
resolveIdaWorkspaceParent: .../ida-workspace-case/workspace
selected allocation:
  actual_parent: .../ida-workspace-case/workspace
  expected_parent: false
  selected_parent_entries: []
  lexical_parent_entries: ["rea-ida-<random>"]
canonical-control allocation:
  actual_parent: .../ida-workspace-case/physical/workspace
  expected_parent: true
  selected_parent_entries: ["rea-ida-<random>"]
  lexical_parent_entries: []

The process exited 0 and both workspaces were released. This establishes wrong-parent allocation independently of any engine. It does not establish IDA engine readiness or doctor output.

Typed outcome or recovery expected

Allocate under the filesystem-resolved selected parent, as in the canonical control. A caller can currently pass that canonical parent explicitly.

Analysis or suspected cause

Source-derived: resolveIdaWorkspaceParent calls realpath(normalize(root)). On POSIX, normalize removes the symlink/parent components lexically before realpath can follow the symlink. Allocation uses the resulting parent. Readiness also consumes this helper; the readiness effect is source-derived and was not separately executed.

Additional context

Open and closed searches for IDA workspaceRoot and resolveIdaWorkspaceParent found related #5559 and #5830, which concern an unusable parent's readiness or failure classification, rather than selecting a different existing parent. #6826 concerns the independent historical-source importer boundary using a similar normalization order.

Prepared with AI assistance. No source fix, IDA process, or real-user configuration change was made. No bug was rerun after this draft was written.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: The production allocator created its private workspace under a different existing directory from the configured POSIX path, while a canonical-path control used the intended parent. No engine failure or data loss is claimed.
  • Workaround status: available
  • Workaround basis: Passing the filesystem-realpath value was executed and allocated under the intended parent.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6828 ## Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ## Area Setup, packaging, or upgrade ## Observed behavior Headless IDA workspace allocation silently uses a different parent directory when the configured POSIX `workspaceRoot` contains a directory symlink followed by `..`. The production `resolveIdaWorkspaceParent` and `IdaWorkspace.allocate` resolve `/tmp/rea-repro/parent/ida-workspace-case/link/../workspace` to the lexical sibling `workspace`, while the filesystem resolves that same selection to `physical/workspace`. Allocation succeeds in the wrong parent. A canonical-path control allocates under the intended parent. This executes the real private-directory allocator on Linux. No IDA engine, upstream MCP connection, or binary copy was started. ## Reproduction Use source revision `7fd722e15bea35255f5eadbd406948e28538d25f` with locked dependencies installed at `/tmp/rea-repro/rea`. Save this as `/tmp/rea-repro/parent/ida-workspace-parent.mts` in a fresh scratch tree: ```ts import { mkdir, symlink, realpath, readdir, rm } from 'node:fs/promises'; import { join, dirname } from 'node:path'; import { IdaWorkspace, resolveIdaWorkspaceParent } from '../rea/src/ida/IdaWorkspace.ts'; import { createIdaTarget } from '../rea/tests/fixtures/idaMcp.ts'; const root='/tmp/rea-repro/parent/ida-workspace-case'; await mkdir(join(root,'physical/inner'),{recursive:true}); await mkdir(join(root,'physical/workspace'),{recursive:true}); await mkdir(join(root,'workspace'),{recursive:true}); await symlink('physical/inner',join(root,'link')); const selected=root+'/link/../workspace'; const expected=await realpath(selected); const resolved=await resolveIdaWorkspaceParent(selected); const {root:targetRoot,target}=await createIdaTarget(); try { for(const [label,parent]of [['selected',selected],['canonical-control',expected]] as const){ const workspace=await IdaWorkspace.allocate(target,parent); try{ console.log(JSON.stringify({label,selected,expected,resolved,actual_parent:dirname(workspace.directory),expected_parent:dirname(workspace.directory)===expected,selected_parent_entries:await readdir(expected),lexical_parent_entries:await readdir(join(root,'workspace'))})); }finally{await workspace.remove();} } }finally{await rm(targetRoot,{recursive:true,force:true});} ``` Run: ```sh mkdir -p /tmp/rea-repro/parent TMPDIR=/tmp/rea-repro/parent node --import /tmp/rea-repro/rea/node_modules/tsx/dist/loader.mjs /tmp/rea-repro/parent/ida-workspace-parent.mts ``` One selected-path allocation and one canonical control ran before drafting. The script removes each allocated workspace and its inert target fixture. Only the disposable directory prefix was normalized above; the executed operations are unchanged. The fixture target is generated by the repository helper and is never executed. ## Expected behavior Canonicalize the filesystem-selected parent without collapsing `link/..` before filesystem resolution. [The IDA provider guide](https://github.com/morluto/rea/blob/7fd722e15bea35255f5eadbd406948e28538d25f/docs/ida-provider.md#L111) says `workspaceRoot` selects an existing absolute temporary-directory parent and tells callers to ensure it has space for input copies and working files. The allocator's own contract is to resolve the exact existing parent used by readiness and allocation. Of the plausible interpretations—lexical path normalization, rejection of this spelling, or following ordinary POSIX filesystem semantics—the last preserves the configured directory and agrees with the successful canonical-path control. ## Impact and scope P2 configuration correctness defect: a caller's existing absolute workspace-parent selection is ignored for this path form. In a full headless session, the same allocator is the path used for workspace creation; no real IDA session, disk-exhaustion outcome, or data-loss outcome was tested. The demonstrated effect is limited to POSIX symlink/parent path spellings. Windows and macOS were not tested. Ordinary canonical paths work. The proof only creates and removes private scratch workspaces; it does not copy or modify target bytes. ## REA version Manifest 6.7.0; main source `7fd722e15bea35255f5eadbd406948e28538d25f`. Latest published release confirmed as `rea-agents-6.7.0`; its packaged runtime was not separately exercised. ## Runtime and platform Node.js v26.12.1; Linux x86_64; repository-locked tsx. No IDA installation or provider process was used. ## Target and artifact context Self-authored scratch directory tree with a relative directory symlink. The repository's inert ELF target fixture supplies the allocator's target basename; its analysis or execution is irrelevant and was not performed. ## Interface Other ## Earliest failure stage Setup or provider admission ## Relevant output or evidence Reduced output from the executed production-allocation proof, with only the scratch prefix and random workspace suffix normalized: ```text filesystem-selected parent: .../ida-workspace-case/physical/workspace resolveIdaWorkspaceParent: .../ida-workspace-case/workspace selected allocation: actual_parent: .../ida-workspace-case/workspace expected_parent: false selected_parent_entries: [] lexical_parent_entries: ["rea-ida-<random>"] canonical-control allocation: actual_parent: .../ida-workspace-case/physical/workspace expected_parent: true selected_parent_entries: ["rea-ida-<random>"] lexical_parent_entries: [] ``` The process exited 0 and both workspaces were released. This establishes wrong-parent allocation independently of any engine. It does not establish IDA engine readiness or doctor output. ## Typed outcome or recovery expected Allocate under the filesystem-resolved selected parent, as in the canonical control. A caller can currently pass that canonical parent explicitly. ## Analysis or suspected cause Source-derived: [`resolveIdaWorkspaceParent`](https://github.com/morluto/rea/blob/7fd722e15bea35255f5eadbd406948e28538d25f/src/ida/IdaWorkspace.ts#L24-L25) calls `realpath(normalize(root))`. On POSIX, `normalize` removes the symlink/parent components lexically before `realpath` can follow the symlink. [Allocation](https://github.com/morluto/rea/blob/7fd722e15bea35255f5eadbd406948e28538d25f/src/ida/IdaWorkspace.ts#L48-L61) uses the resulting parent. Readiness also consumes this helper; the readiness effect is source-derived and was not separately executed. ## Additional context Open and closed searches for IDA `workspaceRoot` and `resolveIdaWorkspaceParent` found related #5559 and #5830, which concern an unusable parent's readiness or failure classification, rather than selecting a different existing parent. #6826 concerns the independent historical-source importer boundary using a similar normalization order. Prepared with AI assistance. No source fix, IDA process, or real-user configuration change was made. No bug was rerun after this draft was written. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: The production allocator created its private workspace under a different existing directory from the configured POSIX path, while a canonical-path control used the intended parent. No engine failure or data loss is claimed. - Workaround status: available - Workaround basis: Passing the filesystem-realpath value was executed and allocated under the intended parent.
Sign in to join this conversation.
No milestone
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
morluto/rea#4172
No description provided.