fix(ai): read a Codex sign-in's memory only under its own account

Every write to the per-sign-in Codex memory checked the account digest,
but the reads did not. A credential reconnected to another ChatGPT
account therefore tried the old account's working model first, sorted
by its refusals and trusted its model list, until the first write
rebound the entry, and the vision gate kept answering from the old
account in the meantime.

The client reads (working model, refusals, model list) now take the
credential and treat an entry learned under another account as empty.
The vision gate holds no account id, so the connect routes rebind the
entry and the disconnect routes drop it, and the gate reads the entry
as its process last bound it.
This commit is contained in:
Marc-André Bombeck committed 2026-10-29 21:36:33 +01:00
1 parent 648810745a
commit dfe34386be
15 files changed
+353 -92

No files matched your search

@@ -11,6 +11,10 @@ import { annotate } from "@/lib/logging/context";
import { INSIGHTS_CACHE_CLEARED } from "@/lib/insights/cached-text-cipher";
import { forgetProviderHealth } from "@/lib/ai/provider-health-ledger";
import { clearStatusNegativeWindows } from "@/lib/insights/status-negative-window";
import {
codexCredentialKey,
startCodexSignIn,
} from "@/lib/ai/codex-slug-cache";
/**
* v1.4.7.1: Device-code flow polling endpoint.
@@ -90,6 +94,12 @@ export const POST = apiHandler(async (request: NextRequest) => {
});
cookieStore.delete("codex_device");
// What this process learned about the old sign-in's models no longer
// describes a different account (`codex-slug-cache.ts`).
startCodexSignIn({
key: codexCredentialKey.user(user.id),
accountId: result.creds.accountId,
});
// A new sign-in: whatever the ledger held against the old one no longer
// describes this credential, and (#1279) the person's notes stop waiting
// out windows it earned, as on a save of their provider settings.
@@ -6,6 +6,10 @@ import { auditLog } from "@/lib/auth/audit";
import { apiSuccess, getClientIp } from "@/lib/api-response";
import { annotate } from "@/lib/logging/context";
import { INSIGHTS_CACHE_CLEARED } from "@/lib/insights/cached-text-cipher";
import {
codexCredentialKey,
forgetCodexSignIn,
} from "@/lib/ai/codex-slug-cache";
export const DELETE = apiHandler(async (request: NextRequest) => {
const { user } = await requireAuth();
@@ -27,6 +31,8 @@ export const DELETE = apiHandler(async (request: NextRequest) => {
},
});
forgetCodexSignIn(codexCredentialKey.user(user.id));
await auditLog("codex.oauth.disconnected", {
userId: user.id,
ipAddress: getClientIp(request),