diff --git a/cli/src/manifest.ts b/cli/src/manifest.ts index 05b70852..46bb7736 100644 --- a/cli/src/manifest.ts +++ b/cli/src/manifest.ts @@ -56,7 +56,7 @@ export interface Manifest { // ── Constants ────────────────────────────────────────────────────────────────── const REPO = "OpenRouterTeam/spawn"; -const RAW_BASE = `https://raw.githubusercontent.com/${REPO}/main`; +const RAW_BASE = `https://raw.githubusercontent.com/${REPO}/main` as const; // Dynamic getters so tests can override XDG_CACHE_HOME at runtime function getCacheDir(): string { return join(process.env.XDG_CACHE_HOME || join(homedir(), ".cache"), "spawn"); diff --git a/cli/src/update-check.ts b/cli/src/update-check.ts index 504697c0..ac001af6 100644 --- a/cli/src/update-check.ts +++ b/cli/src/update-check.ts @@ -16,6 +16,12 @@ export const executor = { const FETCH_TIMEOUT = 5000; // 5 seconds +// Validate RAW_BASE matches expected GitHub raw content URL pattern (defense-in-depth, CWE-78) +const GITHUB_RAW_URL_PATTERN = /^https:\/\/raw\.githubusercontent\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/; +if (!GITHUB_RAW_URL_PATTERN.test(RAW_BASE)) { + throw new Error(`RAW_BASE URL does not match expected GitHub raw URL pattern: ${RAW_BASE}`); +} + // Use ASCII-safe symbols when unicode is disabled (SSH, dumb terminals) const isAscii = process.env.TERM === "linux"; const CHECK_MARK = isAscii ? "*" : "\u2713";