{"record":{"id":"536b70ed34c9c05a","repo":"ruvnet/ruflo","slug":"disallowed-command-only-npm-npx-pnpm-yarn-command","errorCode":null,"errorMessage":"Disallowed command: only npm/npx/pnpm/yarn commands are permitted","messagePattern":"Disallowed command: only npm/npx/pnpm/yarn commands are permitted","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/deployment/src/publisher.ts","lineNumber":224,"sourceCode":"        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 {\n    // Only allow npm/npx build commands for safety\n    const allowedPrefixes = ['npm run ', 'npm ', 'npx ', 'pnpm ', 'yarn '];\n    const isAllowed = allowedPrefixes.some(prefix => cmd.startsWith(prefix));\n    if (!isAllowed) {\n      throw new Error(`Disallowed command: only npm/npx/pnpm/yarn commands are permitted`);\n    }\n    // Validate no dangerous shell metacharacters\n    if (/[;&|`$()<>]/.test(cmd)) {\n      throw new Error(`Invalid command: contains shell metacharacters`);\n    }\n    try {\n      const output = execSync(cmd, {\n        cwd: this.cwd,\n        encoding: 'utf-8',\n        stdio: returnOutput ? 'pipe' : 'inherit'\n      });\n      return returnOutput ? output : '';\n    } catch (error) {\n      throw error;\n    }\n  }\n}\n","sourceCodeStart":206,"sourceCodeEnd":242,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/deployment/src/publisher.ts#L206-L242","documentation":"Publisher.execCommand() — used to run the buildCommand option of publishToNpm() — only accepts strings starting with 'npm run ', 'npm ', 'npx ', 'pnpm ', or 'yarn '. Anything else is treated as a potential command injection and rejected before the build executes.","triggerScenarios":"publishToNpm({ buildCommand: 'node build.js' }), 'make build', './scripts/build.sh', or 'tsc -p .' — none matches an allowed prefix.","commonSituations":"Packages whose build is a plain node script or make target; migrating a hand-rolled release script that invoked the build directly.","solutions":["Move the build into package.json scripts (\"scripts\": { \"build\": \"node build.js\" }) and pass buildCommand: 'npm run build'","Or use an allowlisted prefix form: 'npx tsc -p .', 'pnpm build', 'yarn build'","Or pass skipBuild: true and run your custom build yourself before calling publishToNpm"],"exampleFix":"// before\nawait publisher.publishToNpm({ buildCommand: 'node ./scripts/bundle.js' }); // throws\n\n// after\n// package.json: \"scripts\": { \"build\": \"node ./scripts/bundle.js\" }\nawait publisher.publishToNpm({ buildCommand: 'npm run build' });","handlingStrategy":"validation","validationCode":"const ALLOWED = ['npm run ', 'npm ', 'npx ', 'pnpm ', 'yarn '];\nfunction isAllowedBuildCommand(cmd: string): boolean {\n  return ALLOWED.some(p => cmd.startsWith(p)) && !/[;&|`$()<>]/.test(cmd);\n}\nif (!isAllowedBuildCommand(buildCommand)) buildCommand = 'npm run build';\nawait publisher.publishToNpm({ buildCommand });","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Express every build as an npm script so the default 'npm run build' just works","Never pass node/make/shell-script paths as buildCommand"],"tags":["build","security","command-injection","allowlist","npm"],"backgroundTag":"command-allowlist-rejected","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"}