From 3bd6b54f0b3a3971b12bd63799fe4fd9b879b323 Mon Sep 17 00:00:00 2001 From: "clawsweeper[bot]" <274271284+clawsweeper[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 01:52:09 +0100 Subject: [PATCH] fix: compatibility gaps in the new Google Vertex ADC manifest evidence Tighten Google Vertex ADC manifest evidence to canonical project env vars and canonical ADC fallback paths only. Local proof: - OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test src/agents/model-auth.profiles.test.ts src/plugins/manifest-registry.test.ts src/secrets/provider-env-vars.dynamic.test.ts - pnpm exec oxfmt --check --threads=1 docs/plugins/manifest.md extensions/google/openclaw.plugin.json src/agents/model-auth-env.ts src/agents/model-auth.profiles.test.ts src/plugins/manifest.ts - git diff --check origin/main...HEAD CI note: checks-node-core-support-boundary was red on an unrelated tooling assertion in test/scripts/test-projects.test.ts for packages/sdk/src/index.test.ts routing; that file and scripts/test-projects.mjs are unchanged from origin/main. --- docs/plugins/manifest.md | 18 +++---- extensions/google/openclaw.plugin.json | 5 +- src/agents/model-auth-env.ts | 6 ++- src/agents/model-auth.profiles.test.ts | 75 +++++++++++++++++++++++++- src/plugins/manifest.ts | 2 +- 5 files changed, 93 insertions(+), 13 deletions(-) diff --git a/docs/plugins/manifest.md b/docs/plugins/manifest.md index eff5ed14b91..b66887e9e29 100644 --- a/docs/plugins/manifest.md +++ b/docs/plugins/manifest.md @@ -431,15 +431,15 @@ provider API probes. Supported evidence entries: -| Field | Required | Type | What it means | -| ------------------ | -------- | ---------- | --------------------------------------------------------------------------------------------- | -| `type` | Yes | `string` | Currently `local-file-with-env`. | -| `fileEnvVar` | No | `string` | Env var containing an explicit credential file path. | -| `fallbackPaths` | No | `string[]` | Local credential file paths checked when `fileEnvVar` is absent or empty. Supports `${HOME}`. | -| `requiresAnyEnv` | No | `string[]` | At least one listed env var must be non-empty before the evidence is valid. | -| `requiresAllEnv` | No | `string[]` | Every listed env var must be non-empty before the evidence is valid. | -| `credentialMarker` | Yes | `string` | Non-secret marker returned when the evidence is present. | -| `source` | No | `string` | User-facing source label for auth/status output. | +| Field | Required | Type | What it means | +| ------------------ | -------- | ---------- | -------------------------------------------------------------------------------------------------------------- | +| `type` | Yes | `string` | Currently `local-file-with-env`. | +| `fileEnvVar` | No | `string` | Env var containing an explicit credential file path. | +| `fallbackPaths` | No | `string[]` | Local credential file paths checked when `fileEnvVar` is absent or empty. Supports `${HOME}` and `${APPDATA}`. | +| `requiresAnyEnv` | No | `string[]` | At least one listed env var must be non-empty before the evidence is valid. | +| `requiresAllEnv` | No | `string[]` | Every listed env var must be non-empty before the evidence is valid. | +| `credentialMarker` | Yes | `string` | Non-secret marker returned when the evidence is present. | +| `source` | No | `string` | User-facing source label for auth/status output. | ### setup fields diff --git a/extensions/google/openclaw.plugin.json b/extensions/google/openclaw.plugin.json index 11a5b53dbc7..c96916f19b8 100644 --- a/extensions/google/openclaw.plugin.json +++ b/extensions/google/openclaw.plugin.json @@ -81,7 +81,10 @@ { "type": "local-file-with-env", "fileEnvVar": "GOOGLE_APPLICATION_CREDENTIALS", - "fallbackPaths": ["${HOME}/.config/gcloud/application_default_credentials.json"], + "fallbackPaths": [ + "${HOME}/.config/gcloud/application_default_credentials.json", + "${APPDATA}/gcloud/application_default_credentials.json" + ], "requiresAnyEnv": ["GOOGLE_CLOUD_PROJECT", "GCLOUD_PROJECT"], "requiresAllEnv": ["GOOGLE_CLOUD_LOCATION"], "credentialMarker": "gcp-vertex-credentials", diff --git a/src/agents/model-auth-env.ts b/src/agents/model-auth-env.ts index 8f20f4e89e7..19a5baf2285 100644 --- a/src/agents/model-auth-env.ts +++ b/src/agents/model-auth-env.ts @@ -32,7 +32,11 @@ function expandAuthEvidencePath(rawPath: string, env: NodeJS.ProcessEnv): string return undefined; } const homeDir = normalizeOptionalPathInput(env.HOME) ?? os.homedir(); - return trimmed.replaceAll("${HOME}", homeDir); + const appDataDir = normalizeOptionalPathInput(env.APPDATA); + if (trimmed.includes("${APPDATA}") && !appDataDir) { + return undefined; + } + return trimmed.replaceAll("${HOME}", homeDir).replaceAll("${APPDATA}", appDataDir ?? ""); } function normalizeOptionalPathInput(value: unknown): string | undefined { diff --git a/src/agents/model-auth.profiles.test.ts b/src/agents/model-auth.profiles.test.ts index c9d9c527bae..1e88823acea 100644 --- a/src/agents/model-auth.profiles.test.ts +++ b/src/agents/model-auth.profiles.test.ts @@ -129,7 +129,10 @@ vi.mock("./model-auth-env-vars.js", () => { { type: "local-file-with-env", fileEnvVar: "GOOGLE_APPLICATION_CREDENTIALS", - fallbackPaths: ["${HOME}/.config/gcloud/application_default_credentials.json"], + fallbackPaths: [ + "${HOME}/.config/gcloud/application_default_credentials.json", + "${APPDATA}/gcloud/application_default_credentials.json", + ], requiresAnyEnv: ["GOOGLE_CLOUD_PROJECT", "GCLOUD_PROJECT"], requiresAllEnv: ["GOOGLE_CLOUD_LOCATION"], credentialMarker: "gcp-vertex-credentials", @@ -1013,6 +1016,76 @@ describe("getApiKeyForModel", () => { } }); + it("resolveEnvApiKey('google-vertex') rejects GOOGLE_CLOUD_PROJECT_ID-only ADC auth evidence", async () => { + const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-google-adc-project-id-")); + const credentialsPath = path.join(tempDir, "adc.json"); + await fs.writeFile(credentialsPath, "{}", "utf8"); + + try { + const resolved = resolveEnvApiKey("google-vertex", { + GOOGLE_APPLICATION_CREDENTIALS: credentialsPath, + GOOGLE_CLOUD_LOCATION: "us-central1", + GOOGLE_CLOUD_PROJECT_ID: "vertex-project", + } as NodeJS.ProcessEnv); + + expect(resolved).toBeNull(); + } finally { + await fs.rm(tempDir, { recursive: true, force: true }); + } + }); + + it("resolveEnvApiKey('google-vertex') accepts Windows APPDATA ADC fallback evidence", async () => { + const appDataDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-google-adc-appdata-")); + const fallbackDir = path.join(appDataDir, "gcloud"); + await fs.mkdir(fallbackDir, { recursive: true }); + await fs.writeFile( + path.join(fallbackDir, "application_default_credentials.json"), + "{}", + "utf8", + ); + + try { + const resolved = resolveEnvApiKey("google-vertex", { + APPDATA: appDataDir, + GOOGLE_CLOUD_LOCATION: "us-central1", + GOOGLE_CLOUD_PROJECT: "vertex-project", + } as NodeJS.ProcessEnv); + + expect(resolved?.apiKey).toBe("gcp-vertex-credentials"); + expect(resolved?.source).toBe("gcloud adc"); + } finally { + await fs.rm(appDataDir, { recursive: true, force: true }); + } + }); + + it("resolveEnvApiKey('google-vertex') does not synthesize APPDATA from USERPROFILE", async () => { + const homeDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-google-adc-home-")); + const userProfileDir = await fs.mkdtemp( + path.join(os.tmpdir(), "openclaw-google-adc-userprofile-"), + ); + const fallbackDir = path.join(userProfileDir, "AppData", "Roaming", "gcloud"); + await fs.mkdir(fallbackDir, { recursive: true }); + await fs.writeFile( + path.join(fallbackDir, "application_default_credentials.json"), + "{}", + "utf8", + ); + + try { + const resolved = resolveEnvApiKey("google-vertex", { + HOME: homeDir, + USERPROFILE: userProfileDir, + GOOGLE_CLOUD_LOCATION: "us-central1", + GOOGLE_CLOUD_PROJECT: "vertex-project", + } as NodeJS.ProcessEnv); + + expect(resolved).toBeNull(); + } finally { + await fs.rm(homeDir, { recursive: true, force: true }); + await fs.rm(userProfileDir, { recursive: true, force: true }); + } + }); + it("resolveEnvApiKey('google-vertex') keeps ADC fallback when manifest env candidates are empty", async () => { const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-google-adc-candidates-")); const credentialsPath = path.join(tempDir, "adc.json"); diff --git a/src/plugins/manifest.ts b/src/plugins/manifest.ts index f4822daac79..be331f8dc24 100644 --- a/src/plugins/manifest.ts +++ b/src/plugins/manifest.ts @@ -201,7 +201,7 @@ export type PluginManifestSetupProviderAuthEvidence = { type: "local-file-with-env"; /** Optional env var containing an explicit credential file path. */ fileEnvVar?: string; - /** Optional fallback credential file paths. Supports `${HOME}` only. */ + /** Optional fallback credential file paths. Supports `${HOME}` and `${APPDATA}`. */ fallbackPaths?: string[]; /** At least one of these env vars must be non-empty when provided. */ requiresAnyEnv?: string[];