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

  1. cd into the project root first so the resolved workspace starts with process.cwd()
  2. Use a workspace under <project>/.claude-flow — the '.claude-flow' path segment is whitelisted
  3. If the path targets a binary, keep it in a directory whose resolved path contains 'bin'
  4. 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

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


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)