Independent Pi/OpenRouter review: x-ai/grok-build-0.1
Source: a10323dede4413fbf295916b8ad12e3dbad7514e. Status: completed.
A. Can an in-scope adversary recover or infer request/response contents before the intended delay?
SUPPORTED CONDITIONALLY (with multiple concrete limitations and unverified assumptions).
Strongest code-grounded reasons attacks are blocked (under the stated assumptions):
- All upstream exchange capture, canonicalization, and sealing happens inside the enclave (v2_proxy.rs:92-106, 161-199:
fetchpopulateshistorywith bodies/trailers,canonical_record+encrypt_recordusing the active epoch key + per-record random 24-byte nonce + sequence, thenpublishof the sealed blob). The plaintext body is never written to any vsock, host buffer, or log before sealing. - Sealed records use XChaCha20Poly1305 with context binding (epoch, sequence, puzzle ID) + outer commitment (timelock/lib.rs:105-123, 141-155). Host sees only framed ciphertext (host/artifacts.rs:67-123 + framing.rs).
- Inner TLS (enclave tls + client InnerTLS) + enclave-to-upstream TLS (net.rs + dialer) carry the actual request/response bytes. Parent/Cloudflare/DNS see only outer carrier, IPs, SNI (when ECH unavailable), sizes, and timing.
- Diagnostics are strictly static printable strings (v2_diagnostics.rs:33-41, 56-61). No request-derived values, URLs, or bodies.
- Target validation + redirect following is strict and inside the enclave (v2_proxy.rs:127-136, 161-199): HTTPS only, port 443, no credentials/fragments/local/private, no self-name, public IPs enforced post-DNS.
- Request admission + detached processing: semaphore permit held until after
publish(v2_proxy.rs:78-89, 107); epochArc<Epoch>cloned so in-flight requests keep the key even after watchdog clears the global active (v2_epoch.rs:47-52 + tests). - No POST; only GET. No body in the relay path.
Concrete code-grounded issues and gaps (every finding has file/line, attacker, trace, impact, minimal repro sketch):
- Future epoch keys are materialized in enclave RAM while the prior epoch is active and serving requests. In
v2_epoch.rs:122-148:spawn_blocking(generate...)for epoch N+1 occurs immediately afteractivate(N)(which setspublication_not_before = N.expires), before the wait loop and*active = None+activate(N+1). The returnedGeneratedPuzzle { epoch_key, ... }(containing the final wrapped epoch key) is live in the task while N serves. (See alsogenerate_with_rng:320-321(fills epoch_key),activate:94(moves it intoEpoch), and comment at 130 claiming "never leave enclave memory before currently active ends" — the code computes it earlier.)- Attacker: malicious parent/EC2 owner (in-scope).
- Execution trace: parent reads enclave memory (via side channel, FFI leak, snapshot, or incorrect isolation) during N's lifetime; later receives published puzzle N+1 + any sealed records for N+1.
- Impact: epoch key for N+1 obtained without performing the 7 serial RandomX segments. Contents of future records decryptable immediately on publication (no ~7-day delay).
- Minimal repro sketch (not executed): instrument enclave to log
*generated.epoch_keyafter generate of N+1; confirm it is non-zero whileactivestill points to N (via status or request). In real hardware, use any memory disclosure primitive against the enclave process. - This directly violates the "does NOT assume ... memory handling, secret management, lifecycle or verifier is correct" clause in the objective. Nitro isolation is the only barrier; it is not assumed sound.
- Host receives every sealed record and puzzle before the service considers publication complete.
publish(v2_epoch.rs:61-73) doeswrite_frame(name, data)thenread_exact "OK\n". Hosthandle_record(host/main.rs:330-349) +store(artifacts.rs:67-123) receives the full bytes, then ACKs. Service only then uses the name inx-attested-relay-*headers and activates.- Attacker: parent host.
- Trace: host buffers the sealed blob (and can archive it privately even if it later lies about durability or serves a truncated index).
- Impact: host has all ciphertexts for an epoch as soon as the enclave emits them. Combined with any early key (above) or after public solve, full contents. (Host can also ACK without durable storage, breaking the "publicly decryptable" half of the goal while still having the blobs.)
- No per-record service signature or independent provenance. Records are only XChaCha20Poly1305 under the epoch key (timelock/lib.rs:92-123). Once the key is public (intended after ~7 days), anyone can produce a valid
EncryptedRecordfor any sequence.canonical_recordonly ensures deterministic CBOR for the AEAD (v2_proxy.rs:29-33). Puzzle signatures bind only the manifest (timelock/lib.rs:216-226).- Attacker: host, archive provider, or any solver after key publication.
- Impact: after delay, an adversary can inject plausible fake records for the epoch. A client cannot cryptographically prove that a particular record was the one actually produced by the enclave for a specific request at a specific time (without an out-of-band trusted digest/receipt published earlier). This does not leak real contents but undermines the audit/delayed-disclosure claim.
- Response body is only delivered to the client after record publish succeeds (v2_proxy.rs:107-119). On publish failure the client gets 502 and never sees the upstream body (even though the enclave already fetched it). Upstream request was still made. This is a partial mitigation for "no audit, no delivery" but means real request contents reached the destination without the client ever seeing the response.
- Other minor inference vectors (metadata, not contents): Parent sees exact record sizes, timing, sequence numbers, and artifact names. ECH fallback or resolver queries reveal hostnames. These are explicitly allowed by the model but can enable traffic analysis or candidate inference when combined with other side information. No content bytes leak this way under the crypto assumptions.
- Dev mode completely bypasses (v2_main.rs:138-145, attest.rs:87-88, etc.): short iterations, no Graviton5, OS RNG, etc. Production gates are present but the objective is evaluated against the prod path.
Unresolved questions / insufficient evidence:
- Actual side-channel resistance or memory-zeroization completeness for RandomX FFI paths, intermediate
xvalues during chains, and all secret buffers (explicitly disclaimed in the objective; no analysis supplied). - Whether the 7-segment serial chain has any practical shortcut (objective assumes none; no independent verification of the wrapper + RandomX v2.0.1 + vendored sources under the exact FFI usage).
- Long-running production rollover (generation ~25h per comment, 24h epochs, real artifacts, key recovery) — only short-iteration dev runs and unit tests are in the snapshot.
- Whether host can force a publish ACK path that leaves partial state (e.g., attestation published, puzzle not, then restart) that leaks a key or allows a "solved" puzzle to be used later.
- Archive completeness and retention: host can delete/omit after ACK; no enclave-side confirmation of S3/Object Lock/etc.
Would I rely on this for sensitive content under the explicit assumptions? No. The design correctly keeps contents inside the enclave until sealing and ties the key to serial RandomX work if every disclaimed component (memory handling, FFI, secret lifecycle, absence of side channels, correct composition, RandomX wrapper, Nitro isolation) is perfect. The objective itself states we must not assume those things. A single memory disclosure, FFI bug, or side channel during the window when future keys are live (v2_epoch.rs) is sufficient to break the delay. For anything beyond low-sensitivity "delay by a week if everything goes right" use, add client-side encryption, shorter epochs, or stronger isolation primitives.
B. Can an adversary obtain epoch keys, private seeds/intermediates or equivalent decryption capability early, or make the service use an already substantially solved puzzle, WITHOUT breaking the RandomX primitive?
SUPPORTED CONDITIONALLY (generation/solving parallelism and publication ordering are strong; early materialization and untrusted host ACKs are the main gaps).
Strongest code-grounded reasons attacks are blocked:
- Every secret (dataset_key, 7 seeds, epoch_key, nonces, signing seed) is filled from
attester.fill_random(NSM in prod) before any RandomX work (timelock/lib.rs:312-325 ingenerate_with_rng; v2_main:169-172). No OS fallback in prod. Failures zeroize and bail. - Generation computes the 7 chains in parallel (intended and safe: timelock/lib.rs:327-351,
std::thread::scope+ independentinput(epoch, segment, ...)). Solving is strictly serial:for segment in cp.segment..SEGMENTS { compute chain; x = unwrap(puzzle, segment, &x); ... }(solve:476-492). Eachunwrapdoes AEAD under the just-computed Y (lib.rs:262-284). - Puzzle publication + bundle + attestation ACKs are required before
*active = Someand before any request can use the key (v2_epoch.rs:76-101:activated_* = now; puzzle_artifact = publish(puzzle); ...; *active = Some; ensure(accepts(now))). - Old active is explicitly cleared (
*active = None) before activating the next (v2_epoch.rs:143). Expiration watchdog uses monotonic time +monotonic_expired(v2_epoch.rs:31-34, 47-52). In-flight requests holdArc<Epoch>(leases) so they can finish sealing, but no new requests after deadline. - Per-epoch
sequenceis atomicfetch_update(never wraps; errors on exhaustion) + fresh random nonce per record (v2_proxy.rs:122-125, 103-105). Record context binds epoch + sequence + puzzle ID. - No host-supplied entropy or puzzle material. Host only supplies untrusted creds for hardware verify (hardware.rs) and ACKs for storage.
generateis pure enclave + NSM. - Checkpoints (client-side only) are validated against manifest ID, bounds, and initial seed (timelock/lib.rs:438-455); tampering fails later AEAD or final commitment.
- Attestations for bundles use fresh random nonce (v2_epoch.rs:77); public attestations are rate-limited and cannot starve epoch evidence (semaphore split + test at v2_main:204-224).
- On any publish failure in activate, the whole run bails ("puzzle publication failed; service remains closed") — no partial activation of a published puzzle (v2_epoch.rs:144-146).
Concrete code-grounded issues:
- Future epoch keys/seeds are computed and resident while the prior epoch is active (same as A above; v2_epoch.rs:122-140 generate N+1, then wait on
publication_not_before, then deactivate N). TheGeneratedPuzzlestruct (includingepoch_key) lives in the generator task. If the parent can read enclave memory during this window, it obtains the next epoch key + all intermediate Y values without any RandomX work on the (not-yet-published) puzzle.- Attacker: parent.
- Trace: memory read of
generatedor the laterEpoch.keyduring N's service window. - Impact: early decryption capability for N+1 records as soon as their puzzle is published. No RandomX shortcut required.
- Repro sketch: same as above; add logging of
generated.keyright after the spawn_blocking join while the previousactiveis still Some.
- Host can cause generation/publication to fail closed, but cannot inject a pre-solved puzzle. If host withholds ACKs or returns bad ACKs, publish bails and the epoch is never activated (v2_epoch.rs:73, 144-146; publish retries only 3x). The just-generated puzzle may have been partially emitted (host saw the bytes), but no requests ever use its key. On restart the enclave generates a fresh one. There is no code path that lets the host supply a manifest, wrapped keys, or epoch key.
- No cross-epoch key reuse or checkpoint poisoning of the service. Checkpoints are only used by external solvers (timelock/src/main.rs + lib.rs solve). The service never loads checkpoints. Each epoch has its own fresh entropy and key commitment.
- Publication ordering is enforced, but host controls durability and visibility. Host can ACK a publish without making the artifact retrievable (or can delete it later). Service treats ACK as success and activates. Solvers then cannot obtain the puzzle to start work (or to verify provenance). This hurts the "publicly decryptable after delay" property more than early key recovery.
- Delayed/replayed host messages on the records path. The framing is length-prefixed + exact "OK\n" (common/framing.rs + host). A replayed old ACK would only succeed for an identical (name, data) pair (artifacts.rs:94-118 does exact-content check on AlreadyExists). A malicious host can still replay old sealed records after the key is public.
- Generation of the next puzzle can be starved or delayed by host (e.g., records sink offline), but this only hurts availability/rollover, not early key disclosure. The current active key remains protected until its own expiration.
Unresolved questions / insufficient evidence:
- Whether the parallel generation workers or RandomX FFI ever write intermediate seeds/Ys/epoch_key to host-visible memory, swap, or logs (disclaimed).
- Real behavior under 25h+ generation with production iterations + epoch rollover (only dev short runs + the watchdog/lease tests exist).
- Whether a host that ACKs a puzzle but then withholds it from the public index can later "replay" it after the service has moved on, creating a window where a puzzle is "solved" relative to one client's view but not activated.
- Absence of side channels that would let a parent observe the exact timing or values of the 7 Y computations (even if the final key is not directly readable).
- That the 7-segment wrapping + HKDF + AEAD chain has no chosen-input or related-key weakness when the manifest is public (objective assumes RandomX primitive is strong; the wrapper is not assumed correct).
Would I rely on this for sensitive content under the explicit assumptions? Conditionally for the "no pre-solved puzzle can be forced into service" and "solving remains serial" properties, but no for "no early key material". The code correctly avoids giving the host any influence over puzzle generation or activation ordering, and the serial unwrap is enforced. However, the explicit decision to compute the next epoch's entire secret material (including the final key) while the current epoch is still live, combined with the disclaimer on memory/secret management, means a parent that can read enclave memory obtains future keys without RandomX work. Publication ACKs are from the untrusted host. For high-sensitivity use I would require either (a) stronger memory protection guarantees + side-channel resistance evidence, (b) never materializing future keys until the prior epoch has truly ended, or (c) client-side encryption layered on top.
Overall recommendation: The v2 design is a careful attempt to achieve the stated goal with explicit acknowledgments of its limits. The core data flow (inner TLS → enclave capture → seal with fresh-per-epoch key → publish only after upstream) and the serial-chained RandomX construction are sound where the unassumed components hold. Multiple grounded issues (early future-key materialization in RAM, untrusted host ACKs for both durability and ordering, lack of per-record provenance) mean the confidentiality and delayed-disclosure claims are conditional at best. The snapshot contains good tests for the lease/watchdog/ordering properties and canonical encoding, but not for the long-running production path or the disclaimed security assumptions.