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 → anENC1:frame sealed to the recipients). So even the control plane — and any proxy on the worker→control-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 pathto back it up). Mitigation: recipients is a list — submit with multiplerecipientPubkeys(e.g. your laptop + an offline backup, or a team escrow key) so any one can decrypt. - Artifacts are opaque. Encrypted
*.jsonlaren’t merged across stages server-side (last stage wins); any merging/parsing the engine used to do is a consumer-side concern after decryption. swarmresults are extracted client-side. Because the agent transcript is sealed, the engine can’t parse```<fence>blocks intoresults.jsonlfor an encrypted run. The sealed transcript is served asagent-<task>.log(anENC1:frame stream —pond run artifacts <run> agent-<task>.logdecrypts it); extract your results from the decrypted text with the sameparsespec you authored. Plaintext runs are unchanged (the engine still writesresults.jsonl).- Local-mode stays available.
builtin/shell/noopstages 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)
| Party | Sees logs/artifacts? |
|---|---|
| Cloudflare / any TLS-terminating proxy | No (ciphertext) |
| Pond at-rest storage (DB, disk) | No (ciphertext) |
| Pond control-plane operator / console | No (ciphertext) |
| The worker running the agent | yes — 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
swarmruns 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-modeswarm(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.