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
- Sanitize before the call with the same replacement the library uses: key.replace(/[;&|`$(){}[\]<>!#\\\0]|\.\.[/\\]/g, '_')
- Generate keys through a slug function restricted to [A-Za-z0-9_-]
- For markdown-derived keys, strip leading # and punctuation before storing
- 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
- Generate keys through a slug/allowlist function ([A-Za-z0-9_-]) instead of pasting raw prose, headings, or URLs
- Strip markdown '#' prefixes and punctuation before keying from headings
- Keep the sanitizer in sync with the library's set: ; & | ` $ ( ) { } [ ] < > ! # \ NUL and ../
- Add a unit test that round-trips your key generator through the same dangerous-character regex
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
- Namespace contains disallowed characters
- Key exceeds maximum length of
- memory path contains disallowed characters
- namespace contains disallowed characters
- Dangerous key segment rejected
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-fidelityView on GitHub (pinned to 9c61c86f06)