* Models: gate custom provider keys by usable secret semantics * Config: project runtime writes onto source snapshot * Models: prevent stale apiKey preservation for marker-managed providers * Runner: strip SecretRef marker headers from resolved models * Secrets: scan active agent models.json path in audit * Config: guard runtime-source projection for unrelated configs * Extensions: fix onboarding type errors in CI * Tests: align setup helper account-enabled expectation * Secrets audit: harden models.json file reads * fix: harden SecretRef custom/provider secret persistence (#42554) (thanks @joshavant)
226 lines
6.0 KiB
TypeScript
226 lines
6.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { AuthProfileStore } from "./auth-profiles.js";
|
|
import { NON_ENV_SECRETREF_MARKER } from "./model-auth-markers.js";
|
|
import {
|
|
hasUsableCustomProviderApiKey,
|
|
requireApiKey,
|
|
resolveAwsSdkEnvVarName,
|
|
resolveModelAuthMode,
|
|
resolveUsableCustomProviderApiKey,
|
|
} from "./model-auth.js";
|
|
|
|
describe("resolveAwsSdkEnvVarName", () => {
|
|
it("prefers bearer token over access keys and profile", () => {
|
|
const env = {
|
|
AWS_BEARER_TOKEN_BEDROCK: "bearer",
|
|
AWS_ACCESS_KEY_ID: "access",
|
|
AWS_SECRET_ACCESS_KEY: "secret", // pragma: allowlist secret
|
|
AWS_PROFILE: "default",
|
|
} as NodeJS.ProcessEnv;
|
|
|
|
expect(resolveAwsSdkEnvVarName(env)).toBe("AWS_BEARER_TOKEN_BEDROCK");
|
|
});
|
|
|
|
it("uses access keys when bearer token is missing", () => {
|
|
const env = {
|
|
AWS_ACCESS_KEY_ID: "access",
|
|
AWS_SECRET_ACCESS_KEY: "secret", // pragma: allowlist secret
|
|
AWS_PROFILE: "default",
|
|
} as NodeJS.ProcessEnv;
|
|
|
|
expect(resolveAwsSdkEnvVarName(env)).toBe("AWS_ACCESS_KEY_ID");
|
|
});
|
|
|
|
it("uses profile when no bearer token or access keys exist", () => {
|
|
const env = {
|
|
AWS_PROFILE: "default",
|
|
} as NodeJS.ProcessEnv;
|
|
|
|
expect(resolveAwsSdkEnvVarName(env)).toBe("AWS_PROFILE");
|
|
});
|
|
|
|
it("returns undefined when no AWS auth env is set", () => {
|
|
expect(resolveAwsSdkEnvVarName({} as NodeJS.ProcessEnv)).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("resolveModelAuthMode", () => {
|
|
it("returns mixed when provider has both token and api key profiles", () => {
|
|
const store: AuthProfileStore = {
|
|
version: 1,
|
|
profiles: {
|
|
"openai:token": {
|
|
type: "token",
|
|
provider: "openai",
|
|
token: "token-value",
|
|
},
|
|
"openai:key": {
|
|
type: "api_key",
|
|
provider: "openai",
|
|
key: "api-key",
|
|
},
|
|
},
|
|
};
|
|
|
|
expect(resolveModelAuthMode("openai", undefined, store)).toBe("mixed");
|
|
});
|
|
|
|
it("returns aws-sdk when provider auth is overridden", () => {
|
|
expect(
|
|
resolveModelAuthMode(
|
|
"amazon-bedrock",
|
|
{
|
|
models: {
|
|
providers: {
|
|
"amazon-bedrock": {
|
|
baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
|
|
models: [],
|
|
auth: "aws-sdk",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{ version: 1, profiles: {} },
|
|
),
|
|
).toBe("aws-sdk");
|
|
});
|
|
|
|
it("returns aws-sdk for bedrock alias without explicit auth override", () => {
|
|
expect(resolveModelAuthMode("bedrock", undefined, { version: 1, profiles: {} })).toBe(
|
|
"aws-sdk",
|
|
);
|
|
});
|
|
|
|
it("returns aws-sdk for aws-bedrock alias without explicit auth override", () => {
|
|
expect(resolveModelAuthMode("aws-bedrock", undefined, { version: 1, profiles: {} })).toBe(
|
|
"aws-sdk",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("requireApiKey", () => {
|
|
it("normalizes line breaks in resolved API keys", () => {
|
|
const key = requireApiKey(
|
|
{
|
|
apiKey: "\n sk-test-abc\r\n",
|
|
source: "env: OPENAI_API_KEY",
|
|
mode: "api-key",
|
|
},
|
|
"openai",
|
|
);
|
|
|
|
expect(key).toBe("sk-test-abc");
|
|
});
|
|
|
|
it("throws when no API key is present", () => {
|
|
expect(() =>
|
|
requireApiKey(
|
|
{
|
|
source: "env: OPENAI_API_KEY",
|
|
mode: "api-key",
|
|
},
|
|
"openai",
|
|
),
|
|
).toThrow('No API key resolved for provider "openai"');
|
|
});
|
|
});
|
|
|
|
describe("resolveUsableCustomProviderApiKey", () => {
|
|
it("returns literal custom provider keys", () => {
|
|
const resolved = resolveUsableCustomProviderApiKey({
|
|
cfg: {
|
|
models: {
|
|
providers: {
|
|
custom: {
|
|
baseUrl: "https://example.com/v1",
|
|
apiKey: "sk-custom-runtime", // pragma: allowlist secret
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
provider: "custom",
|
|
});
|
|
expect(resolved).toEqual({
|
|
apiKey: "sk-custom-runtime",
|
|
source: "models.json",
|
|
});
|
|
});
|
|
|
|
it("does not treat non-env markers as usable credentials", () => {
|
|
const resolved = resolveUsableCustomProviderApiKey({
|
|
cfg: {
|
|
models: {
|
|
providers: {
|
|
custom: {
|
|
baseUrl: "https://example.com/v1",
|
|
apiKey: NON_ENV_SECRETREF_MARKER,
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
provider: "custom",
|
|
});
|
|
expect(resolved).toBeNull();
|
|
});
|
|
|
|
it("resolves known env marker names from process env for custom providers", () => {
|
|
const previous = process.env.OPENAI_API_KEY;
|
|
process.env.OPENAI_API_KEY = "sk-from-env"; // pragma: allowlist secret
|
|
try {
|
|
const resolved = resolveUsableCustomProviderApiKey({
|
|
cfg: {
|
|
models: {
|
|
providers: {
|
|
custom: {
|
|
baseUrl: "https://example.com/v1",
|
|
apiKey: "OPENAI_API_KEY",
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
provider: "custom",
|
|
});
|
|
expect(resolved?.apiKey).toBe("sk-from-env");
|
|
expect(resolved?.source).toContain("OPENAI_API_KEY");
|
|
} finally {
|
|
if (previous === undefined) {
|
|
delete process.env.OPENAI_API_KEY;
|
|
} else {
|
|
process.env.OPENAI_API_KEY = previous;
|
|
}
|
|
}
|
|
});
|
|
|
|
it("does not treat known env marker names as usable when env value is missing", () => {
|
|
const previous = process.env.OPENAI_API_KEY;
|
|
delete process.env.OPENAI_API_KEY;
|
|
try {
|
|
expect(
|
|
hasUsableCustomProviderApiKey(
|
|
{
|
|
models: {
|
|
providers: {
|
|
custom: {
|
|
baseUrl: "https://example.com/v1",
|
|
apiKey: "OPENAI_API_KEY",
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"custom",
|
|
),
|
|
).toBe(false);
|
|
} finally {
|
|
if (previous === undefined) {
|
|
delete process.env.OPENAI_API_KEY;
|
|
} else {
|
|
process.env.OPENAI_API_KEY = previous;
|
|
}
|
|
}
|
|
});
|
|
});
|