{"record":{"id":"3cd7aa43e82e2bba","repo":"can1357/oh-my-pi","slug":"invalid-package-name-name","errorCode":null,"errorMessage":"Invalid package name: ${name}","messagePattern":"Invalid package name: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/extensibility/plugins/installer.ts","lineNumber":17,"sourceCode":"import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { getAgentDir, getProjectDir, isEnoent } from \"@oh-my-pi/pi-utils\";\nimport { extractPackageName } from \"./parser\";\nimport type { InstalledPlugin } from \"./types\";\n\nconst PLUGINS_DIR = path.join(getAgentDir(), \"plugins\");\n\n// Valid npm package name pattern (scoped and unscoped)\nconst VALID_PACKAGE_NAME = /^(@[a-z0-9-~][a-z0-9-._~]*\\/)?[a-z0-9-~][a-z0-9-._~]*(@[a-z0-9-._^~>=<]+)?$/i;\n\n/**\n * Validate package name to prevent command injection\n */\nfunction validatePackageName(name: string): void {\n\tif (!VALID_PACKAGE_NAME.test(name)) {\n\t\tthrow new Error(`Invalid package name: ${name}`);\n\t}\n\t// Extra safety: no shell metacharacters\n\tif (/[;&|`$(){}[\\]<>\\\\]/.test(name)) {\n\t\tthrow new Error(`Invalid characters in package name: ${name}`);\n\t}\n}\n\n/**\n * Ensure the plugins directory exists\n */\nasync function ensurePluginsDir(): Promise<void> {\n\tawait fs.mkdir(PLUGINS_DIR, { recursive: true });\n\tawait fs.mkdir(path.join(PLUGINS_DIR, \"node_modules\"), { recursive: true });\n}\n\nexport async function installPlugin(packageName: string): Promise<InstalledPlugin> {\n\t// Validate package name to prevent command injection\n\tvalidatePackageName(packageName);","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/extensibility/plugins/installer.ts#L1-L35","documentation":"validatePackageName checks the plugin package name against VALID_PACKAGE_NAME before any install/uninstall command runs, throwing 'Invalid package name: <name>' on failure. It is an injection guard for names later passed to spawned bun/npm processes.","triggerScenarios":"installPlugin(name) or uninstallPlugin(name) with a name that doesn't match the allowed pattern — empty string, scoped names if the regex disallows them (@scope/pkg), names with version specifiers, whitespace, or URL-ish inputs.","commonSituations":"Passing 'my-plugin@1.2.0' or a git URL to uninstallPlugin; accidental whitespace from config parsing; programmatically constructed names with null bytes or shell fragments.","solutions":["Pass the bare package name (e.g. 'omp-plugin-git'), no version or URL.","Trim whitespace from names sourced from config/CLI args before calling.","For scoped packages, check whether the validator's pattern accepts the @scope/ prefix; strip or normalize accordingly.","Handle the throw and surface a clear user-facing validation message."],"exampleFix":"// before\nawait installPlugin(\"my-plugin@^1.0.0\")\n// after\nawait installPlugin(\"my-plugin\") // version goes elsewhere, not in the name","handlingStrategy":"validation","validationCode":"const VALID = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/; // mirror the shim's expectation\nif (!VALID.test(name) || /[;&|`$(){}[\\]<>\\\\]/.test(name)) {\n  throw new Error(`Refusing invalid plugin name: ${name}`);\n}\nawait installPlugin(name);","typeGuard":"function isValidPluginName(name: string): boolean {\n  return /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(name) && !/[;&|`$(){}[\\]<>\\\\]/.test(name);\n}","tryCatchPattern":"try {\n  await uninstallPlugin(name);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"Invalid package name:\")) {\n    // report validation failure to the caller; do not mutate-and-retry\n  } else throw err;\n}","preventionTips":["Trim and normalize names from CLI args and config files","Pass bare package names only — never name@version or URLs","Treat untrusted plugin names as hostile input; validate at the boundary"],"tags":["validation","security","plugins","input-validation"],"backgroundTag":"invalid-package-name","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}