{"record":{"id":"24338abb231cd2e8","repo":"affaan-m/ECC","slug":"claude-code-command-contains-characters-that-are-u","errorCode":null,"errorMessage":"Claude Code command contains characters that are unsafe for cmd.exe","messagePattern":"Claude Code command contains characters that are unsafe for cmd\\.exe","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/claude-plugin-setup.js","lineNumber":141,"sourceCode":"      || typeof marketplace.source !== 'string'\n      || !['github', 'git'].includes(marketplace.source)\n      || !normalizeMarketplaceRepository(marketplace)\n    ) {\n      fail(\n        'INVALID_MARKETPLACE_INVENTORY',\n        'Claude marketplace inventory contains an invalid `ecc` entry'\n      );\n    }\n  }\n  return marketplaces;\n}\n\nconst UNSAFE_WINDOWS_SHELL_CHARS = /[\\r\\n&|<>^%!]/;\n\nfunction quoteWindowsCommandToken(value) {\n  const token = String(value);\n  if (UNSAFE_WINDOWS_SHELL_CHARS.test(token)) {\n    throw new Error('Claude Code command contains characters that are unsafe for cmd.exe');\n  }\n  if (token === '') return '\"\"';\n  if (!/[\\s\"]/.test(token)) return token;\n  return `\"${token.replace(/\"/g, '\"\"')}\"`;\n}\n\nfunction buildWindowsCommandLine(command, args) {\n  return [command, ...args].map(quoteWindowsCommandToken).join(' ');\n}\n\nfunction resolveWindowsCmdShim(command, env) {\n  if (typeof command !== 'string' || command.length === 0) return null;\n  if (/\\.(cmd|bat)$/i.test(command)) return command;\n  if (path.extname(command)) return null;\n\n  const isPathLike = path.isAbsolute(command)\n    || command.includes('/')\n    || command.includes('\\\\');","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/claude-plugin-setup.js#L123-L159","documentation":"quoteWindowsCommandToken quotes a single argument for cmd.exe before invoking the Claude Code CLI on Windows. cmd.exe treats characters like & | < > ^ % ! and newlines as metacharacters, so any token containing them is rejected outright rather than escaped, preventing command injection or broken commands.","triggerScenarios":"Calling the Windows command builder with arguments containing & (e.g. \"a && b\"), pipes, redirection characters, ^ or % (env expansion), ! (delayed expansion), or embedded \\r\\n — typically user-controlled values such as repo names, paths, or plugin names flowing into the claude CLI command.","commonSituations":"Plugin or marketplace names pasted with shell operators; Windows paths accidentally containing stray characters; scripts that build multi-command strings instead of passing one token per argument; CI inputs with untrusted text.","solutions":["Remove or encode the unsafe characters from the offending argument before calling the setup API.","Pass each logical argument as a separate token instead of one combined command string (never \"plugin add foo && rm -rf x\").","If a value legitimately contains such characters (e.g. % in a path), rename the file/directory or use an 8.3/alternate path without metacharacters.","Sanitize upstream inputs (validate plugin/repo names against ^[A-Za-z0-9._\\/-]+$) before they reach the command builder."],"exampleFix":"// before\nconst cmd = buildWindowsCommand(['plugin', 'add', userInput]); // userInput = \"foo && calc\"\n// after\nif (!/^[\\w.\\/-]+$/.test(userInput)) throw new Error('Invalid plugin name');\nconst cmd = buildWindowsCommand(['plugin', 'add', userInput]);","handlingStrategy":"validation","validationCode":"const UNSAFE = /[\\r\\n&|<>^%!]/;\nfunction assertSafeToken(v) { if (UNSAFE.test(String(v))) throw new Error(`Unsafe cmd.exe characters in: ${v}`); }","typeGuard":"const isShellSafe = (v) => typeof v === 'string' && !/[\\r\\n&|<>^%!]/.test(v);","tryCatchPattern":"try { await setupPlugin({ name }); } catch (e) { if (e.message.includes('unsafe for cmd.exe')) { console.error('Strip shell metacharacters from the argument'); } else throw e; }","preventionTips":["Validate user/plugin/repo inputs against a strict allowlist regex before they reach commands","Pass one argument per token; never build combined command strings","On Windows, prefer spawning without shell:true and quote via this library only"],"tags":["security","windows","shell"],"backgroundTag":"invalid-argument-value","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}