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

  1. Read result.error for the exact rule that fired and fix the input (remove metacharacters/null bytes, shorten the string, drop denylisted env keys)
  2. For user-facing inputs prefer the non-throwing API: branch on result.valid and return a typed error instead of assertValid
  3. For Windows paths rely on validatePath's backslash normalization; avoid shell metacharacters in any path handed to a shell
  4. 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

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


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)