can1357/oh-my-pi · error · Error
Invalid package name: ${name}
Error message
Invalid package name: ${name} What it means
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.
Source
Thrown at packages/coding-agent/src/extensibility/plugins/installer.ts:17
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { getAgentDir, getProjectDir, isEnoent } from "@oh-my-pi/pi-utils";
import { extractPackageName } from "./parser";
import type { InstalledPlugin } from "./types";
const PLUGINS_DIR = path.join(getAgentDir(), "plugins");
// Valid npm package name pattern (scoped and unscoped)
const VALID_PACKAGE_NAME = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*(@[a-z0-9-._^~>=<]+)?$/i;
/**
* Validate package name to prevent command injection
*/
function validatePackageName(name: string): void {
if (!VALID_PACKAGE_NAME.test(name)) {
throw new Error(`Invalid package name: ${name}`);
}
// Extra safety: no shell metacharacters
if (/[;&|`$(){}[\]<>\\]/.test(name)) {
throw new Error(`Invalid characters in package name: ${name}`);
}
}
/**
* Ensure the plugins directory exists
*/
async function ensurePluginsDir(): Promise<void> {
await fs.mkdir(PLUGINS_DIR, { recursive: true });
await fs.mkdir(path.join(PLUGINS_DIR, "node_modules"), { recursive: true });
}
export async function installPlugin(packageName: string): Promise<InstalledPlugin> {
// Validate package name to prevent command injection
validatePackageName(packageName);View on GitHub (pinned to 9690622007)
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.
Example fix
// before
await installPlugin("my-plugin@^1.0.0")
// after
await installPlugin("my-plugin") // version goes elsewhere, not in the name Defensive patterns
Strategy: validation
Validate before calling
const VALID = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/; // mirror the shim's expectation
if (!VALID.test(name) || /[;&|`$(){}[\]<>\\]/.test(name)) {
throw new Error(`Refusing invalid plugin name: ${name}`);
}
await installPlugin(name); Type guard
function isValidPluginName(name: string): boolean {
return /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(name) && !/[;&|`$(){}[\]<>\\]/.test(name);
} Try / catch
try {
await uninstallPlugin(name);
} catch (err) {
if (err instanceof Error && err.message.startsWith("Invalid package name:")) {
// report validation failure to the caller; do not mutate-and-retry
} else throw err;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Invalid characters in package name: ${name}
- Invalid path: ${localPath} resolves outside working director
- Invalid package name: ${name}
- Provider delete URL must not embed an account credential
- Destination paths cannot contain parent traversal or NUL byt
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/3cd7aa43e82e2bba.
Report an issue: GitHub.