fix(foundry): wrap malformed az token json

This commit is contained in:
Vincent Koc
2026-05-14 19:10:08 +08:00
parent 278e3eabf2
commit c96181fdbe
3 changed files with 38 additions and 4 deletions

View File

@@ -53,6 +53,7 @@ Docs: https://docs.openclaw.ai
- Twilio voice-call: report malformed successful API JSON responses with provider-owned errors instead of leaking raw parser failures.
- Voice-call provider APIs: report malformed successful guarded JSON responses with provider-prefixed errors instead of leaking raw parser failures.
- Realtime transcription: report malformed provider websocket JSON frames with owned parser errors instead of leaking raw `SyntaxError` objects.
- Microsoft Foundry: report malformed Azure CLI token JSON with owned auth errors instead of leaking raw parser failures.
- Models config/auth: stop inferring provider env-var markers from broad `^[A-Z_][A-Z0-9_]*$` strings, and resolve config-backed provider `apiKey` values only through structured env SecretRefs (`secrets.providers[id]` / `secrets.defaults`), so unrelated env vars cannot accidentally become provider credentials. Thanks @sallyom.
- Media fetch: skip allocating and buffering the response body for bodyless media responses (HEAD probes and 204-style empty bodies), avoiding wasted heap on streams that carry no payload. Thanks @shakkernerd.
- CLI/onboarding: forward provider-specific auth flags (e.g. `--openai-api-key`) through the onboarding wizard so they reach provider auth methods via `ctx.opts`, letting `--openai-api-key "$OPENAI_API_KEY"` skip the redundant "use existing env var?" prompt in non-interactive harnesses. (#81669) Thanks @sjf.

View File

@@ -85,7 +85,7 @@ export function isAzCliInstalled(): boolean {
export function getLoggedInAccount(): AzAccount | null {
try {
return JSON.parse(execAz(["account", "show", "--output", "json"])) as AzAccount;
return parseAzJson(execAz(["account", "show", "--output", "json"]), "account") as AzAccount;
} catch {
return null;
}
@@ -93,8 +93,9 @@ export function getLoggedInAccount(): AzAccount | null {
export function listSubscriptions(): AzAccount[] {
try {
const subs = JSON.parse(
const subs = parseAzJson(
execAz(["account", "list", "--output", "json", "--all"]),
"subscriptions",
) as AzAccount[];
return subs.filter((sub) => sub.state === "Enabled");
} catch {
@@ -102,6 +103,14 @@ export function listSubscriptions(): AzAccount[] {
}
}
function parseAzJson(raw: string, label: string): unknown {
try {
return JSON.parse(raw) as unknown;
} catch {
throw new Error(`Azure CLI returned malformed ${label} JSON.`);
}
}
type AccessTokenParams = {
subscriptionId?: string;
tenantId?: string;
@@ -125,13 +134,16 @@ function buildAccessTokenArgs(params?: AccessTokenParams): string[] {
}
export function getAccessTokenResult(params?: AccessTokenParams): AzAccessToken {
return JSON.parse(execAz(buildAccessTokenArgs(params))) as AzAccessToken;
return parseAzJson(execAz(buildAccessTokenArgs(params)), "access token") as AzAccessToken;
}
export async function getAccessTokenResultAsync(
params?: AccessTokenParams,
): Promise<AzAccessToken> {
return JSON.parse(await execAzAsync(buildAccessTokenArgs(params))) as AzAccessToken;
return parseAzJson(
await execAzAsync(buildAccessTokenArgs(params)),
"access token",
) as AzAccessToken;
}
export async function azLoginDeviceCode(): Promise<void> {

View File

@@ -235,6 +235,19 @@ function mockAzureCliToken(params: { accessToken: string; expiresInMs: number; d
);
}
function mockAzureCliTokenRaw(stdout: string) {
execFileMock.mockImplementationOnce(
(
_file: unknown,
_args: unknown,
_options: unknown,
callback: (error: Error | null, stdout: string, stderr: string) => void,
) => {
callback(null, stdout, "");
},
);
}
function mockAzureCliLoginFailure(delayMs?: number) {
execFileMock.mockImplementationOnce(
(
@@ -306,6 +319,14 @@ describe("microsoft-foundry plugin", () => {
expect(config.auth?.order?.["microsoft-foundry"]).toEqual(["microsoft-foundry:default"]);
});
it("reports malformed Azure CLI token JSON with an owned error", async () => {
mockAzureCliTokenRaw("{not json");
await expect(getAccessTokenResultAsync()).rejects.toThrow(
"Azure CLI returned malformed access token JSON.",
);
});
it("fails clearly when the selected Azure subscription is not in the enabled list", async () => {
const provider = registerProvider();
execFileSyncMock.mockImplementation((_file: string, args: string[]) => {