diff --git a/CHANGELOG.md b/CHANGELOG.md index fdc2848ce..b2db4f284 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,12 @@ refused in every mode, including `allow_all`, and the browser's WebRTC traffic n filter instead of around it. A computer run without an API server can set `EGRESS_POLICY_REQUIRED=0` to keep the old behaviour. +### Provider and Bot lookups ignore inherited object properties + +Unknown names such as `constructor` and `__proto__` no longer return an inherited +JavaScript object as a provider or Bot entry. Unknown providers return no spec, and +missing Bots raise the existing startup error. Configured providers and Bots are unchanged. + ### A malformed `%` in a stream URL no longer returns a 500 A request to `/api/computers//stream` whose id held a broken percent-escape, such as `%zz`, made the server throw and answer 500. It is now treated as not matching the stream route and goes through normal routing. Valid ids behave as before. diff --git a/shared/model-providers.test.ts b/shared/model-providers.test.ts index 6f42fde3e..ac71102fe 100644 --- a/shared/model-providers.test.ts +++ b/shared/model-providers.test.ts @@ -319,3 +319,18 @@ describe("what a Bot runs from the spec file", () => { ); }); }); + +describe("registry lookups ignore inherited object properties", () => { + test("prototype names are not providers", () => { + for (const name of ["constructor", "__proto__", "toString", "valueOf"]) { + expect(providerSpec(name)).toBeUndefined(); + expect(keyVariableFor(name)).toBeUndefined(); + expect(baseUrlVariableFor(name)).toBeUndefined(); + } + }); + test("prototype names are not Bot entries", () => { + for (const name of ["constructor", "__proto__", "toString", "valueOf"]) { + expect(() => botSettings(name, {})).toThrow(`bots has no ${name} entry`); + } + }); +}); diff --git a/shared/model-providers.ts b/shared/model-providers.ts index ab1ca34dd..ac7a7aa70 100644 --- a/shared/model-providers.ts +++ b/shared/model-providers.ts @@ -166,7 +166,9 @@ export function providerSpec( provider: string | undefined, ): ProviderSpec | undefined { const normalized = provider?.trim().toLowerCase() || "openai"; - return MODEL_PROVIDERS[normalized as ModelProviderId]; + return Object.hasOwn(MODEL_PROVIDERS, normalized) + ? MODEL_PROVIDERS[normalized as ModelProviderId] + : undefined; } /** The environment variable this provider's key arrives in, or nothing for a provider unknown. */ @@ -248,7 +250,9 @@ export function botSettings( env: Readonly> = process.env, pinnedProvider?: string, ): BotSettings { - const entry = BOT_ENTRIES[botId]; + const entry = Object.hasOwn(BOT_ENTRIES, botId) + ? BOT_ENTRIES[botId] + : undefined; if (!entry) { fail( `bots has no ${botId} entry. Add one before this Bot reads the spec file.`,