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
- Declare plain relative POSIX paths from the repo root, e.g. 'dist/assets/manifest.json'
- Strip leading './' and collapse '//', '.', and '..' segments before declaring
- Normalize unicode once at the source: value.normalize('NFC')
- 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
- Convert absolute paths to repo-relative with path.relative(repoRoot, abs) and POSIX separators before declaring
- Normalize all declaration strings with .normalize('NFC') at ingest
- Add a lint/test that greps declarations for backslashes and leading '-'
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
- case-fold collision in declared build inputs
- build input escapes repository: ${path}
- unsafe repository-relative path: ${path}
- repository path escapes root: ${candidate}
- unknown game "${key}". Known: ${Object.keys(GAMES).join(', '
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/2b07a3590c914f5d.
Report an issue: GitHub.