Confidential runs (encrypted logs + artifacts)

A run’s logs and artifacts are the agent’s output over your code — the most sensitive thing on the /v1 surface. By default Pond now encrypts them to the consumer’s key: the control plane stores and serves only ciphertext, and only a holder of the matching private key can read them. So a TLS-terminating proxy (e.g. Cloudflare Tunnel) and Pond’s own storage are untrusted by design — we can’t read your results.

(Source bundles and model credentials were already sealed; this closes the last plaintext on the path.)

How it works

  • At submit, the client sends an X25519 public key. Each log line is sealed to it; each artifact is stream-encrypted with a per-file key that’s sealed to it (sidecar <name>.pondkey). The plaintext key is discarded.
  • The control plane writes, stores, and serves ciphertext — it never holds a decryption key. The consumer decrypts locally on read.
  • Recipients are a list, so you can add a backup/escrow key (see below).
  • For swarm (LLM-agent) stages on a worker pool, the worker seals the agent’s output before it leaves the worker (each stdout/stderr chunk an ENC1: frame sealed to the recipients). So even the control plane — and any proxy on the workercontrol-plane hop — only ever sees ciphertext, in transit and at rest. The recipient public keys ride to the worker on the job (they’re public; nothing secret crosses the orch wire). The result attestation is unaffected: the worker still hashes the plaintext it produced, and the trusted side verifies that signed hash — it never re-hashes the stored ciphertext.

Using it with the CLI (default on)

Nothing to do — pond run submit encrypts by default, generating + storing a keypair for the context on first use:

pond run submit --project <id> --source <git-url> --harness codex-brokered --wait
#                                                              ↑ logs stream back decrypted
pond run artifacts <run-id> results.jsonl     # fetched as ciphertext, decrypted locally
pond keys show                                # your public key + where the private key lives

Opt a single run out with --no-encrypt (logs/artifacts stored in plaintext). Turn the deployment default off with POND_ENCRYPTION_DEFAULT=false.

Using it over the raw /v1 API

Generate an X25519 keypair, send the base64 public key, and decrypt with your private key (the cli/cryptobox.py wire format: sealed-box for log lines, chacha20-stream-v1 for artifacts):

POST /v1/runs
{ "projectId": "…", "definition": {…},
  "encrypt": true,
  "recipientPubkeys": ["<base64 32-byte X25519 pubkey>"] }

GET /runs/{id}/logs returns each message as ENC1:<b64>,…; GET /runs/{id}/artifacts/{name} returns ciphertext and …/{name}.pondkey the sealed content key. An encrypt:true submit with no recipient is rejected.

Operability & trade-offs (read these)

  • No server-side reading. The operator console and any server-side log/artifact viewing show only ciphertext for encrypted runs — that’s the guarantee.
  • Key loss = unrecoverable results. The private key lives with the consumer (pond keys path to back it up). Mitigation: recipients is a list — submit with multiple recipientPubkeys (e.g. your laptop + an offline backup, or a team escrow key) so any one can decrypt.
  • Artifacts are opaque. Encrypted *.jsonl aren’t merged across stages server-side (last stage wins); any merging/parsing the engine used to do is a consumer-side concern after decryption.
  • swarm results are extracted client-side. Because the agent transcript is sealed, the engine can’t parse ```<fence> blocks into results.jsonl for an encrypted run. The sealed transcript is served as agent-<task>.log (an ENC1: frame stream — pond run artifacts <run> agent-<task>.log decrypts it); extract your results from the decrypted text with the same parse spec you authored. Plaintext runs are unchanged (the engine still writes results.jsonl).
  • Local-mode stays available. builtin/shell/noop stages keep working under encryption — the control plane (their executor) seals output as it’s produced, then discards the key.

Threat model (what each party can see)

PartySees logs/artifacts?
Cloudflare / any TLS-terminating proxyNo (ciphertext)
Pond at-rest storage (DB, disk)No (ciphertext)
Pond control-plane operator / consoleNo (ciphertext)
The worker running the agentyes — it executes the agent (it’s the trust boundary)
The consumer (holds the private key)yes

The worker necessarily processes plaintext to run the agent; encryption protects everything downstream of the executor. For pooled swarm runs the worker seals the agent’s output before upload, so the control plane never sees it in plaintext — in transit or at rest. The one residual exposure is local-mode swarm (no pool attached): there the control plane is the executor, so it sees plaintext and seals at the storage boundary (encrypt-at-control-plane) — pair a worker pool for the full end-to-end guarantee.