ruvnet/ruflo · error

unsafe build input path: ${value}

Error message

unsafe build input path: ${value}

What it means

normalizePath enforces a strict repository-relative POSIX shape for declared build input paths: no backslashes, no leading '/' (absolute) or '-', Unicode NFC normalization, and no empty, '.', or '..' segments. Any violation throws before the path is resolved or hashed. These rules keep declarations portable across platforms and prevent option-injection and traversal at digest time.

Source

Thrown at v3/@claude-flow/codex/src/harness/build-evidence.ts:73

  if (!result) throw new Error(`${label} must be non-empty`);
  return result;
}

function requireDigest(value: string, label: string): string {
  if (!DIGEST.test(value)) throw new Error(`${label} must be a canonical sha256 digest`);
  return value;
}

function normalizePath(value: string): string {
  const path = requireText(value, 'build input path');
  if (
    path.includes('\\')
    || path.startsWith('/')
    || path.startsWith('-')
    || path !== path.normalize('NFC')
    || path.split('/').some((part) => !part || part === '.' || part === '..')
  ) {
    throw new Error(`unsafe build input path: ${value}`);
  }
  return path;
}

function sha256(value: string): string {
  return `sha256:${createHash('sha256').update(value).digest('hex')}`;
}

function digestPath(path: string, followSymlink: boolean): { digest: string; bytes: number } {
  const resolved = followSymlink ? realpathSync(path) : path;
  const stat = lstatSync(resolved);
  const content = stat.isSymbolicLink()
    ? Buffer.from(readlinkSync(resolved), 'utf8')
    : stat.isFile()
      ? readFileSync(resolved)
      : undefined;
  if (!content) throw new Error(`build evidence path is not a file or symlink: ${path}`);
  return {

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Declare plain relative POSIX paths from the repo root, e.g. 'dist/assets/manifest.json'
  2. Strip leading './' and collapse '//', '.', and '..' segments before declaring
  3. Normalize unicode once at the source: value.normalize('NFC')
  4. Never pass path.resolve() or path.join() results directly — relativize against the repo root first

Example fix

// before
const inputs = [
  { name: 'config', path: path.resolve(repoRoot, 'dist\app.json') }, // absolute + backslash
];

// after
const inputs = [
  { name: 'config', path: 'dist/app.json' },
];
Defensive patterns

Strategy: validation

Validate before calling

function isSafeRelativeInputPath(value: string): boolean {
  return !value.includes('\\')
    && !value.startsWith('/')
    && !value.startsWith('-')
    && value === value.normalize('NFC')
    && value.split('/').every((part) => part && part !== '.' && part !== '..');
}

Type guard

function isSafeRelativeInputPath(value: unknown): value is string {
  return typeof value === 'string'
    && !value.includes('\\')
    && !value.startsWith('/')
    && !value.startsWith('-')
    && value === value.normalize('NFC')
    && value.split('/').every((part) => part && part !== '.' && part !== '..');
}

Prevention

When it happens

Trigger: A declared path containing a backslash ('dist\app.js'), starting with '/' or '-', not in NFC form, or containing '', '.', or '..' segments such as 'src/./x', 'src/../x', 'src//x'.

Common situations: Feeding path.join/path.resolve output (absolute, platform separators) into declarations; copying paths from Windows shells; pasting macOS filenames (NFD-normalized) into configs; leaving a leading './' in generated paths.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/2b07a3590c914f5d. Report an issue: GitHub.