Skip to content

Confidential compute: roadmap to privacy *from* the provider #27

Description

@DGaffney

Summary

Our first external security report showed that a provider can read the prompts and completions it serves — e.g. by dropping a 13-line sitecustomize.py into the inference venv and capturing a requester's prompt (SSN, card number, etc.) verbatim. The report is correct, and the root cause is architectural, not a bug:

Decryption happens in the Rust agent (advisor.rs::open_from) and the plaintext is streamed to a Python + vllm-mlx child over a Unix socket. Everything downstream of that decryption runs on hardware the provider owns.

End-to-end crypto_box sealing protects the prompt in transit to the provider you chose; it does not protect it from that provider. This issue tracks the path from where we are (best-effort hardening) to actually raising the posture.

Full trust model: docs/security/confidential-compute.md. Reporting: SECURITY.md. Decision record: docs/adr/0002-confidential-compute-threat-model.md.

What "solved" requires

Two things have to become true, and neither is true today:

  1. The plaintext must live somewhere the machine owner can't read it. Today it sits in an interpreted Python process the owner provisioned and fully controls.
  2. The requester's encryption must be bound to an attestation of that environment — not to the provider's long-lived X25519 key (which the provider holds in the clear).

The posture ladder

Rung 0 — Cost-raising hardening ✅ (done)

Defense in depth that kills the cheap attacks but doesn't change the trust model. Shipped:

  • Launch the inference child under python -S -E -B — disables the site module, removing the entire sitecustomize/usercustomize/.pth startup-hook class the report used (the class that needs no package source change and so survives the wrapper-overwrite check). Wrapper re-adds only the venv site-packages via sys.path.insert.
  • Self-harden the Python child: PT_DENY_ATTACH (macOS) / PR_SET_DUMPABLE=0 (Linux) / RLIMIT_CORE=0.
  • Plus existing: mlock over plaintext, Zeroizing, never piping child output into agent logs, agent-side SIP requirement + PT_DENY_ATTACH + no-core.

This raises cost; it does not solve it. A provider who edits imported source or reads process memory is not stopped.

Rung 1 — Make "trust by selection" real and accountable (cheap, ship now)

Stop implying trustlessness; make the trusted-provider model good: verifiable provider identity (DIDs — have it), on-record ToS/confidentiality attestations (mint these already), reputation/history, requester-side allowlists / "friend networks," optional stake-and-slash so a provider caught leaking loses something. Adds consequences and informed choice, not secrecy. Honest and fully shippable.

Rung 2 — Attestation-bound ephemeral keys (buildable now, additive to the lexicon)

Requester seals to a per-session key the provider generates and attests, and the receipt commits to the attestation that was live. Receipts already strong-ref an attestation record — natural hook. Adds no secrecy on a Mac yet, but closes the "long-lived key sitting in the clear" gap and is the substrate Rungs 3/4 bind to. Lexicon-additive (new optional fields), so it respects the evolve-additively invariant.

Prerequisite for Rung 3.

Rung 3 — macOS-native sealed inference (the real ceiling without new hardware)

Strongest thing achievable on the hardware we target. Good news: ~60% of the attestation data model already existsattestation.rs signs binaryHash, sipEnabled, secureBootEnabled, authenticatedRootEnabled, osVersion, an MDA cert chain to Apple's Enterprise Attestation Root (mda.rs), a SEP-backed selfSignature, and encryptionPubKey; cocore.entitlements already sets get-task-allow=false + library validation on + allow-jit=false. The rung is really "(1) make the measured binary be the one that sees plaintext, and (2) make the requester actually check, fail-closed."

Work streams:

  • WS1 — Get plaintext out of the owner-controlled interpreter (long pole). Replace SubprocessEngine (Python+vllm-mlx over UDS) with a native engine so the attested binaryHash actually covers the code that touches the prompt. The Engine trait already isolates this. (A) in-process native inference via mlx-c FFI or llama.cpp+Metal (de-risk: mature/embeddable, but moves to GGUF weights), or (B) a separate signed native helper over a mutually-authenticated UDS. (A) is cleaner. Cost: lose process-level crash isolation (native segfault aborts the agent).
  • WS2 — Sign / harden / notarize the agent (not just the shell). Parallel entitlements for the agent binary: hardened runtime, get-task-allow=false, library validation on (blocks swapping a bundled dylib), no dyld-env / disable-* escapes; Developer ID signed + notarized + stapled. Early spike needed: Metal shader JIT vs hardened runtime — precompile signed .metallib to avoid allow-jit (which would punch a hole in the exact protection we're buying). Payoff: on a SIP-enabled Mac, a hardened-runtime binary with no get-task-allow cannot have its task port acquired by the owner (even root) without an Apple debugger entitlement or disabling SIP (reboot → kills the process; agent refuses SIP-off). That blocks the task_for_pid/memory-read path.
  • WS3 — Measure the right binary + requester verifies (highest leverage). Repoint attestation at the inference code path; add cdhash (csops(CS_OPS_CDHASH)/SecCodeCopySelf), Developer-ID team id, hardened-runtime/library-validation/get-task-allow status (additive lexicon fields). Bind the Rung-2 ephemeral key into the attestation instead of the long-lived key. Add requester-side verification in packages/sdk / sdk/py / console (verifier-side equivalent of mda.rs): verify MDA chain → Apple root, sipEnabled && get-task-allow==false && libraryValidation==true, osVersion ≥ floor, cdhash ∈ known-good, freshness nonce — then seal. Default-on, fail-closed. Skipping this makes the rung theater.
  • WS4 — Reproducible builds + transparency log. cdhash ∈ known-good only means something if a requester can map cdhash → reviewable source. Deterministic builds + a published append-only measurement log (Apple PCC's "code transparency" property).
  • WS5 — Productization. Receipt/attestation tier marker (attested-confidential vs best-effort); keep the Python path as an explicit untrusted tier. OS-version floor enforcement; revisit MDA revocation (currently skipped per mda.rs).

Residual holes (still "mostly solved," not "solved"): self-attestation of software on macOS isn't first-class (no TDX/SEV-SNP/TPM-style measured-launch quote; cdhash+MDA+SEP leans on library-validation/no-debugger-entitlement/SIP, which a kernel compromise could defeat); Metal JIT tension; kernel exploit / vulnerable OS beats kernel-enforced task-port protection (mitigate by attesting osVersion); physical / DMA / cold-boot out of scope.

Rung 4 — A real TEE (true solution, hardware pivot)

Trustless confidentiality needs memory the owner cannot read by construction: NVIDIA Confidential Computing GPUs (H100/H200) + AMD SEV-SNP / Intel TDX, with attestation the requester checks before sealing — but that means providers run Linux + CC GPUs, not Macs/MLX (hardware/business pivot). Or wait for Apple to expose enclaved inference; Apple Private Cloud Compute is the existence proof and design template (attested stateless nodes, ephemeral per-node keys, code transparency, no persistence) but relies on custom silicon a federated network can't replicate. darkbloom.dev is chasing the Apple-silicon-confidential angle — worth tracking for primitives.

FHE / secure-MPC (no trusted hardware) remain many orders of magnitude too slow for useful-size LLMs — watch-the-research, not buildable.

Recommended sequencing

  1. Rung 1 now (honest + immediately valuable).
  2. Rung 2 next (additive, lexicon-compatible, the substrate everything above binds to).
  3. WS2 JIT spike (cheap; de-risks whether Rung 3 is even feasible under hardened runtime).
  4. WS1 (long pole; start with llama.cpp+Metal to de-risk).
  5. WS3 (leverage existing attestation; new work is fail-closed requester verification), WS4 in parallel.

The contract until a rung lands

The network gives you verifiable receipts and transport privacy, not privacy from the provider. Don't send secrets to a provider you don't operate or trust. Receipt integrity (signed, content-addressed, non-forgeable) is a separate guarantee and is not weakened by any of the above.


Contributions from people who know more about confidential compute than we do are very welcome — especially on WS1 (native engine), the WS2 JIT/notarization spike, and WS3 requester-side verification.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions