diff --git a/extensions/bluebubbles/src/client.test.ts b/extensions/bluebubbles/src/client.test.ts index 441fa648baa..407e2c4e7d6 100644 --- a/extensions/bluebubbles/src/client.test.ts +++ b/extensions/bluebubbles/src/client.test.ts @@ -581,6 +581,26 @@ describe("client cache", () => { }); expect(a).not.toBe(b); }); + + it("private-network config changes rebuild the client without explicit invalidation", () => { + const cfg = { + channels: { + bluebubbles: { + serverUrl: "http://192.168.1.50:1234", + password: "s3cret", + network: { dangerouslyAllowPrivateNetwork: true }, + }, + }, + }; + const allowed = createBlueBubblesClient({ cfg: cfg as never }); + expect(allowed.getSsrfPolicy()).toEqual({ allowPrivateNetwork: true }); + + cfg.channels.bluebubbles.network.dangerouslyAllowPrivateNetwork = false; + const denied = createBlueBubblesClient({ cfg: cfg as never }); + + expect(denied).not.toBe(allowed); + expect(denied.getSsrfPolicy()).toEqual({}); + }); }); describe("client construction", () => { diff --git a/extensions/bluebubbles/src/client.ts b/extensions/bluebubbles/src/client.ts index a00705dba36..3a65779ebc8 100644 --- a/extensions/bluebubbles/src/client.ts +++ b/extensions/bluebubbles/src/client.ts @@ -458,7 +458,7 @@ export class BlueBubblesClient { type CachedClientEntry = { client: BlueBubblesClient; - /** Fingerprint of {baseUrl, password, authStrategy.id} — cache hit requires full match. */ + /** Fingerprint of auth + SSRF-policy inputs — cache hit requires full match. */ fingerprint: string; }; const clientFingerprints = new Map(); @@ -467,11 +467,19 @@ function buildClientFingerprint(params: { baseUrl: string; password: string; authStrategyId: string; + allowPrivateNetwork: boolean; + allowPrivateNetworkConfig?: boolean; }): string { - // authStrategyId is included so two clients for the same account + credential - // that differ only in auth strategy do not silently share a cached instance. - // (Greptile #68234 P2) - return `${params.baseUrl}|${params.password}|${params.authStrategyId}`; + // Keep every construction-time behavior input here. The client stores auth + // and SSRF policy immutably, so config flips must rebuild without requiring + // a process restart or an explicit cache invalidation call. + return JSON.stringify({ + baseUrl: params.baseUrl, + password: params.password, + authStrategyId: params.authStrategyId, + allowPrivateNetwork: params.allowPrivateNetwork, + allowPrivateNetworkConfig: params.allowPrivateNetworkConfig ?? null, + }); } /** @@ -495,6 +503,8 @@ export function createBlueBubblesClient(opts: BlueBubblesClientOptions = {}): Bl baseUrl: resolved.baseUrl, password: resolved.password, authStrategyId: authStrategy.id, + allowPrivateNetwork: resolved.allowPrivateNetwork, + allowPrivateNetworkConfig: resolved.allowPrivateNetworkConfig, }); const cached = clientFingerprints.get(cacheKey); if (cached && cached.fingerprint === fingerprint) {