{"record":{"id":"bbfb88dbc0a72cf9","repo":"ruvnet/ruflo","slug":"invalid-command-contains-shell-metacharacters-bbfb88","errorCode":null,"errorMessage":"Invalid command: contains shell metacharacters","messagePattern":"Invalid command: contains shell metacharacters","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/deployment/src/release-manager.ts","lineNumber":29,"sourceCode":" * Allowed git commands for security - prevents command injection\n */\nconst ALLOWED_GIT_COMMANDS = [\n  'git status --porcelain',\n  'git rev-parse HEAD',\n  'git log',\n  'git tag',\n  'git add',\n  'git commit',\n  'git describe',\n];\n\n/**\n * Validate command against allowlist to prevent command injection\n */\nfunction validateCommand(cmd: string): void {\n  // Check for shell metacharacters\n  if (/[;&|`$()<>]/.test(cmd)) {\n    throw new Error(`Invalid command: contains shell metacharacters`);\n  }\n\n  // Must start with an allowed command prefix\n  const isAllowed = ALLOWED_GIT_COMMANDS.some(prefix => cmd.startsWith(prefix));\n  if (!isAllowed) {\n    throw new Error(`Command not allowed: ${cmd.split(' ')[0]}`);\n  }\n}\nimport type {\n  ReleaseOptions,\n  ReleaseResult,\n  PackageInfo,\n  GitCommit,\n  ChangelogEntry,\n  VersionBumpType\n} from './types.js';\n\nexport class ReleaseManager {","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/deployment/src/release-manager.ts#L11-L47","documentation":"ReleaseManager.execCommand() validates git command strings with validateCommand(), rejecting anything containing ; & | ` $ ( ) < >. Critically, prepareRelease() itself builds the commit command `git commit -m \"chore(release): <version>\"` — the literal parentheses in 'chore(release)' match the guard, so with the default commit: true the release flow throws this error at the commit step after the version bump and changelog were already written.","triggerScenarios":"prepareRelease() with commit: true (the default): the generated `git commit -m \"chore(release): 1.2.3\"` contains ( and ) and is rejected by validateCommand. Also triggered by caller-built strings passed through ReleaseManager containing &&, $(...), or parentheses in paths or messages.","commonSituations":"Any first run of prepareRelease() that gets far enough to commit — the tag step and `git add` pass, but the commit line trips the parenthesis check; release scripts with chained git commands.","solutions":["Pass commit: false to prepareRelease() and create the release commit yourself: git add package.json CHANGELOG.md && git commit -m 'chore(release): x.y.z' — you get createTag: true for the annotated tag, which passes validation","Report/upstream the bug: the built-in commit message conflicts with the module's own metacharacter regex","Avoid parentheses, &&, $(), and redirects in any command string that reaches ReleaseManager"],"exampleFix":"// before\nawait manager.prepareRelease({ bumpType: 'patch' }); // throws at `git commit -m \"chore(release): ...\"`\n\n// after\nconst r = await manager.prepareRelease({ bumpType: 'patch', commit: false });\nexecSync('git add package.json CHANGELOG.md');\nexecSync('git commit -m \"chore(release): ' + r.newVersion + '\"');","handlingStrategy":"validation","validationCode":"// The library's own commit message contains parentheses, so bypass its commit step\nawait manager.prepareRelease({ bumpType, commit: false, createTag: true });\nexecSync('git add package.json CHANGELOG.md');\nexecSync(`git commit -m \"chore(release): ${newVersion}\"`); // your own exec, no allowlist","typeGuard":null,"tryCatchPattern":"const r = await manager.prepareRelease(options);\nif (!r.success && /shell metacharacters/.test(r.error ?? '')) {\n  // version/changelog were written but commit/tag aborted: finish git steps manually\n}","preventionTips":["Always pass commit: false to prepareRelease() until the built-in 'chore(release)' message is fixed against the metacharacter regex","Check result.error on ReleaseResult — prepareRelease records this failure instead of throwing"],"tags":["git","release","security","command-injection","bug"],"backgroundTag":"command-injection-guard","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}