Attestation Flow — DeviceCheck, Challenge, e o Hierarchy com PoW Fallback
The complete integrity mechanism of the desktop app — DeviceCheck native addon, challenge flow, and the if/else hierarchy with PoW fallback for non-Apple platforms
Attestation Flow — DeviceCheck, Challenge, and the PoW Hierarchy
Part of openai-research. Sources: runtime eval in the main process (bezetacil v4 harness), real captured traces, and main process code.
The if/else hierarchy
The desktop app has a boolean gate that determines which integrity mechanism is used:
vPr() = isDeviceCheckSupported() && appVersion !== '0.0.0'
(both checked via window.electronBridge in the renderer)
if (vPr()) {
→ ATTESTATION FLOW (macOS arm64 with DeviceCheck addon)
GET /ios/attestation_challenge + x-sentinel-dc
→ app_attest_challenge in the conversation body
} else {
→ SENTINEL FLOW (PoW fallback for non-Apple platforms)
POST /sentinel/chat-requirements/prepare
→ OpenAI-Sentinel-* headers (PoW FNV-1a on desktop, SHA3-512 on web)
}
The desktop on macOS takes the attestation path. The sentinel/PoW path exists for platforms where Apple's DeviceCheck API is unavailable.
The DeviceCheck native addon
File: /Applications/ChatGPT.app/Contents/Resources/native/devicecheck.node (Mach-O 64-bit bundle, arm64; N-API C++ — napi_register_module_v1, Napi::AsyncWorker pattern; links /System/Library/Frameworks/DeviceCheck.framework)
API: single export generateToken() returning a Promise:
{
"supported": true,
"tokenBase64": "AgAAAK9Pf1MC0r1SMXZiiu+iiG…", // 2980 chars
"latencyMs": 17.02
}
- It is a thin wrapper over macOS's DCDevice (DeviceCheck framework; the macOS 15+ DeviceCheck/AppAttest surface) — the token is an opaque device/app attestation (Apple server-validated with the developer's credentials).
- NO
com.apple.developer.devicecheckentitlement exists — verified on the addon AND the app (codesign -d --entitlements): the app carriescom.apple.developer.team-identifier: 2DC432GLL2,aps-environment: production, application-groups (2DC432GLL2.com.openai.codex.notifications,2DC432GLL2.com.openai.sky.CUAService), allow-jit/allow-unsigned-executable-memory, audio/camera/network/calendars, keychain-access-groups (2DC432GLL2.*,2DC432GLL2.com.openai.shared). - Standalone loading verified:
require()from a plain Node process (outside the app) →{supported: true, tokenBase64: "AgAA…"}— no code-signature chain validation in the addon (earlier claim disproven: the loader does not check the caller; macOS DCDevice provides the attestation regardless). - Implication for flow A — UPDATED 2026-08-27: the addon is self-sufficient (no re-signing/entitlements), BUT the plain-Node token produced 403 at the challenge (2026-08-26, first run) and the documented acceptance continues only for tokens generated in the app context; the validated A1 path generates the token in the main process (the bezetacil
devicechecktrigger) — see flow-definitions / flow-a-manual-step-by-step. The server-side validation is keyed to the developer's Team/bundle; acceptance of an out-of-bundle token was NOT observed (one 403 observed).
The challenge flow (step by step)
1. Generate DeviceCheck token:
addon.generateToken() → {supported: true, tokenBase64: "AgAA…", latencyMs: 17}
(Apple hardware attestation, ~17ms)
2. GET /backend-api/ios/attestation_challenge
Headers:
Authorization: Bearer <codex access token>
chatgpt-account-id: <account id from JWT>
OAI-Language: en-US
originator: Codex Desktop
oai-did: <device uuid>
x-sentinel-dc: {"token": "<DeviceCheck tokenBase64>"}
→ 200 {"attestation_challenge": "gAAAAAB…"}
3. POST /f/conversation with the challenge in the body:
"app_attest_challenge": "gAAAAAB…"
Device registration (the missing piece — runtime-verified 2026-08-27)
The main process DeviceCheckCookieManager (asar class Epe) implements the registration:
registerCookie():POST /devicecheckbody{bundle_id, device_token}withcredentials: 'include'(the Electron session cookies — the web session__oailb), then stores the returned_devicecheckcookie (name const_devicecheck, domain.chatgpt.com, secure; freshness checked against ~60s).ensureCookie(): readsoai-didfrom the request headers; if the did differs from the registered one or the cookie is stale →registerCookie().attachToken():x-sentinel-dc: {"token": <DeviceCheck token>}— attached to the challenge request.- Runtime state (verified via bezetacil): the Electron default session holds
_devicecheck(.chatgpt.com, secure) +__oailb(chatgpt.com and chat.openai.com, HttpOnly — the link-session derived web session).installation_idin this install:594e0bbc-3b0c-470a-833a-7943425e76ba(file~/.codex/installation_id, resolved by the app-server at boot — uuid v4, regenerate-or-reuse percodex-rs/core/src/installation_id.rs). - Implication (facts, not yet exercised): an unregistered did+token pair gets 403 on
/ios/attestation_challenge; the registration POST carries the web-session cookies — the standalone probe flow needs the__oailb(link-session) + the/devicecheckstep before the challenge.
What the server validates
The server receives the DeviceCheck token via x-sentinel-dc and validates it against Apple's DeviceCheck API using the app's server-side credentials. This proves:
- The request originates from a real Apple device
- The app is the genuine ChatGPT desktop (bundle ID
com.openai.codex) - The device hasn't been modified to bypass attestation
The attestation_challenge returned is a one-time-use token that the server validates when the conversation request arrives. It expires quickly (observed: valid for the duration of one message send).
The sentinel/PoW fallback (non-Apple platforms)
On Windows/Linux, or({arch, platform}) returns false (requires darwin && arm64), so generateToken() returns null → vPr() is false → the renderer falls back to the sentinel flow:
POST /sentinel/chat-requirements/preparewith fingerprint- PoW solving (FNV-1a on desktop, SHA3-512 on web)
OpenAI-Sentinel-*headers on the conversation request
This is the flow that OmniRoute implements (for web/cookies) and that our probes initially replicated — but it's the fallback, not the primary mechanism.
The x-sentinel-dc header format
{"token": "<Apple DeviceCheck tokenBase64>"}
Wrapped by attachToken() on the deviceCheckCookieManager (asar class Epe), which also manages a registration cookie (POST /devicecheck with {bundle_id, device_token}) to register the device with the backend before the first attested request.
DeviceCheck addon standalone loading (verified again 2026-08-26, plain Node)
The addon exports exactly one function. Loading it from a plain Node process (no app context) works — this is the flow-A basis for a standalone:
node -e "const a=require('/Applications/ChatGPT.app/Contents/Resources/native/devicecheck.node');
a.generateToken().then(t=>console.log(t.supported, t.tokenBase64.slice(0,12)))"
# → true AgAAAK9Pf1MC…
Object.keys(addon) → ["generateToken"]. (Note: the app's own main loads it via process.resourcesPath + '/native/' + 'devicecheck.node' through createRequire in the app — same addon.)
The platform gate (the if/else that decides attestation vs sentinel)
The main process gate is or({arch, platform}):
function or({arch = process.arch, platform = process.platform} = {})
{ return platform === 'darwin' && arch === 'arm64' }
Condition chain:
or()false →sr()(the addon loader) returns null → DeviceCheck unavailablear({platform})— platform pre-gate:platform === 'darwin' || platform === 'win32'(Windows can generate but the arch gate filters to darwin arm64)vPr()in the renderer — the final boolean:isDeviceCheckSupported() === true && appVersion !== '0.0.0'
To exercise the sentinel/PoW fallback from a Mac, the gate to patch at runtime is vPr() in the renderer (or or() in the main) — flipping either makes the app take the sentinel route.
What the server validates (DeviceCheck token)
- Sends the DeviceCheck token via
x-sentinel-dc: {"token": "<tokenBase64>"} - The server validates it against Apple's DeviceCheck API using its own server-side credentials for the app's Team ID + bundle ID (
com.openai.codex) - Returns the one-time
attestation_challengewhich the client must echo in the conversation body asapp_attest_challenge
This is why the token is not forgeable outside an Apple device running the signed app: Apple's API rejects tokens from unregistered devices or other bundle IDs, and the server owns the server-side DeviceCheck verification secrets.
TypeScript types (addon + manager + wire)
// ── the native addon ─────────────────────────────────────────────────────
export interface GenerateTokenResponse {
supported: boolean
tokenBase64: string // base64 App-Attest (2980 chars) — 'AgAA…'
latencyMs: number // ~17
}
export interface DeviceCheckAddon {
generateToken(): Promise<GenerateTokenResponse> // single export
}
// ── the main-process gate ────────────────────────────────────────────────
export type PlatformGate = { arch?: string; platform?: string } // defaults: process.arch/process.platform
export const gate = (o: PlatformGate): boolean => o.platform === 'darwin' && o.arch === 'arm64'
export interface ElectronBridge {
isDeviceCheckSupported(): boolean
// plus appVersion from SentryInitOptions ('0.0.0' → gate off)
}
export const vPr = (isSupported: boolean, appVersion: string): boolean =>
isSupported && appVersion !== '0.0.0'
// ── the DeviceCheckCookieManager (asar class Epe) ────────────────────────
export interface DeviceCheckRegistrationBody { // POST /devicecheck
bundle_id: string // 'com.openai.codex'
device_token: string
}
export interface DeviceCheckRegistrationResponse {
is_ok: boolean
status_code: number
// …(server description fields)
}
export interface EpeOptions {
bundleIdentifier: string
generateToken(): Promise<GenerateTokenResponse> // the addon loader
url: string // '/devicecheck' resolved against the prod API base
}
export interface EpeState {
registration: Promise<unknown> | null
registeredCookie: Electron.Cookie | null
registeredDeviceId: string | null
}
export interface DeviceCheckCookieManager {
ensureCookie(headers: Record<string, string>): Promise<void> // compare oai-did + cookie; register if stale/unknown
attachToken(headers: Record<string, string>): Promise<void> // headers['x-sentinel-dc'] = JSON.stringify({token})
registerCookie(headers: Record<string, string>): Promise<Electron.Cookie> // POST /devicecheck (credentials:'include')
}
export const DeviceCheckCookieName = '_devicecheck' // the registration cookie name
export const DeviceCheckCookieFreshnessMs = 60_000 // the freshness window (Tpe)
// ── the challenge wire ───────────────────────────────────────────────────
export interface XSentinelDc { token: string } // headers['x-sentinel-dc'] = JSON string of this
export interface AttestationChallengeResponse { attestation_challenge: string } // 'gAAAAAB…', one-time
// ── entitlements (verified) ──────────────────────────────────────────────
export interface AppEntitlements {
'com.apple.developer.team-identifier': '2DC432GLL2'
'com.apple.developer.aps-environment': 'production'
'com.apple.security.application-groups': string[]
'com.apple.security.cs.allow-jit': boolean
'com.apple.security.cs.allow-unsigned-executable-memory': boolean
'com.apple.security.device.audio-input'?: boolean
'com.apple.security.device.camera'?: boolean
'com.apple.security.files.user-selected.read-write'?: boolean
'com.apple.security.network.client': boolean
'com.apple.security.personal-information.calendars'?: boolean
'keychain-access-groups': string[]
// NO com.apple.developer.devicecheck entitlement (verified — macOS DCDevice does not require it)
}
Cross-references
- chat-submode-protocol — where the challenge enters the conversation body
- sentinel-fingerprint-comparison — the PoW fingerprint details
- codex-flow-protocol — the backend codex endpoints
- chatgpt-desktop-architecture — the app architecture
- flow-definitions / flow-a-manual-step-by-step — the A/A1 placement and the validated steps