ruvnet/ruflo · error

Key contains disallowed characters

Error message

Key contains disallowed characters

What it means

validateMemoryInput rejects keys containing shell metacharacters or traversal sequences via DANGEROUS_KEY_PATTERN: ; & | ` $ ( ) { } [ ] < > ! # \ and NUL, plus ../ or ..\ (#1425 — anti shell-injection and path-traversal for the memory backends). The key fails before anything is written or read. Spaces, unicode, /, and : are allowed by this particular check — only the listed metacharacters and dot-dot-slash trigger it.

Solutions

  1. Sanitize before the call with the same replacement the library uses: key.replace(/[;&|`$(){}[\]<>!#\\\0]|\.\.[/\\]/g, '_')
  2. Generate keys through a slug function restricted to [A-Za-z0-9_-]
  3. For markdown-derived keys, strip leading # and punctuation before storing
  4. If the original string must survive verbatim, hash it into the key and keep the original inside the value

Example fix

// before
const key = `## ${heading} (${section})`; // contains # ( ) -> Key contains disallowed characters
await mcp.callTool('memory_store', { key, value });

// after — mirror sanitizeMemoryKey
const safeKey = heading.replace(/[;&|`$(){}[\]<>!#\\\0]|\.\.[/\\]/g, '_').slice(0, 1024);
await mcp.callTool('memory_store', { key: safeKey, value });
Defensive patterns

Strategy: validation

Validate before calling

// Same dangerous-character set the library enforces (memory-tools DANGEROUS_KEY_CHARS).
const DANGEROUS_KEY_CHARS = /[;&|`$(){}[\]<>!#\\\0]|\.\.[/\\]/g;
function sanitizeMemoryKey(key: string, maxLen = 1024): string {
  const safe = key.replace(DANGEROUS_KEY_CHARS, '_');
  return safe.length > maxLen ? safe.slice(0, maxLen) : safe;
}
// const key = sanitizeMemoryKey(rawHeadingOrSymbol);

Type guard

function isSafeMemoryKey(key: string): boolean {
  return key.length <= 1024 && !/[;&|`$(){}[\]<>!#\\\0]|\.\.[/\\]/.test(key);
}

Try / catch

try {
  await memoryStore({ key, value });
} catch (e) {
  if (e instanceof Error && e.message.includes('Key contains disallowed characters')) {
    // sanitize and retry once: key = key.replace(/[;&|`$(){}[\]<>!#\\\0]|\.\.[/\\]/g, '_')
  }
  throw e;
}

Prevention

When it happens

Trigger: Keys derived from markdown headings ('## Overview' contains #), code symbols ('resize(width)' contains parentheses), template placeholders ('user$name'), URLs with query strings ('a?x=1&y=2' contains &), or path-like keys ('../etc/passwd', 'a\..\b').

Common situations: Auto-keying memory entries from headings, function signatures, or pasted prose; LLM-generated keys that copy punctuation; migrating from a store that accepted arbitrary strings; the library's own read path hits this too, which is why it ships sanitizeMemoryKey to strip these characters.

Related errors


AI-assisted analysis of ruvnet/ruflo@9c61c86f06 (2026-08-18). Data as JSON: /api/errors/b9c5bad9e2dcc4bf. Report an issue: GitHub.

Appendix: source

Thrown at v3/@claude-flow/cli/src/mcp-tools/memory-tools.ts:83

// #1425 — single source of truth for the dangerous-character set rejected by
// validateMemoryInput. Imported by sanitizeMemoryKey so write-side sanitization
// and read-side rejection can never drift apart (the symmetry bug behind #1884).
const DANGEROUS_KEY_CHARS = /[;&|`$(){}[\]<>!#\\\0]|\.\.[/\\]/g;
const DANGEROUS_KEY_PATTERN = /[;&|`$(){}[\]<>!#\\\0]|\.\.[/\\]/;

function validateMemoryInput(key?: string, value?: string, query?: string, namespace?: string): void {
  if (key && key.length > MAX_KEY_LENGTH) {
    throw new Error(`Key exceeds maximum length of ${MAX_KEY_LENGTH} characters`);
  }
  if (value && value.length > MAX_VALUE_SIZE) {
    throw new Error(`Value exceeds maximum size of ${MAX_VALUE_SIZE} bytes`);
  }
  if (query && query.length > MAX_QUERY_LENGTH) {
    throw new Error(`Query exceeds maximum length of ${MAX_QUERY_LENGTH} characters`);
  }
  // Reject path traversal and shell metacharacters in keys/namespaces (#1425)
  if (key && DANGEROUS_KEY_PATTERN.test(key)) {
    throw new Error('Key contains disallowed characters');
  }
  if (namespace && DANGEROUS_KEY_PATTERN.test(namespace)) {
    throw new Error('Namespace contains disallowed characters');
  }
}

// #1884 — sanitize a key produced from arbitrary input (markdown headings,
// frontmatter names, file names) so it survives validateMemoryInput on the
// read/delete path. Replaces every dangerous char with `_`. Truncates to
// MAX_KEY_LENGTH so the bound check in validateMemoryInput also passes.
// Keep this in sync with DANGEROUS_KEY_PATTERN — they share DANGEROUS_KEY_CHARS.
function sanitizeMemoryKey(key: string): string {
  const safe = key.replace(DANGEROUS_KEY_CHARS, '_');
  return safe.length > MAX_KEY_LENGTH ? safe.slice(0, MAX_KEY_LENGTH) : safe;
}

// #1937 — minimal glob → RegExp helper for memory_import_claude exclusion
// patterns. Anchored. Supports the three operators the issue's voice-fidelity

View on GitHub (pinned to 9c61c86f06)