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.
This commit is contained in:
clawsweeper[bot]
2026-04-30 01:52:09 +01:00
committed by GitHub
parent 5a631e1ee9
commit 3bd6b54f0b
5 changed files with 93 additions and 13 deletions

View File

@@ -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

View File

@@ -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",

View File

@@ -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 {

View File

@@ -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");

View File

@@ -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[];