Proof of Work — Sentinel PoW Mechanism (Desktop vs Web)
Proof of Work — Sentinel PoW Mechanism (Desktop vs Web)
Part of openai-research. The sentinel PoW is the integrity mechanism used by the web/browser route (and by the desktop fallback when DeviceCheck is unavailable — the gate flip).
What it is, where it lives
- The client (renderer) builds a fingerprint (array of slots, sPr, 24 slots on desktop — see sentinel-fingerprint-comparison), serializes
gAAAAAC + base64(fingerprint)as the prekey body forPOST /sentinel/chat-requirements/prepare. - The server returns requirements + a seed; the client then solves a PoW to mutate the fingerprint slots (
counterandtimeslots — the solver increments/mutates them) and computes the final value. - PoW hash: desktop = FNV-1a 32-bit murmur-finalized; web route (OmniRoute reference) = SHA3-512 (100k iteration loop, target
0fffff) — mutually exclusive families per client/route. - The final key travels in the
OpenAI-Sentinel-*headers of the protected requests.
When it runs / who invokes it
- Web route: per typing throttle (
/f/conversation/preparefires on every keystroke, debounced — the prepare carries the growing partial text), so longer conversations → heavier continuous PoW (observed CPU increase / GPU idle in Safari = single-threaded JS solving, no GPU/WASM accelerator, no web worker). - Desktop (fallback): the same sentinel chain with the desktop fingerprint,
originator: Codex Browser+ plain Chrome UA (Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) … Chrome/151.0.0.0), one-time per prepare/first request. - Solver is in the renderer main thread (no dedicated worker found in the asar; the murmur2/murmur3 hash primitives are bundled). This is the CPU spike/GPU idle observation: single-thread JS.
- The desktop main route (attestation) has no PoW — the prepare still runs on keystrokes (typing throttle) but without the sentinel chain.
The slot values baked in (desktop, real trailer)
0 screen dims (e.g. 2494)? No — real: slot 0 = screen.width + screen.height derived, slot 1 = new Date().toString(), slot 2 = performance.memory.jsHeapSizeLimit (real ~4.4GB in the flip run), slot 3 = counter (solver-mutated), slot 4 = UA, slot 5 = real webview chunk (app://-/assets/index-rLYmMQgS.js), slot 6 = <!-- PROD_BUILD_TAG_HERE --> (confirmed leaving the client, accepted by the server), 7-8 languages, 9 = time (solver-mutated), 10-12 navigator/document/window props, 13 performance.now(), 14 uuid, 15 search params, 16 hardwareConcurrency, 17 timeOrigin, 18-23 flags.
Contrast with DeviceCheck (the alternative)
| Sentinel/PoW | DeviceCheck attestation | |
|---|---|---|
| Gate | !vPr() (no DeviceCheck) | vPr() = isDeviceCheckSupported && appVersion ≠ 0.0.0 |
| Cost | CPU-bound single-thread JS, per prepare/typing | ~17ms native (hardware attestation via addon) |
| Header/body | OpenAI-Sentinel-* + prekey p | x-sentinel-dc: {token} → challenge gAAAAAB… → body app_attest_challenge |
| Detection surface | fingerprint slots + PoW hash family | Apple's DeviceCheck server validation |
| Mobile/web/desktop | web (SHA3-512) + desktop fallback (FNV-1a) | desktop macOS arm64 only |
For the standalone
The PoW is single-threaded JS on the web route — a standalone harness can solve it far faster with WebGPU/WASM (or wasi/wasix in Node), but for the desktop flows the attestation path is the better target (no PoW, ~17ms). The fallback PoW (desktop flavor: FNV-1a + real fingerprint) is reproducible with the captured slot values. performance.now()/timeOrigin should be sampled honestly — the server validates ranges (the web-vendor fixed/heap hardcodes (4294705152) are a detectable deviation).
TypeScript types (solver)
export type PoWHashFamily = 'fnv1a-32-murmur' | 'sha3-512'
export interface SolverResult { answer: string; slots: unknown[] }
export interface PoWSolver {
/** Mutate the mutable slots (counter/time) until the hash meets the target. */
solve(seed: string, difficulty: string): SolverResult
/** FNV-1a 32-bit murmur-finalized (desktop) — the only family on the desktop fallback. */
hash: (input: string) => string
}
export interface SentinelRequirements { // POST /sentinel/chat-requirements/prepare → 200
token?: string
prepare_token?: string
pow?: { seed: string; difficulty: string } // when non-empty, the client solves and sets
}
// ── headers the solved key lands in ──────────────────────────────────────
export interface OpenAIRequestSentinelHeaders {
'OpenAI-Sentinel-Chat-Requirements-Token'?: string
'OpenAI-Sentinel-Chat-Requirements-Prepare-Token'?: string
'OpenAI-Sentinel-Proof-Token'?: string // 'gAAAAAB' + base64(seed+~S…)
}
// ── the contrast table as types ──────────────────────────────────────────
export interface IntegrityMechanism {
gate: 'devicecheck' | 'sentinel-pow'
cost: 'native ~17ms' | 'cpu-bound single-thread js'
headers: { 'x-sentinel-dc'?: string; 'OpenAI-Sentinel-*'?: string }
lifetime: 'one-time challenge' | 'per-prepare'
}
Cross-references
- sentinel-fallback-validation — the exercised desktop fallback (real fingerprint run)
- sentinel-fingerprint-comparison — slot-by-slot vs OmniRoute
- attestation-flow — the primary mechanism on the desktop
- vendor-analysis — why the web vendors' PoW causes bans