From 9061aa8ea8ee2dd35b2efdddb80a1b61e30de2f6 Mon Sep 17 00:00:00 2001 From: juyaohuidt Date: Tue, 21 Jul 2026 11:24:35 +0800 Subject: [PATCH] fix(plugin-sdk): bound Anthropic Vertex ADC credential read (#111120) * fix(plugin-sdk): bound Anthropic Vertex ADC credential read canReadAnthropicVertexAdc read the entire GOOGLE_APPLICATION_CREDENTIALS file via unbounded fs.readFileSync solely to check readability (the contents are discarded). The provider-local copy of this logic in extensions/anthropic-vertex/region.ts was already bounded with tryReadSecretFileSync + a 1 MiB limit in #109260 ("reject oversized credential files in remaining readers"); this standalone plugin-sdk preflight helper is a duplicate that sweep missed. Mirror the region.ts bound: replace the unbounded readFileSync with tryReadSecretFileSync(..., { maxBytes: 1 MiB, rejectHardlinks: false }), wrapped in try/catch (the helper throws FsSafeError on oversize) so an oversized credential file is rejected instead of slurped into memory. Presence-check semantics are unchanged for normal-sized files. * refactor(plugin-sdk): remove orphaned vertex auth helper --------- Co-authored-by: Peter Steinberger --- ...pic-vertex-auth-presence.preflight.test.ts | 54 -------------- .../anthropic-vertex-auth-presence.test.ts | 26 ------- .../anthropic-vertex-auth-presence.ts | 70 ------------------- src/scripts/test-projects.test.ts | 4 +- 4 files changed, 2 insertions(+), 152 deletions(-) delete mode 100644 src/plugin-sdk/anthropic-vertex-auth-presence.preflight.test.ts delete mode 100644 src/plugin-sdk/anthropic-vertex-auth-presence.test.ts delete mode 100644 src/plugin-sdk/anthropic-vertex-auth-presence.ts diff --git a/src/plugin-sdk/anthropic-vertex-auth-presence.preflight.test.ts b/src/plugin-sdk/anthropic-vertex-auth-presence.preflight.test.ts deleted file mode 100644 index 2fc9a07db774..000000000000 --- a/src/plugin-sdk/anthropic-vertex-auth-presence.preflight.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Preflight tests for Anthropic Vertex auth presence helpers. - */ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; - -const { existsSyncMock, readFileSyncMock } = vi.hoisted(() => ({ - existsSyncMock: vi.fn(), - readFileSyncMock: vi.fn(), -})); - -vi.mock("node:fs", async () => { - const actual = await vi.importActual("node:fs"); - existsSyncMock.mockImplementation((pathname) => actual.existsSync(pathname)); - readFileSyncMock.mockImplementation((pathname, options) => - String(pathname) === "/tmp/vertex-adc.json" - ? '{"client_id":"vertex-client"}' - : actual.readFileSync(pathname, options as never), - ); - return { - ...actual, - existsSync: existsSyncMock, - readFileSync: readFileSyncMock, - default: { - ...actual, - existsSync: existsSyncMock, - readFileSync: readFileSyncMock, - }, - }; -}); - -describe("hasAnthropicVertexAvailableAuth ADC preflight", () => { - beforeEach(() => { - vi.resetModules(); - }); - - afterEach(() => { - existsSyncMock.mockClear(); - readFileSyncMock.mockClear(); - }); - - it("reads explicit ADC credentials without an existsSync preflight", async () => { - existsSyncMock.mockClear(); - readFileSyncMock.mockClear(); - const { hasAnthropicVertexAvailableAuth } = await import("./anthropic-vertex-auth-presence.js"); - - expect( - hasAnthropicVertexAvailableAuth({ - GOOGLE_APPLICATION_CREDENTIALS: "/tmp/vertex-adc.json", - } as NodeJS.ProcessEnv), - ).toBe(true); - expect(existsSyncMock).not.toHaveBeenCalled(); - expect(readFileSyncMock).toHaveBeenCalledWith("/tmp/vertex-adc.json", "utf8"); - }); -}); diff --git a/src/plugin-sdk/anthropic-vertex-auth-presence.test.ts b/src/plugin-sdk/anthropic-vertex-auth-presence.test.ts deleted file mode 100644 index b3e61f2764c6..000000000000 --- a/src/plugin-sdk/anthropic-vertex-auth-presence.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Tests Anthropic Vertex auth presence helpers. - */ -import fs from "node:fs/promises"; -import path from "node:path"; -import { describe, expect, it } from "vitest"; -import { hasAnthropicVertexAvailableAuth } from "./anthropic-vertex-auth-presence.js"; -import { createPluginSdkTestHarness } from "./test-helpers.js"; - -const { createTempDir } = createPluginSdkTestHarness(); - -describe("hasAnthropicVertexAvailableAuth", () => { - it("preserves unicode GOOGLE_APPLICATION_CREDENTIALS paths", async () => { - const root = await createTempDir("openclaw-vertex-auth-"); - const unicodeDir = path.join(root, "認証情報"); - await fs.mkdir(unicodeDir, { recursive: true }); - const credentialsPath = path.join(unicodeDir, "application_default_credentials.json"); - await fs.writeFile(credentialsPath, "{}\n", "utf8"); - - expect( - hasAnthropicVertexAvailableAuth({ - GOOGLE_APPLICATION_CREDENTIALS: ` ${credentialsPath} `, - } as NodeJS.ProcessEnv), - ).toBe(true); - }); -}); diff --git a/src/plugin-sdk/anthropic-vertex-auth-presence.ts b/src/plugin-sdk/anthropic-vertex-auth-presence.ts deleted file mode 100644 index 38a23b63c502..000000000000 --- a/src/plugin-sdk/anthropic-vertex-auth-presence.ts +++ /dev/null @@ -1,70 +0,0 @@ -// Anthropic Vertex auth helpers detect local credential presence for provider setup flows. -import { readFileSync } from "node:fs"; -import { homedir, platform } from "node:os"; -import { join } from "node:path"; -import { - normalizeLowercaseStringOrEmpty, - normalizeOptionalString, -} from "../../packages/normalization-core/src/string-coerce.js"; -import { normalizeOptionalSecretInput } from "../utils/normalize-secret-input.js"; - -const GCLOUD_DEFAULT_ADC_PATH = join( - homedir(), - ".config", - "gcloud", - "application_default_credentials.json", -); - -function hasAnthropicVertexMetadataServerAdc(env: NodeJS.ProcessEnv = process.env): boolean { - const explicitMetadataOptIn = normalizeOptionalSecretInput(env.ANTHROPIC_VERTEX_USE_GCP_METADATA); - return ( - explicitMetadataOptIn === "1" || - normalizeLowercaseStringOrEmpty(explicitMetadataOptIn) === "true" - ); -} - -function resolveAnthropicVertexDefaultAdcPath(env: NodeJS.ProcessEnv = process.env): string { - return platform() === "win32" - ? join( - env.APPDATA ?? join(homedir(), "AppData", "Roaming"), - "gcloud", - "application_default_credentials.json", - ) - : GCLOUD_DEFAULT_ADC_PATH; -} - -function resolveAnthropicVertexAdcCredentialsPathCandidate( - env: NodeJS.ProcessEnv = process.env, -): string | undefined { - const explicit = normalizeOptionalString(env.GOOGLE_APPLICATION_CREDENTIALS); - if (explicit) { - return explicit; - } - // Only probe the user's default ADC file for the real process environment; injected - // test/runtime env objects should not accidentally depend on host filesystem state. - if (env !== process.env) { - return undefined; - } - return resolveAnthropicVertexDefaultAdcPath(env); -} - -function canReadAnthropicVertexAdc(env: NodeJS.ProcessEnv = process.env): boolean { - const credentialsPath = resolveAnthropicVertexAdcCredentialsPathCandidate(env); - if (!credentialsPath) { - return false; - } - try { - readFileSync(credentialsPath, "utf8"); - return true; - } catch { - return false; - } -} - -/** - * Return whether Anthropic Vertex can authenticate through GCP metadata or ADC credentials. - * This is a preflight signal only; provider calls still perform their own auth validation. - */ -export function hasAnthropicVertexAvailableAuth(env: NodeJS.ProcessEnv = process.env): boolean { - return hasAnthropicVertexMetadataServerAdc(env) || canReadAnthropicVertexAdc(env); -} diff --git a/src/scripts/test-projects.test.ts b/src/scripts/test-projects.test.ts index c0fabc18c449..b6fd205c374c 100644 --- a/src/scripts/test-projects.test.ts +++ b/src/scripts/test-projects.test.ts @@ -225,11 +225,11 @@ describe("test-projects args", () => { }); it("routes plugin-sdk targets to the plugin-sdk config", () => { - expect(buildVitestRunPlans(["src/plugin-sdk/anthropic-vertex-auth-presence.test.ts"])).toEqual([ + expect(buildVitestRunPlans(["src/plugin-sdk/migration-runtime.test.ts"])).toEqual([ { config: "test/vitest/vitest.plugin-sdk.config.ts", forwardedArgs: [], - includePatterns: ["src/plugin-sdk/anthropic-vertex-auth-presence.test.ts"], + includePatterns: ["src/plugin-sdk/migration-runtime.test.ts"], watchMode: false, }, ]);