fix(ai): Azure Entra mode is explicit opt-in only — no silent az shell-out on missing key (#3460)

Co-authored-by: Garry Tan <garrytan@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Time Attakc
2026-07-27 17:17:25 -07:00
committed by GitHub
co-authored by Garry Tan Claude Fable 5
parent ae8753c872
commit 5dfd2696d1
2 changed files with 11 additions and 4 deletions
+7 -2
View File
@@ -45,9 +45,14 @@ export function __setEntraTokenForTests(token: string | null): void {
_entraToken = token === null ? null : { token, fetchedAt: Date.now() };
}
/** Entra/keyless mode: explicit opt-in, or no api-key present (disableLocalAuth). */
/** Entra/keyless mode: EXPLICIT opt-in only (AZURE_OPENAI_USE_ENTRA=1 /
* config azure_openai_use_entra). A missing api-key must NOT silently shell
* out to `az` — that surprises CI boxes and every non-Azure-CLI environment,
* and it broke the cross-recipe auth iron-rule test. Keyless subscriptions
* (disableLocalAuth) set the flag; missing key without the flag keeps the
* original loud AIConfigError. */
function isEntraMode(env: Record<string, string | undefined>): boolean {
return env.AZURE_OPENAI_USE_ENTRA === '1' || !env.AZURE_OPENAI_API_KEY;
return env.AZURE_OPENAI_USE_ENTRA === '1';
}
/**
+4 -2
View File
@@ -74,7 +74,7 @@ describe('recipe: azure-openai', () => {
expect(auth.headerName).toBe('api-key');
});
test('resolveAuth Entra mode (no key) returns Authorization Bearer from the token cache', async () => {
test('resolveAuth Entra mode (explicit opt-in, no key) returns Authorization Bearer from the token cache', async () => {
const { __setEntraTokenForTests } = await import('../../src/core/ai/recipes/azure-openai.ts');
__setEntraTokenForTests('fake-aad-token');
try {
@@ -82,6 +82,7 @@ describe('recipe: azure-openai', () => {
const auth = r.resolveAuth!({
AZURE_OPENAI_ENDPOINT: FULL_ENV.AZURE_OPENAI_ENDPOINT,
AZURE_OPENAI_DEPLOYMENT: FULL_ENV.AZURE_OPENAI_DEPLOYMENT,
AZURE_OPENAI_USE_ENTRA: '1',
});
expect(auth.headerName).toBe('Authorization');
expect(auth.token).toBe('Bearer fake-aad-token');
@@ -110,7 +111,8 @@ describe('recipe: azure-openai', () => {
const cfg = r.resolveOpenAICompatConfig!({
AZURE_OPENAI_ENDPOINT: FULL_ENV.AZURE_OPENAI_ENDPOINT,
AZURE_OPENAI_DEPLOYMENT: FULL_ENV.AZURE_OPENAI_DEPLOYMENT,
}); // no key → Entra mode
AZURE_OPENAI_USE_ENTRA: '1',
}); // explicit Entra opt-in (no key)
const capturedAuth: (string | null)[] = [];
const realFetch = globalThis.fetch;
globalThis.fetch = ((_input: any, init?: any) => {