Skip to content

fix: stop opencode-go quota lookup defaulting to Z.AI endpoint (#7022) - #7187

Merged
diegosouzapw merged 2 commits into
release/v3.8.49from
fix/7022-opencode-go-quota-url-zai
Jul 15, 2026
Merged

diegosouzapw merged 2 commits into
release/v3.8.49from
fix/7022-opencode-go-quota-url-zai

Conversation

@diegosouzapw

Copy link
Copy Markdown
Owner

Closes #7022

Root cause

open-sse/services/opencodeOllamaUsage.ts 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 an opencode-go connection 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 a Bearer Authorization 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

  • Removed the hardcoded Z.AI default. OPENCODE_GO_QUOTA_URL is now empty unless the operator explicitly sets OMNIROUTE_OPENCODE_GO_QUOTA_URL.
  • When unset, getOpenCodeGoUsage() skips the network call entirely and returns a descriptive message — no outbound request to any undisclosed third-party host.
  • Updated .env.example and both EN/zh-CN copies of docs/reference/ENVIRONMENT.md to drop the stale Z.AI default and fix the stale open-sse/services/usage.ts source-file reference (now open-sse/services/opencodeOllamaUsage.ts).

Regression test (TDD, Hard Rule #18)

tests/unit/opencode-go-quota-no-zai.test.ts — stubs globalThis.fetch and asserts getOpenCodeGoUsage() makes zero outbound calls (in particular, none to api.z.ai) when OMNIROUTE_OPENCODE_GO_QUOTA_URL is unset.

  • Confirmed RED against the pre-fix code (assertion failed: call went to api.z.ai).
  • Confirmed GREEN after the fix.

Gates run

  • node scripts/check/check-file-size.mjs — OK
  • node 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 — OK
  • npm run typecheck:core — clean
  • npx eslint --suppressions-location config/quality/eslint-suppressions.json open-sse/services/opencodeOllamaUsage.ts tests/unit/opencode-go-quota-no-zai.test.ts — clean
  • npm run test:unit (full suite) — exit 0
  • No pre-existing tests reference this module (opencodeOllamaUsage.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

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).

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +4 to +31
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;
}
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.
@diegosouzapw
diegosouzapw merged commit 3df06e5 into release/v3.8.49 Jul 15, 2026
14 checks passed
@diegosouzapw
diegosouzapw deleted the fix/7022-opencode-go-quota-url-zai branch July 19, 2026 21:01
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
…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.
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant