Skip to content

[Bug] Custom tool cwd follows symlinks / absolute paths out of the project #1027

Description

@Dhirenderchoudhary

Description

Custom tool cwd is not kept inside the project.

resolveCwd in source/custom-tools/handler.ts (lines 132-142 on main, v1.30.0) does ${VAR} expansion, path.resolve, then existsSync. If the path exists, that becomes the cwd. There is no realpath and no project-root check.

export function resolveCwd(
	configured: string | undefined,
	projectRoot: string,
): string {
	if (!configured) return projectRoot;
	const expanded = expandVars(configured);
	const absolute = isAbsolute(expanded)
		? expanded
		: resolve(projectRoot, expanded);
	return existsSync(absolute) ? absolute : projectRoot;
}

File tools already block this. resolveFilePath in source/utils/path-validation.ts (around 156) follows symlinks and throws if the real path leaves the project. Custom tools never got that check.

The docs example is cwd: ./scripts. If scripts is a symlink to a folder outside the repo, the shell starts there. It still looks like an in-repo path, so nothing is logged. Same thing with an absolute path or ${HOME}.

handler.spec.ts only covers the missing-path fallback (lines 63-66), so CI stays green.

Environment

  • OS: macOS (darwin 25.5.0)
  • Node version: v26.7.0
  • Nanocoder version: 1.30.0 (main @ 1d0cf10)
  • Provider: N/A
  • Model: N/A

Steps to Reproduce

  1. mkdir -p /tmp/nc-cwd-proj /tmp/nc-cwd-out && ln -s /tmp/nc-cwd-out /tmp/nc-cwd-proj/scripts
  2. Call resolveCwd with cwd ./scripts and project root /tmp/nc-cwd-proj
  3. Repeat with cwd set to /tmp/nc-cwd-out, then with ${HOME}

Expected Behavior

cwd stays inside the project after symlink resolution. If the real path is outside, fall back to the project root, same as a missing path. Reuse the file-tool check (resolveFilePath / getContainedSessionCwd).

Actual Behavior

./scripts is a symlink, so the shell cwd is outside the repo. Absolute paths and ${HOME} do the same. No warning.

Logs/Screenshots

viaSymlink     : /tmp/nc-cwd-proj/scripts
symlinkReal    : /private/tmp/nc-cwd-out
escaped        : true

absolute cwd=/tmp/nc-cwd-out
absEscaped     : true

missing path   : falls back to projectRoot  (the existing test)

Additional Context

Small fix: run the resolved cwd through the same realpath + containment check as resolveFilePath / getContainedSessionCwd. If it is outside, return projectRoot.

Would add tests for symlink-out, absolute-out, ${HOME}, and a normal in-project relative path.

I can open a PR if this gets assigned.

  • I have searched existing issues to ensure this is not a duplicate
  • I can reproduce this issue consistently
  • This issue occurs with the latest version of nanocoder

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions