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

  1. Reject or sanitize the path at the input boundary before it reaches fromFile — strip control characters, not just NUL
  2. 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
  3. Return a 400-style error to the caller rather than retrying — the input is malformed, not transient
  4. 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

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


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)