diff --git a/packages/cli/src/pty-manager.ts b/packages/cli/src/pty-manager.ts index 1f49aab..6c60931 100644 --- a/packages/cli/src/pty-manager.ts +++ b/packages/cli/src/pty-manager.ts @@ -20,12 +20,11 @@ export function getDefaultShell(): string { * Inherits the current process env and sets TERM for proper terminal behavior. */ function buildEnv(): Record { - const env: Record = {}; - for (const [key, value] of Object.entries(process.env)) { - if (value !== undefined) { - env[key] = value; - } - } + const env = Object.fromEntries( + Object.entries(process.env).filter( + (entry): entry is [string, string] => entry[1] !== undefined + ) + ); // Set TERM for unix systems to enable color and cursor support if (process.platform !== "win32") { env["TERM"] = env["TERM"] || "xterm-256color"; diff --git a/packages/hub/src/windows-firewall.ts b/packages/hub/src/windows-firewall.ts index fe77809..654f4bb 100644 --- a/packages/hub/src/windows-firewall.ts +++ b/packages/hub/src/windows-firewall.ts @@ -32,7 +32,9 @@ export class WindowsFirewall { async addRule(label: string, port: number): Promise<{ success: boolean; error?: string }> { if (!this.isWindows) return { success: true }; - const ruleName = `${RULE_PREFIX}-${label}`; + // Sanitize label to prevent log/command injection + const safeLabel = label.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 64); + const ruleName = `${RULE_PREFIX}-${safeLabel}`; // Skip if already tracked if (this.rules.has(ruleName)) return { success: true }; @@ -70,7 +72,8 @@ export class WindowsFirewall { async removeRule(label: string): Promise { if (!this.isWindows) return; - const ruleName = `${RULE_PREFIX}-${label}`; + const safeLabel = label.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 64); + const ruleName = `${RULE_PREFIX}-${safeLabel}`; await this.runNetsh([ "advfirewall", "firewall", "delete", "rule", `name=${ruleName}`, diff --git a/scripts/sync-versions.mjs b/scripts/sync-versions.mjs index 0fe67d2..1a03e9e 100644 --- a/scripts/sync-versions.mjs +++ b/scripts/sync-versions.mjs @@ -16,6 +16,13 @@ if (!version) { process.exit(1); } +// Validate semver format to prevent injection via crafted version strings +const SEMVER_RE = /^\d+\.\d+\.\d+(-[\w.]+)?(\+[\w.]+)?$/; +if (!SEMVER_RE.test(version)) { + console.error(`Invalid version format: "${version}". Expected semver (e.g. 1.2.3).`); + process.exit(1); +} + const packagesDir = join(import.meta.dirname, "..", "packages"); const packages = readdirSync(packagesDir, { withFileTypes: true }) .filter((d) => d.isDirectory())