ruvnet/ruflo · error
Path contains null bytes
Error message
Path contains null bytes
What it means
RvfaReader.fromFile rejects any path containing a NUL byte ('\0') before handing it to readFile. On POSIX systems an embedded NUL truncates the effective path at the C-string boundary, which is a classic path-injection primitive (e.g. 'safe-dir/../../etc/passwd\0.png' being treated as 'safe-dir/../../etc/passwd'). Node's fs would throw its own opaque error; this guard fails fast with an explicit message.
Solutions
- Reject or sanitize the path at the input boundary before it reaches fromFile — strip control characters, not just NUL
- Trace where the string originated (URL query, env var, file listing) and validate/encode it there; decodeURIComponent on attacker-controlled input is a common source
- Return a 400-style error to the caller rather than retrying — the input is malformed, not transient
- Prefer allowlisting (e.g. /^[\w.-]+\.rvfa$/) over blocklisting for filenames you accept
Example fix
// before — raw external input straight to fromFile
const reader = await RvfaReader.fromFile(req.query.path);
// after — validate the shape at the boundary
const m = String(req.query.path ?? '').match(/^[\w./-]+\.rvfa$/);
if (!m) throw new Error('invalid image path');
const reader = await RvfaReader.fromFile(m[0]); Defensive patterns
Strategy: validation
Validate before calling
function isSafeImagePath(p: string): boolean {
return typeof p === 'string' && !p.includes('\0') && /^[\w./-]+\.rvfa$/.test(p);
} Type guard
function isNullByteFreePath(p: unknown): p is string {
return typeof p === 'string' && !p.includes('\0');
} Try / catch
try { reader = await RvfaReader.fromFile(path); }
catch (e) {
if (e instanceof Error && e.message === 'Path contains null bytes') {
// reject the request/input; never sanitize by stripping \0 and continuing blindly
}
throw e;
} Prevention
- Validate paths at the trust boundary with an allowlist pattern, not a NUL blocklist
- Never build paths from raw URL-decoded or archive-entry strings
- Log and reject (400) rather than cleaning tainted filenames
When it happens
Trigger: Calling await RvfaReader.fromFile(path) where path includes '\0' — typically because the path came from untrusted user input, a URL-decoded string, or upstream data containing raw binary/control bytes.
Common situations: Paths assembled from CLI arguments, HTTP parameters, or archive entry names without sanitization; binary data accidentally concatenated into a path buffer; test harnesses feeding fuzzed filenames. Legitimate filesystem paths never contain NUL, so seeing this error always means tainted input.
Related errors
- Section " " has negative offset or size
- AI budget file is a symlink (refusing)
- AI job registry is a symlink (refusing)
- AIDefence failed to load
- AIDefence installed but failed to load
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/9486e3f27b89d0cb.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/cli/src/appliance/rvfa-format.ts:387
const sorted = [...header.sections].sort((a, b) => a.offset - b.offset);
for (let i = 1; i < sorted.length; i++) {
const prev = sorted[i - 1];
const curr = sorted[i];
if (prev.offset + prev.size > curr.offset) {
throw new Error(
`Sections "${prev.id}" and "${curr.id}" overlap ` +
`(${prev.offset}+${prev.size} > ${curr.offset})`,
);
}
}
return new RvfaReader(buf, header);
}
/** Read an RVFA image from a file path. */
static async fromFile(path: string): Promise<RvfaReader> {
if (path.includes('\0')) {
throw new Error('Path contains null bytes');
}
const data = await readFile(path);
return RvfaReader.fromBuffer(data);
}
/** Return the parsed header. */
getHeader(): RvfaHeader {
return this.header;
}
/** List all sections declared in the header. */
getSections(): RvfaSection[] {
return this.header.sections;
}
/**
* Extract and decompress a section by its id.
*View on GitHub (pinned to fa13ee4ad6)