ruvnet/ruflo · error · Error
escapes project directory
Error message
${label} escapes project directory What it means
Thrown by validatePath() in the daemon command when the resolved absolute path is neither under the current working directory nor inside a directory whose resolved path contains '.claude-flow' or 'bin'. The guard prevents path traversal: daemon paths are expected to stay within the project or the tool's own layout directories.
Solutions
- cd into the project root first so the resolved workspace starts with process.cwd()
- Use a workspace under <project>/.claude-flow — the '.claude-flow' path segment is whitelisted
- If the path targets a binary, keep it in a directory whose resolved path contains 'bin'
- Verify what the path resolves to: node -e "console.log(require('path').resolve(process.argv[1]))" <your-path>
Example fix
# before (run from ~/other) claude-flow daemon start --workspace /home/me/repo/data # after cd /home/me/repo && claude-flow daemon start --workspace /home/me/repo/.claude-flow
Defensive patterns
Strategy: validation
Validate before calling
import { resolve } from 'node:path';
function isInsideProject(p: string): boolean {
const r = resolve(p);
return r.includes('.claude-flow') || r.includes(`${resolve('bin')}`) || r.startsWith(process.cwd());
}
if (!isInsideProject(workspace)) workspace = resolve(process.cwd(), '.claude-flow'); Try / catch
try {
await cli.daemon.start({ workspace });
} catch (err) {
if (err instanceof Error && err.message.endsWith('escapes project directory')) {
// re-run from the project root or point the path under <project>/.claude-flow
} else throw err;
} Prevention
- Always run daemon commands from the project root
- Default workspaces to <projectRoot>/.claude-flow in wrappers
- Resolve relative paths against the project root before passing them
When it happens
Trigger: Running `claude-flow daemon start --workspace /tmp/scratch` from ~/repo (path is outside cwd and has no .claude-flow/bin segment), passing a relative path like ../../elsewhere that resolves outside cwd, or invoking the CLI from a different directory than the project.
Common situations: cd'ed into the wrong directory before running the command; pointing the workspace at a shared absolute path (/var/lib/...); containers or symlinks where process.cwd() differs from where the project actually lives.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- contains shell metacharacters
- approval issuance requires an authenticated human identity…
- basePath contains disallowed characters
- build input escapes repository
- Invalid filename
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/f8617774e51ac9e8.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/cli/src/commands/daemon.ts:385
// Must be absolute after resolution
const resolved = resolve(path);
// Check for null bytes (injection attack)
if (path.includes('\0')) {
throw new Error(`${label} contains null bytes`);
}
// Check for shell metacharacters in path components
if (/[;&|`$<>]/.test(path)) {
throw new Error(`${label} contains shell metacharacters`);
}
// Prevent path traversal outside expected directories
if (!resolved.includes('.claude-flow') && !resolved.includes('bin')) {
// Allow only paths within project structure
const cwd = process.cwd();
if (!resolved.startsWith(cwd)) {
throw new Error(`${label} escapes project directory`);
}
}
}
/**
* #1914: Resolve the `--workspace` flag to an absolute path, or return null
* if it is absent / not a usable string. Rejects values with null bytes or
* shell metacharacters (defence-in-depth — the value is later embedded in a
* forked child's argv and compared against `ps`/`tasklist` output).
*/
export function resolveWorkspaceFlag(raw: unknown): string | null {
if (typeof raw !== 'string') return null;
const trimmed = raw.trim();
if (!trimmed) return null;
if (trimmed.includes('\0') || /[;&|`$<>]/.test(trimmed)) return null;
return resolve(trimmed);
}
View on GitHub (pinned to fa13ee4ad6)