reviews/moonshotai__kimi-k3.md
On this page

Review by moonshotai/kimi-k3

commit: 765b6710b621474e53c575a0ae3f02b76643355d
usage: {'prompt_tokens': 93450, 'completion_tokens': 19155, 'total_tokens': 112605, 'cost': 0.5675022, 'is_byok': False, 'prompt_tokens_details': {'cached_tokens': 64, 'cache_write_tokens': 0, 'audio_tokens': 0, 'video_tokens': 0}, 'cost_details': {'upstream_inference_cost': 0.5675022, 'upstream_inference_prompt_cost': 0.2801772, 'upstream_inference_completions_cost': 0.287325}, 'completion_tokens_details': {'reasoning_tokens': 16159, 'image_tokens': 0, 'audio_tokens': 0}}
time: 146s


Security review: timelock-proxy @ 765b6710

I read the README's threat model first and then the enclave, host, CLI, and build/deploy code against it. Overall the architecture matches the claims: TLS terminates in the enclave, upstream TLS is verified in the enclave, sealing is age∘tlock with a pinned chain, round selection rounds up, diagnostics are disciplined, and the CLI binds the attestation to the handshake certificate. I found no critical or high-severity confidentiality break. Findings below, in priority order.


High

H1. tlproxy verify nonce check compares against base64(nonce), not the raw nonce — verification always fails (or worse, gets patched around)

Medium

M2. Self-referential upstream: /<proxy's-own-name>/... amplifies one request into hundreds of in-flight exchanges and records

M3. smoltcp set_timeout(120s) silently aborts idle upstream connections, truncating long exchanges

M4. socket→user path can drop already-consumed bytes, corrupting the stream and the record

M5. Memory budgets sum to more than the 1 GiB enclave — OOM crash loses all in-flight records

M6. No per-sender fairness: one sender can hold all 256 in-flight slots for 10 minutes each

Low

L7. Attestation endpoint accepts a missing noncecrates/enclave/src/proxy.rs, attestation: nonce is Option; GET /.well-known/attestation with no query returns a valid nonce-less document. The CLI always sends one, so this is not exploitable today, but a nonce-less document is replayable by construction and future/other verifiers may not be as careful. Fix: require nonce (400 otherwise).

L8. Finalizer::drop leaks in_flight/capture_bytes when no runtime is availablecrates/enclave/src/proxy.rs: if Handle::try_current() fails, the record is dropped and the counters are never decremented; enough of these permanently degrades the enclave to 503s. Only reachable during shutdown/teardown, hence low. Fix: decrement the counters before the early return.

L9. ECH config that fails to parse silently downgrades to plaintext SNIcrates/enclave/src/net.rs, connect_tls: on ech_config() error it logs and uses the plain config, exposing the SNI to the parent for that upstream. The x-timelock-proxy-ech response header does let the sender detect it (not-offered/grease instead of accepted), which mitigates this; still, consider failing closed for hosts that published an ECH config, or at least documenting that a poisoned/unparseable HTTPS record downgrades privacy (not content).

L10. relay.state strings surface third-party API error text in the attestation response and landing pagecrates/enclave/src/relay.rs status() + net.rs Dialer::json (error includes up to 300 bytes of the Mullvad API response body) → Mode::Down(format!(...))RelayStatus.stateAttestationResponse.relay and the landing HTML. This is infrastructure data, not sender data, so it doesn't violate the diagnostics rule, but it is uncontrolled third-party text rendered into an HTML page and a security-relevant API response. Fix: reduce to a fixed category like the upstream-error classifier.

L11. Request/response trailers are forwarded but never capturedcrates/enclave/src/proxy.rs, TeeBody::poll_frame copies only data frames; trailer frames pass through unrecorded, and the record has no trailer field, so a record can show body_complete: true while trailers (which can carry meaningful data, e.g. gRPC status) are absent. Fix: capture trailers into the record, or strip te: trailers (already stripped) and drop trailer frames.

Info

I12. The seven-day guarantee rests solely on the NSM clock whenever the parent withholds beacons. The beacon lower bound only helps if refresh_loop can fetch; the parent carries those fetches and can blackhole them indefinitely (Purpose::Bootstrap goes direct). Then lock_round_at_ms uses only the NSM attestation timestamp. If the NSM/hypervisor clock were ever behind real time by more than the lock period, the target round would already be published at seal time and the operator could open records immediately. This is within the README's stated AWS trust assumption, but the README's "withholding beacons can only make a lock longer" is only half the story — beacons are also the only cross-check on the NSM clock. Consider an optional measured fail-closed mode: refuse to seal (or refuse requests) if no verified beacon is newer than some measured age.

I13. DoH resolvers (Cloudflare/Google) learn every upstream host name. TLS to the resolver hides queries from the parent, but the resolver itself sees them. This is implied by the design but absent from the README's explicit trust-assumption list; add it.

I14. ACME challenge window serves a throwaway-key certificate to any client offering acme-tls/1 ALPN (tls.rs CertResolver::resolve). A sender who offers that ALPN during a challenge gets a cert under a different key; they can't do anything with it (attestation binding fails), but it's worth a comment that this is intentional.

Nothing found at critical severity, and nothing else at high: the sealing construction, round math (round_after with div_ceil, max of start/finish/beacon bounds, 7-day floor enforced at parse and at seal time), the fail-closed relay, the diagnostics discipline, and the attestation binding (handshake-signature verification + user_data == cert SPKI + nonce + PCR0/1/2 + all-zero-PCR rejection) all check out as designed.


Verdict

The design is sound and the implementation is unusually careful about the things that matter — content never leaves the enclave unsealed, diagnostics are scrubbed, the lock math is conservative, and the CLI's attestation check is structurally correct. I would not deploy it as-is, for two reasons: (1) the nonce encoding bug in tlproxy verify means the entire sender-verification story currently fails closed — before trusting any deployment I would require that fixed and covered by an end-to-end test that runs verify against a real (dev-mode) enclave; and (2) the memory budgets provably exceed the 1 GiB enclave under adversarial load, so a sender can crash the enclave and destroy in-flight records. I'd also want M2 (self-proxy amplification) and M3/M4 (userspace-TCP truncation/byte-loss) fixed before relying on the "every exchange is recorded" property. With those addressed, the remaining findings are hardening, and I would trust the deployment to enforce its stated confidentiality and timelock properties under its documented trust assumptions (AWS Nitro, drand League of Entropy, operator key hygiene).