Repository navigation
fix: stop opencode-go quota lookup defaulting to Z.AI endpoint (#7022) - #7187
Conversation
getOpenCodeGoUsage() defaulted OPENCODE_GO_QUOTA_URL to https://api.z.ai/api/monitor/usage/quota/limit, a Zhipu AI (Z.AI/GLM) endpoint unrelated to opencode.ai. Whenever a connection had no dashboard-scraping config (workspaceId/authCookie), the user's real OpenCode Go API key was sent as a Bearer token to that third-party host by default, with no operator opt-in. Remove the hardcoded default: the quota-by-API-key fetch now only runs when the operator explicitly sets OMNIROUTE_OPENCODE_GO_QUOTA_URL. With it unset (the default), getOpenCodeGoUsage() returns a descriptive message and makes zero outbound calls, since OpenCode Go has no public quota API. Also updates .env.example and both EN/zh-CN copies of docs/reference/ENVIRONMENT.md to drop the stale Z.AI default value and fix the stale open-sse/services/usage.ts source-file reference. Regression test: tests/unit/opencode-go-quota-no-zai.test.ts (RED on current code, GREEN after the fix).
There was a problem hiding this comment.
Code Review
This pull request addresses a security issue where the OpenCode Go quota lookup defaulted to an unrelated third-party Z.AI endpoint, potentially leaking API keys. It removes this default, making the lookup opt-in, and updates the documentation and environment variables accordingly. The reviewer identified a critical issue in the new unit test: because static imports are hoisted, modifying process.env inside the test body does not affect top-level constants evaluated at module load time. The reviewer suggested using dynamic imports and improving the mock fetch implementation to handle Request objects robustly.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| import { getOpenCodeGoUsage } from "../../open-sse/services/opencodeOllamaUsage.ts"; | ||
|
|
||
| test("getOpenCodeGoUsage does not send the user's OpenCode Go API key to api.z.ai by default", async () => { | ||
| const originalFetch = globalThis.fetch; | ||
| const originalEnv = process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL; | ||
| delete process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL; | ||
|
|
||
| let calledHost: string | null = null; | ||
| globalThis.fetch = (async (input: RequestInfo | URL) => { | ||
| const url = typeof input === "string" ? input : input.toString(); | ||
| calledHost = new URL(url).host; | ||
| throw new Error(`unexpected outbound fetch to ${url}`); | ||
| }) as typeof fetch; | ||
|
|
||
| try { | ||
| const result = await getOpenCodeGoUsage("sk-fake-opencode-go-key", undefined); | ||
| assert.notStrictEqual(calledHost, "api.z.ai"); | ||
| assert.strictEqual(calledHost, null); | ||
| assert.ok( | ||
| typeof result.message === "string" && result.message.length > 0, | ||
| "expected a descriptive message when no quota URL is configured" | ||
| ); | ||
| } finally { | ||
| globalThis.fetch = originalFetch; | ||
| if (originalEnv === undefined) delete process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL; | ||
| else process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL = originalEnv; | ||
| } | ||
| }); |
There was a problem hiding this comment.
In ES modules, static import statements are hoisted and executed before any other code in the file. Since open-sse/services/opencodeOllamaUsage.ts evaluates OPENCODE_GO_QUOTA_URL as a top-level constant at module load time, deleting process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL inside the test body has no effect if the module was already evaluated with the environment variable set. This can lead to flaky tests if the test suite is run in an environment where OMNIROUTE_OPENCODE_GO_QUOTA_URL is pre-configured.
Additionally, the mock fetch implementation assumes input is always a string or can be converted to a string via .toString(). If fetch is called with a Request object, input.toString() will return "[object Request]", causing new URL() to throw a TypeError instead of the expected mock error.
Using a dynamic import() inside the test ensures the environment variable is deleted before the module is evaluated, and updating the URL extraction makes the mock fetch robust to Request objects.
test("getOpenCodeGoUsage does not send the user's OpenCode Go API key to api.z.ai by default", async () => {
const originalFetch = globalThis.fetch;
const originalEnv = process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL;
try {
delete process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL;
// Dynamically import to ensure process.env is modified before the module evaluates its top-level constants
const { getOpenCodeGoUsage } = await import("../../open-sse/services/opencodeOllamaUsage.ts");
let calledHost: string | null = null;
globalThis.fetch = (async (input: RequestInfo | URL) => {
const url = typeof input === "string" ? input : ("url" in input ? input.url : input.toString());
calledHost = new URL(url).host;
throw new Error("unexpected outbound fetch to " + url);
}) as typeof fetch;
const result = await getOpenCodeGoUsage("sk-fake-opencode-go-key", undefined);
assert.notStrictEqual(calledHost, "api.z.ai");
assert.strictEqual(calledHost, null);
assert.ok(
typeof result.message === "string" && result.message.length > 0,
"expected a descriptive message when no quota URL is configured"
);
} finally {
globalThis.fetch = originalFetch;
if (originalEnv === undefined) {
delete process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL;
} else {
process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL = originalEnv;
}
}
});) The prior commit removed the hardcoded api.z.ai default from OPENCODE_GO_QUOTA_URL, making the quota-by-API-key path opt-in via OMNIROUTE_OPENCODE_GO_QUOTA_URL. Six pre-existing tests in opencode-go-usage.test.ts still asserted the old default-fetch behavior and the old Z.AI-specific error wording, so they broke. Set OMNIROUTE_OPENCODE_GO_QUOTA_URL before the module import (the value is read once at load time) to simulate an operator who opted in, and update the two error-message assertions to the new generic wording ("the configured OMNIROUTE_OPENCODE_GO_QUOTA_URL endpoint" instead of "the Z.AI quota API"). Each test still verifies exactly the same behavior it did before (invalid key, fetch failure, 200 with auth error in body, invalid JSON, quota shape) — only the opt-in setup and message wording changed.
…souzapw#7022) (diegosouzapw#7187) * fix: stop opencode-go quota lookup defaulting to Z.AI endpoint (diegosouzapw#7022) getOpenCodeGoUsage() defaulted OPENCODE_GO_QUOTA_URL to https://api.z.ai/api/monitor/usage/quota/limit, a Zhipu AI (Z.AI/GLM) endpoint unrelated to opencode.ai. Whenever a connection had no dashboard-scraping config (workspaceId/authCookie), the user's real OpenCode Go API key was sent as a Bearer token to that third-party host by default, with no operator opt-in. Remove the hardcoded default: the quota-by-API-key fetch now only runs when the operator explicitly sets OMNIROUTE_OPENCODE_GO_QUOTA_URL. With it unset (the default), getOpenCodeGoUsage() returns a descriptive message and makes zero outbound calls, since OpenCode Go has no public quota API. Also updates .env.example and both EN/zh-CN copies of docs/reference/ENVIRONMENT.md to drop the stale Z.AI default value and fix the stale open-sse/services/usage.ts source-file reference. Regression test: tests/unit/opencode-go-quota-no-zai.test.ts (RED on current code, GREEN after the fix). * fix: align opencode-go-usage tests with opt-in quota URL contract (diegosouzapw#7022) The prior commit removed the hardcoded api.z.ai default from OPENCODE_GO_QUOTA_URL, making the quota-by-API-key path opt-in via OMNIROUTE_OPENCODE_GO_QUOTA_URL. Six pre-existing tests in opencode-go-usage.test.ts still asserted the old default-fetch behavior and the old Z.AI-specific error wording, so they broke. Set OMNIROUTE_OPENCODE_GO_QUOTA_URL before the module import (the value is read once at load time) to simulate an operator who opted in, and update the two error-message assertions to the new generic wording ("the configured OMNIROUTE_OPENCODE_GO_QUOTA_URL endpoint" instead of "the Z.AI quota API"). Each test still verifies exactly the same behavior it did before (invalid key, fetch failure, 200 with auth error in body, invalid JSON, quota shape) — only the opt-in setup and message wording changed.
…souzapw#7022) (diegosouzapw#7187) * fix: stop opencode-go quota lookup defaulting to Z.AI endpoint (diegosouzapw#7022) getOpenCodeGoUsage() defaulted OPENCODE_GO_QUOTA_URL to https://api.z.ai/api/monitor/usage/quota/limit, a Zhipu AI (Z.AI/GLM) endpoint unrelated to opencode.ai. Whenever a connection had no dashboard-scraping config (workspaceId/authCookie), the user's real OpenCode Go API key was sent as a Bearer token to that third-party host by default, with no operator opt-in. Remove the hardcoded default: the quota-by-API-key fetch now only runs when the operator explicitly sets OMNIROUTE_OPENCODE_GO_QUOTA_URL. With it unset (the default), getOpenCodeGoUsage() returns a descriptive message and makes zero outbound calls, since OpenCode Go has no public quota API. Also updates .env.example and both EN/zh-CN copies of docs/reference/ENVIRONMENT.md to drop the stale Z.AI default value and fix the stale open-sse/services/usage.ts source-file reference. Regression test: tests/unit/opencode-go-quota-no-zai.test.ts (RED on current code, GREEN after the fix). * fix: align opencode-go-usage tests with opt-in quota URL contract (diegosouzapw#7022) The prior commit removed the hardcoded api.z.ai default from OPENCODE_GO_QUOTA_URL, making the quota-by-API-key path opt-in via OMNIROUTE_OPENCODE_GO_QUOTA_URL. Six pre-existing tests in opencode-go-usage.test.ts still asserted the old default-fetch behavior and the old Z.AI-specific error wording, so they broke. Set OMNIROUTE_OPENCODE_GO_QUOTA_URL before the module import (the value is read once at load time) to simulate an operator who opted in, and update the two error-message assertions to the new generic wording ("the configured OMNIROUTE_OPENCODE_GO_QUOTA_URL endpoint" instead of "the Z.AI quota API"). Each test still verifies exactly the same behavior it did before (invalid key, fetch failure, 200 with auth error in body, invalid JSON, quota shape) — only the opt-in setup and message wording changed.
Closes #7022
Root cause
open-sse/services/opencodeOllamaUsage.tsdefaultedOPENCODE_GO_QUOTA_URLtohttps://api.z.ai/api/monitor/usage/quota/limit— a Zhipu AI (Z.AI/GLM) endpoint unrelated to opencode.ai. Whenever anopencode-goconnection had no dashboard-scraping config (workspaceId/authCookie) and the Usage/Limits page was opened,getOpenCodeGoUsage()silently sent the user's real OpenCode Go API key as aBearerAuthorization header to that third-party host — with no operator opt-in.Root cause history: PR #2861 copy-pasted the GLM/Z.AI quota-fetcher pattern as a template; a later fix marked it a "known broken placeholder" via comment, but that comment was lost in the #4642 file-split refactor, leaving a bare Z.AI literal.
Fix
OPENCODE_GO_QUOTA_URLis now empty unless the operator explicitly setsOMNIROUTE_OPENCODE_GO_QUOTA_URL.getOpenCodeGoUsage()skips the network call entirely and returns a descriptive message — no outbound request to any undisclosed third-party host..env.exampleand both EN/zh-CN copies ofdocs/reference/ENVIRONMENT.mdto drop the stale Z.AI default and fix the staleopen-sse/services/usage.tssource-file reference (nowopen-sse/services/opencodeOllamaUsage.ts).Regression test (TDD, Hard Rule #18)
tests/unit/opencode-go-quota-no-zai.test.ts— stubsglobalThis.fetchand assertsgetOpenCodeGoUsage()makes zero outbound calls (in particular, none toapi.z.ai) whenOMNIROUTE_OPENCODE_GO_QUOTA_URLis unset.api.z.ai).Gates run
node scripts/check/check-file-size.mjs— OKnode scripts/check/check-complexity.mjs— OK (baseline 2056, unchanged)node scripts/check/check-cognitive-complexity.mjs— OK (baseline 890, unchanged)node scripts/check/check-changelog-integrity.mjs— OKnpm run typecheck:core— cleannpx eslint --suppressions-location config/quality/eslint-suppressions.json open-sse/services/opencodeOllamaUsage.ts tests/unit/opencode-go-quota-no-zai.test.ts— cleannpm run test:unit(full suite) — exit 0opencodeOllamaUsage.ts/getOpenCodeGoUsage), so the new regression test is the only guard here.Plan-file:
_tasks/pipeline/bugs/2-implementing/7022-opencode-go-quota-url-uses-zai-instead-of-opencode.plan.md