diff --git a/backend/src/config.ts b/backend/src/config.ts index 616d8349..a52df4b3 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -41,6 +41,33 @@ function absoluteRegistryPath(value: string, label: string): string { return pathValue; } +function refSafeGitBranch(value: string): string { + const branch = requiredRegistryValue(value, "branch"); + if ( + branch === "@" + || branch.startsWith("-") + || branch.startsWith("/") + || branch.endsWith("/") + || branch.endsWith(".") + || branch.includes("..") + || branch.includes("@{") + || branch.includes("//") + || branch.split("/").some((component) => component.startsWith(".") || component.endsWith(".lock")) + || /[\p{Cc} ~^:?*\[\\]/u.test(branch) + ) { + throw new Error("workspace registry branch configuration is invalid"); + } + return branch; +} + +function safeInstallationId(value: string): string { + const installationId = requiredRegistryValue(value, "installation ID"); + if (/\p{Cc}/u.test(installationId)) { + throw new Error("workspace registry installation ID configuration is invalid"); + } + return installationId; +} + function positiveImportLimit(value: string | undefined, fallback: number): number { const limit = Number(value ?? fallback); if (!Number.isSafeInteger(limit) || limit <= 0) { @@ -119,10 +146,9 @@ export function loadConfig(env: Record): AppConfig { env.THT_WORKSPACE_REGISTRY_ROOT ?? "/data/workspace-registry", "root", ); - const registryBranch = requiredRegistryValue(env.THT_WORKSPACE_GIT_BRANCH ?? "main", "branch"); - const installationId = requiredRegistryValue( + const registryBranch = refSafeGitBranch(env.THT_WORKSPACE_GIT_BRANCH ?? "main"); + const installationId = safeInstallationId( env.THT_WORKSPACE_INSTALLATION_ID ?? "local", - "installation ID", ); const remoteUrl = env.THT_WORKSPACE_GIT_REMOTE === undefined ? undefined diff --git a/backend/test/workspaces-config.test.ts b/backend/test/workspaces-config.test.ts index c624b6e6..10c14853 100644 --- a/backend/test/workspaces-config.test.ts +++ b/backend/test/workspaces-config.test.ts @@ -46,3 +46,19 @@ test("rejects unsafe registry branch, installation ID, and secret roots", () => expect(() => loadConfig({ THT_WORKSPACE_SECRET_ROOTS: "/run/secrets,relative" })) .toThrow(/secret/i); }); + +test("rejects ref-unsafe Git branches", () => { + for (const branch of ["topic..bad", "--upload-pack=/tmp/x", "release/.hidden", "release.lock"]) { + expect(() => loadConfig({ THT_WORKSPACE_GIT_BRANCH: branch })).toThrow(/branch/i); + } +}); + +test("rejects control characters in installation IDs", () => { + for (const codePoint of [...Array(0x20).keys(), ...Array(0x21).keys()].map((code, index) => ( + index < 0x20 ? code : code + 0x7f + ))) { + expect(() => loadConfig({ + THT_WORKSPACE_INSTALLATION_ID: `server-psd-1${String.fromCodePoint(codePoint)}`, + })).toThrow(/installation/i); + } +});