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

@@ -10,6 +10,10 @@ import { getClientIp, apiSuccess, apiError } from "@/lib/api-response";
import { annotate } from "@/lib/logging/context";
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";
/**
* Operator-shared central Codex — device-code poll (admin only).
@@ -93,6 +97,12 @@ export const POST = apiHandler(async (request: NextRequest) => {
});
cookieStore.delete("central_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.central,
accountId: result.creds.accountId,
});
// A new central sign-in: every account's ledger rows describe the refused
// one, and the central connection is one credential shared by all of them.
// #1279 — and every record's note windows close, as on the admin AI
+6
View File
@@ -5,6 +5,10 @@ import { auditLog } from "@/lib/auth/audit";
import { apiSuccess, getClientIp } from "@/lib/api-response";
import { annotate } from "@/lib/logging/context";
import { checkRateLimit } from "@/lib/rate-limit";
import {
codexCredentialKey,
forgetCodexSignIn,
} from "@/lib/ai/codex-slug-cache";
/**
* Operator-shared central Codex (ChatGPT subscription) — status + disconnect.
@@ -55,6 +59,8 @@ export const DELETE = apiHandler(async (request: NextRequest) => {
create: { id: "singleton", adminCodexConnectionStatus: "disconnected" },
});
forgetCodexSignIn(codexCredentialKey.central);
await auditLog("admin.central-codex.disconnected", {
userId: user.id,
ipAddress: getClientIp(request),