ruvnet/ruflo · error
Validation failed
Error message
Validation failed: ${result.error} What it means
assertValid is the throwing wrapper over the ValidationResult returned by the validate-input validators (validatePath, validateText, validateIdentifier, validateEnv, …). When result.valid is false it throws with the specific rule that fired embedded in the message — null bytes, path traversal, shell metacharacters, length caps, denylisted env names, bad types. Hitting it means the input failed schema/security validation and the code path chose exceptions over branching.
Solutions
- Read result.error for the exact rule that fired and fix the input (remove metacharacters/null bytes, shorten the string, drop denylisted env keys)
- For user-facing inputs prefer the non-throwing API: branch on result.valid and return a typed error instead of assertValid
- For Windows paths rely on validatePath's backslash normalization; avoid shell metacharacters in any path handed to a shell
- Add unit tests for each rejection rule so regressions in the sanitizer surface locally
Example fix
// before
const path = assertValid(validatePath(input.file_path, 'file_path'));
// after
const res = validatePath(input.file_path, 'file_path');
if (!res.valid) {
return { error: `invalid file_path: ${res.error}` }; // typed error to caller
}
const path = res.sanitized; Defensive patterns
Strategy: validation
Validate before calling
const res = validatePath(input.file_path, 'file_path');
if (!res.valid) {
return { error: `invalid file_path: ${res.error}` };
}
const filePath = res.sanitized; Type guard
function isValidationResultOk(r: ValidationResult): r is { valid: true; sanitized: string } {
return r.valid;
} Try / catch
try { value = assertValid(validateText(raw, 'description')); }
catch (e) { if (e instanceof Error && e.message.startsWith('Validation failed')) { replyToCaller(e.message); } else throw e; } Prevention
- Branch on result.valid for user-supplied input; reserve assertValid for trusted internal invariants
- Test each validator rule (null byte, traversal, metacharacter, length, denylist) in CI
- Surface result.error verbatim to callers so they can fix the offending field
When it happens
Trigger: assertValid(validatePath(v, 'file_path')) where v is empty, over 4096 chars, contains '..' or shell metacharacters like ; & | $() backticks; assertValid(validateText(...)) on a non-string or >10,000-char value; assertValid(validateEnv(env)) where a key is LD_PRELOAD/NODE_OPTIONS, is not a POSIX name, or a value contains a null byte.
Common situations: MCP tool handlers validating untrusted tool_input; user-supplied env objects containing NODE_OPTIONS; long descriptions exceeding the 10k cap; paths that legitimately contain '$' or parentheses being sent to shell execution.
Related errors
- Dangerous key segment rejected
- FORBIDDEN_PROTOCOL
- Key contains disallowed characters
- memory path contains disallowed characters
- Namespace contains disallowed characters
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/068badbee170b383.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/cli-core/src/mcp-tools/validate-input.ts:201
return { valid: false, sanitized: {}, error: `${label}["${name}"] must be a string` };
}
if (rawVal.length > 32_768) {
return { valid: false, sanitized: {}, error: `${label}["${name}"] exceeds 32768 characters` };
}
if (rawVal.includes('\0')) {
return { valid: false, sanitized: {}, error: `${label}["${name}"] contains a null byte` };
}
out[name] = rawVal;
}
return { valid: true, sanitized: out };
}
/**
* Assert validation or throw with a structured error.
*/
export function assertValid(result: ValidationResult): string {
if (!result.valid) {
throw new Error(`Validation failed: ${result.error}`);
}
return result.sanitized;
}
// Try to load the full @claude-flow/security module for enhanced validation
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let _securityModule: Record<string, any> | null = null;
let _securityLoaded = false;
async function getSecurityModule(): Promise<Record<string, any> | null> {
if (_securityLoaded) return _securityModule;
_securityLoaded = true;
try {
// Dynamic import — @claude-flow/security is an optional dependency
_securityModule = await (Function('return import("@claude-flow/security")')() as Promise<Record<string, any>>);
} catch {
// @claude-flow/security is optional — fallback to inline validation above
}View on GitHub (pinned to fa13ee4ad6)