reviews/current/objective-20260909T223027Z/x-ai__grok-4.6.md
On this page

Independent Pi/OpenRouter review: x-ai/grok-4.6

Source: a10323dede4413fbf295916b8ad12e3dbad7514e. Status: completed.

Independent review of timelock-proxy v2 (a10323dede4413fbf295916b8ad12e3dbad7514e)

This is a source review only. I did not execute tests, build the image, talk to NSM/RandomX, or reproduce PCR0. Tests and comments are treated as intent, not evidence.

Verdict on the two questions

Property Rating
A. Recover/infer request/response contents before the intended delay SUPPORTED CONDITIONALLY for cryptographic plaintext (inner/upstream TLS + epoch AEAD). Not implied by “RandomX has no shortcut” alone for a one-week wall clock. Metadata/size/destination inference is real and in-scope, but is not a demonstrated plaintext break.
B. Early epoch keys / seeds / intermediates / decryption capability, or forcing an already-worked puzzle, without breaking RandomX SUPPORTED CONDITIONALLY for the lifecycle/composition that is actually in the v2 path. I did not find a protocol path that hands the parent epoch_key, seeds[1..], or wrap outputs, or that accepts a host-supplied puzzle. Faster hardware and shared solving of the same published chain are not blocked by the RandomX assumption.

I would not rely on this for sensitive content whose premature disclosure is unacceptable. Under the stated assumptions it is a calibrated public time-lock with a measured enclave, not a week-bound VDF, and several confidentiality-adjacent claims (per-request delay, host-invisible destinations, durable capture, post-release provenance) are explicitly weaker than “contents stay secret for ~7 days”).


What the v2 path actually does

Reachable production binary is attested-relay-enclavecrates/enclave/src/v2_main.rs, not legacy tlproxy-enclave. Records are not drand/tlock (crates/common/src/lib.rs seal()). They are XChaCha20-Poly1305 under an epoch key wrapped by seven RandomX segments (crates/timelock/src/lib.rs).

Compiled work parameters (config/relay-v2.toml):

Delay of contents from operator/intermediaries is therefore: inner TLS + upstream TLS while live; after that, whoever holds puzzle.json must evaluate seven dependent RandomX chains. Generation evaluates those seven chains in parallel; solving cannot.


A. Contents before the intended delay

Strongest code-grounded reasons a parent/network adversary does not get plaintext

  1. Inner TLS terminates in the enclave, with attestation bound to the actual peer SPKI (v2_main.rs attestation_for_epoch, tls.rs spki_der, Python verify.py _bound_policy requiring doc["public_key"] == peer SPKI and a fresh 32-byte nonce). Outer GET /relay?...&payload= is stop-and-wait ciphertext (crates/host/src/http_relay.rs); the host comments that it does not interpret inner bytes.

  2. Upstream HTTP is TLS’d inside the enclave (v2_proxy.rs connect_upstream / net.rs connect_tls) after enclave DoH to pinned resolver IPs (dns.rs). Parent CONNECT sees an IP and TLS records, not URL path or body.

  3. The audit record is sealed before the client response is built, and publish ACK is required (v2_proxy.rs ~104–119). Failure → 502 relay operation failed, not a plaintext body on the inner channel via that path. Ciphertext is XChaCha20-Poly1305 with AAD relay-encrypted-record-v1 || version || epoch || sequence || puzzle_id (encrypt_record in crates/timelock/src/lib.rs).

  4. Diagnostics are static (v2_diagnostics.rs; v2_main.rs log_infra discards the formatted string). Host logs are also static (crates/host/src/main.rs log). I did not find a path that prints URL, body, or keys.

  5. --dev cannot silently become production confidentiality. Same PCR0 (runtime flag), but NSM attestation is skipped and mode is "dev" (attest.rs new, v2_main.rs attestation JSON). A verifier that requires Nitro + graviton5_verified + measured iterations rejects it. A PCR0-only client would not; the Python client is not PCR0-only.

Why “RandomX is strong” does not imply a one-week confidentiality interval

The owner’s RandomX assumption is only: no cryptanalytic shortcut around a specified dependent chain. The intended delay is calibration against one Graviton5 core, encoded as 7 × 43,768,124 hashes (calibrate_workers uses the fastest worker to size a day; config/relay-v2.toml comments that this is “not a hardware speed bound”).

In-scope solvers may use faster CPUs. That does not break RandomX. Wall-clock recovery on day 2–4 is compatible with the primitive assumption and with the threat model’s own “faster hardware can shorten recovery.” Treating “I found no protocol leak” as “one week of confidentiality” would be exactly the error the prompt forbids.

Publication-relative vs per-request:

Metadata / inference (do not hide this inside “metadata exceptions”)

v2 boots the network as direct, not Mullvad (v2_main.rs Net::new(transport.clone(), false, Vec::new())). The parent therefore sees every upstream IP. Without ECH, it also sees SNI (net.rs fallback on ECH failure). Response sizes and timings are visible on vsock and on outer /relay payload lengths (MAX_OUTPUT = 32KiB chunks).

That is not AEAD plaintext. It is in-scope inference of destination and, for some sites, of which response was fetched. The objective asked for CONTENTS; I am not rating A as VIOLATED on this alone. It does mean “intermediaries learn nothing about the request” is false.

Conditional guarantees (A)

If Nitro isolation holds, if the client actually runs the Nitro/SPKI/nonce/policy checks (not a fetch-only client), if RandomX+AEAD+HKDF composition is correct, if the adversary’s serial hash rate is close to the Graviton5 calibration, then I did not find a source-level way for parent/Cloudflare/other clients to read URL path or bodies until that chain is evaluated.

That is not a proof of impossibility. Unsafe RandomX FFI, unreviewed vendored C++, kernel/library copies of secrets, and side channels remain open.


B. Early keys, intermediates, or a pre-worked puzzle

Construction (this is the actual sequential bottleneck)

generate_with_rng (crates/timelock/src/lib.rs ~301–398):

solve (~458–498) is strictly serial: hash segment → unwrap next seed → … → key_commitment check. Checkpoints are digest-integrity only; a forged skip fails AEAD or the commitment. Extra JSON fields are denied.

Generation parallelism does not give solvers free segments, unless those y_i / later seeds leave the enclave. I did not find a send path for them.

Shared work: one puzzle per epoch, by design. Colluding solvers share checkpoints; they do not reduce the dependent hash count on equivalent hardware. They also decrypt every record in that epoch once the key exists.

Lifecycle checks that actually sit on the v2 path

Concern What the code does Residual
Publish before key is used activate starts monotonic+NSM clock before puzzle.json is written (v2_epoch.rs 79–88), then ensure!(active.accepts(now)) (98). Delayed ACK cannot rebase an already-disclosed puzzle onto a fresh 24h window. Dev test test_delayed_publication_ack_never_rebases_expired_puzzle_as_fresh_epoch encodes that intent; not executed. Host can still ship puzzle.json to solvers at vsock-receipt, seconds before ACK. That is publication, not pre-publication.
Next puzzle while current is live Next generate_with_rng may run concurrently in enclave RAM; publication waits on NSM publication_not_before (v2_epoch.rs 132–140). Active slot is cleared before activate (143). Relies on NSM time and on secrets not leaving RAM. Parent pause vs monotonic is discussed below.
Host-injected / old puzzle Enclave never reads a puzzle from the host. Signing key is NSM-random at boot (v2_main.rs 169–171). Restart ⇒ new signer ⇒ new puzzles. Host can mix artifacts in the public directory (availability/provenance), not force the running enclave’s epoch.key.
Expiration / leases Watchdog drops the global Arc on monotonic lifetime (v2_epoch.rs 40–52) even if NSM/generation hangs. Admission uses uncached NSM time (trusted_time_ms_uncached, v2_proxy.rs 81–84). Sequence checked_add (next_sequence) refuses wrap. Request lease is the cloned Arc until seal+publish (~30s fetch + 30s publish). Pause: if monotonic freezes while NSM advances, accepts() still fails on NSM expiry; key may linger in RAM but not on the host. AWS is trusted for NSM; lingering RAM is not parent-readable under that assumption.
Entropy / RNG Production fill_random is NSM-only; failure zeroizes (attest.rs). seed_os_rng reseeds /dev/random twice or aborts. Record nonces still use OsRng in encrypt_record (timelock lib.rs 103–104), i.e. kernel RNG after that seed, not NSM. Conditional on the 4.14 reseed ioctl doing what the comments claim. I did not run it. Weak nonces would be integrity/identity of records more than early epoch keys (24-byte XChaCha nonce).
Light vs Full Production RandomXMode::Full (v2_epoch.rs 120). Light is --dev only. Same hash test-vector is claimed for both; Full is the speed path. Changing iterations changes measured config/PCR0. Parent cannot drop iterations without changing PCR0 or running --dev (rejected by a real verifier).
Hardware gate MIDR Neoverse V3, NSM PCR4 = SHA384(0x00×48 || instance-id), EC2 DescribeInstances for c9g.4xlarge + enclaves enabled (hardware.rs). Fail-closed in non-dev. Binds generation machine to the calibration class; does not bind solvers.

I did not find: replay of host ACKs that causes key reuse; checkpoint load inside the enclave; publication of wrapped plaintext; reuse of an already-solved dataset_key/seed_1 pair across boots.

What would count as a B violation and is not shown

Not a B violation in this code, but also not prevented by the RandomX assumption: a faster core finishes the same chain in much less than seven calendar days; after seed_1 is public the operator has the same starting position as everyone else, with first access measured in seconds.

Conditional guarantees (B)

If AWS/Nitro isolation holds, if the RandomX FFI computes the intended dependent chain (vendored C++ was not in this snapshot beyond randomx.h), if AEAD/HKDF wrap is correctly bound, then the lifecycle I traced does not give early equivalent decryption capability via generation parallelism, host storage lies, delayed ACKs, restarts, leases, or chosen inputs.

That is still not a proof. The unsafe binding (crates/timelock/src/randomx.rs: flags | 128 | 16, cache init without RANDOMX_FLAG_V2, unsafe impl Sync for Dataset) is a review target. A wrong-flag VM that still returned the light-mode test vector would be a silent delay collapse; I cannot confirm it from this snapshot.


Concrete issues (code-grounded)

1. One-week claim is not implied by the stated cryptographic assumption

2. Delay is publication-relative; late-epoch requests are ~1 day short

3. v2 parent sees destinations (and often SNI)

4. Record nonces/keys vs NSM: mixed entropy story

5. Post-release provenance is not a confidentiality property, but the code invites confusion

Records are not service-signed. After epoch_key is public, anyone can encrypt_record with a new XChaCha nonce (lib.rs 92–124). Puzzle signature does not authenticate a particular ciphertext. Host index is explicitly unauthenticated (artifacts.rs comments). This does not give early decrypt; it means “this ciphertext is what the enclave sealed” is not proved by AEAD+puzzle alone.

6. Host ACK is not durability; capture is not complete

publish (v2_epoch.rs 57–74) treats OK\n as success. A malicious parent can ACK and delete, or kill the enclave after upstream TLS and before seal (v2_proxy.rs fetch then publish). That is the documented availability gap. It does not by itself decrypt contents.

7. Generation longer than the serving epoch

Config comments ~25.14h generation vs 24h epoch. Source: parallel generate then publication_not_before wait; if generate overruns, watchdog already cleared the key (expiration_watchdog) and admission returns 503 (v2_proxy.rs 82–84). Confidentiality fail-closed; availability gaps. Full-duration production generate/rollover is unverified in this review (and in the labeled docs).

8. FFI / missing RandomX source

Snapshot includes vendor/randomx/src/randomx.h and a SHA256SUMS check in crates/timelock/build.rs, not the C++ bodies. I cannot independently confirm the dependent chain, FLAG_V2 interaction with cache init, or JIT/SECURE behavior on aarch64 enclave kernels. Vm::hash is raw randomx_calculate_hash. Memory-safety defects here are in-scope even with PCR0.

9. Live Python get() does not re-bind the puzzle artifact

client.py get() forwards /f/https/... after one verify(). It does not check x-attested-relay-puzzle against the attested signing key. Confidentiality then rests on PCR0 code doing the right encrypt. Archive verify_bundle does bind puzzle parameters to attested policy — that is offline recovery, not the live GET.


Tests not executed

All of them, including:

Green tests in-tree are not used as proof.


Missing source that limits the conclusion

If RandomX sources differ from upstream aaafe713… while still matching the in-tree checksums, PCR0 would still attest the backdoored hasher. That is a build-identity issue, not something PCR0 solves.


Would I use this for sensitive content?

No, if the content must remain unknown to the operator/solvers for a real week, or forever.

Under the explicit assumptions (AWS/Nitro honest, client verifier honest, RandomX has no shortcut, calibration ≈ attacker serial rate), it is a coherent design: parallel generate, serial solve, NSM entropy, clock-before-puzzle, monotonic key drop, inner TLS+SPKI attestation, seal-before-return. Those are the strongest reasons early protocol decrypt looks blocked.

The same assumptions do not give a week against a faster core, do not hide destinations from the parent, do not make host storage real, and do not prove FFI/memory/side-channel correctness. For data that is acceptable to publish in ~6–7 Graviton5-core-days and whose destination may be known to the operator, this source is consistent with that weaker goal; it is not a substitute for it.