{"record":{"id":"174aa213cf461bb2","repo":"ruvnet/ruflo","slug":"invalid-argument-contains-shell-metacharacters","errorCode":null,"errorMessage":"Invalid argument: contains shell metacharacters","messagePattern":"Invalid argument: contains shell metacharacters","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/deployment/src/publisher.ts","lineNumber":201,"sourceCode":"\n      const output = this.execNpmCommand(packArgs, true);\n      const tarballName = output.trim().split('\\n').pop() || '';\n\n      return outputDir ? join(outputDir, tarballName) : tarballName;\n    } catch (error) {\n      throw new Error(`Failed to pack: ${error}`);\n    }\n  }\n\n  /**\n   * Execute npm command safely using execFileSync\n   */\n  private execNpmCommand(args: string[], returnOutput = false): string {\n    try {\n      // Validate args don't contain shell metacharacters\n      for (const arg of args) {\n        if (/[;&|`$()<>]/.test(arg)) {\n          throw new Error(`Invalid argument: contains shell metacharacters`);\n        }\n      }\n      const output = execFileSync('npm', args, {\n        cwd: this.cwd,\n        encoding: 'utf-8',\n        shell: false,\n        stdio: returnOutput ? ['pipe', 'pipe', 'pipe'] : 'inherit'\n      });\n      return returnOutput ? output : '';\n    } catch (error) {\n      throw error;\n    }\n  }\n\n  /**\n   * Execute command (for build scripts only - validated)\n   */\n  private execCommand(cmd: string, returnOutput = false): string {","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/deployment/src/publisher.ts#L183-L219","documentation":"execNpmCommand() rejects any argument containing ; & | ` $ ( ) < > before spawning npm — even though it uses execFileSync with shell: false. It is a defense-in-depth injection guard, so values that are harmless under shell:false but merely contain those characters (Windows paths with parentheses, decorated dist-tags) are rejected too.","triggerScenarios":"publishToNpm({ tag: 'next$(whoami)' }) or any tag/registry/otp string containing ()<>|&`$; pack('dir (1)') with parentheses in the outputDir; branch-derived tag names like 'feature/fix-(auth)'.","commonSituations":"Windows directories with parentheses (e.g. 'Dir (1)'); automated tag names built from branch or PR titles; release scripts interpolating versions like '1.0.0-beta(1)'.","solutions":["Restrict tag and outputDir values to [A-Za-z0-9._-]: use 'beta-1' not 'beta (1)', 'next-rc1' not 'next$(date)'","Choose an outputDir without parentheses or special characters, or omit outputDir and move the resulting tarball yourself","Pre-validate with the same class /[;&|`$()<>]/ before calling pack/publishToNpm so you control the error message"],"exampleFix":"// before\nawait publisher.publishToNpm({ tag: 'release (main)' }); // throws: shell metacharacters\n\n// after\nawait publisher.publishToNpm({ tag: 'release-main' });","handlingStrategy":"validation","validationCode":"const METACHARS = /[;&|`$()<>]/;\nfunction isSafeNpmArg(value: string): boolean {\n  return typeof value === 'string' && value.length > 0 && !METACHARS.test(value);\n}\nif (!isSafeNpmArg(tag)) throw new Error('tag contains shell metacharacters');\nawait publisher.publishToNpm({ tag });","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Generate dist-tags and paths from [A-Za-z0-9._-] only","Sanitize branch/PR-derived values before using them as npm tags or destinations"],"tags":["npm","security","command-injection","input-validation"],"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-14T00:17:10.932Z"}