From 463a876284c3f326ae4ea6997142c9a67ca84473 Mon Sep 17 00:00:00 2001 From: "Alex Yang (DevDiv)" Date: Mon, 20 Jul 2026 14:32:51 -0700 Subject: [PATCH] Harden persisted authentication recovery Treat malformed authentication records as missing so browser sign-in can recover, preserve real file-read failures, and pin the Azure Identity persistence dependencies exactly. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- extensions/connector-namespaces/auth.mjs | 15 ++++++++++--- extensions/connector-namespaces/auth.test.mjs | 21 +++++++++++++++++++ extensions/connector-namespaces/package.json | 4 ++-- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/extensions/connector-namespaces/auth.mjs b/extensions/connector-namespaces/auth.mjs index 06ea6494..30418d64 100644 --- a/extensions/connector-namespaces/auth.mjs +++ b/extensions/connector-namespaces/auth.mjs @@ -28,14 +28,23 @@ let legacyAuthCacheRemoved = false; useIdentityPlugin(cachePersistencePlugin); -async function loadAuthenticationRecord() { +export async function loadAuthenticationRecord({ + readFile = fs.readFile, + deserialize = deserializeAuthenticationRecord, + authRecordFile = AUTH_RECORD_FILE, +} = {}) { + let serialized; try { - const serialized = await fs.readFile(AUTH_RECORD_FILE, "utf-8"); - return deserializeAuthenticationRecord(serialized); + serialized = await readFile(authRecordFile, "utf-8"); } catch (error) { if (error?.code === "ENOENT") return undefined; throw error; } + try { + return deserialize(serialized); + } catch { + return undefined; + } } async function saveAuthenticationRecord(record) { diff --git a/extensions/connector-namespaces/auth.test.mjs b/extensions/connector-namespaces/auth.test.mjs index c96e649f..89f551e6 100644 --- a/extensions/connector-namespaces/auth.test.mjs +++ b/extensions/connector-namespaces/auth.test.mjs @@ -4,6 +4,7 @@ import assert from "node:assert/strict"; import { ConnectorAuthenticationRequiredError, InteractiveAuthBroker, + loadAuthenticationRecord, TOKEN_CACHE_NAME, } from "./auth.mjs"; @@ -36,6 +37,7 @@ test("ARM token requests require an explicit browser sign-in", async () => { }, loadAuthRecord: async () => undefined, }); + await assert.rejects( broker.getToken(), (error) => error instanceof ConnectorAuthenticationRequiredError @@ -47,6 +49,25 @@ test("ARM token requests require an explicit browser sign-in", async () => { }); }); +test("a malformed authentication record falls back to browser sign-in", async () => { + assert.equal( + await loadAuthenticationRecord({ + readFile: async () => "{malformed-json", + }), + undefined, + ); +}); + +test("authentication record read failures remain operational errors", async () => { + const readError = Object.assign(new Error("authentication record is unreadable"), { code: "EACCES" }); + await assert.rejects( + loadAuthenticationRecord({ + readFile: async () => { throw readError; }, + }), + (error) => error === readError, + ); +}); + test("interactive sign-in reports pending then done and caches the ARM token", async () => { let credentialOptions; let authenticateOptions; diff --git a/extensions/connector-namespaces/package.json b/extensions/connector-namespaces/package.json index 27749254..24e6f05b 100644 --- a/extensions/connector-namespaces/package.json +++ b/extensions/connector-namespaces/package.json @@ -14,8 +14,8 @@ ], "license": "MIT", "dependencies": { - "@azure/identity": "^4.13.1", - "@azure/identity-cache-persistence": "^1.3.0", + "@azure/identity": "4.13.1", + "@azure/identity-cache-persistence": "1.3.0", "@github/copilot-sdk": "1.0.6" } }