Skip to content

Security: RunKiwi/docs

Security

docs/security.md

id security
title Running coding agents without exposing your API keys
sidebar_label Security & credentials
description Kiwi seals customer credentials to a daemon's X25519 public key, so secrets are plaintext only inside the process that needs them — never in the sandbox, never on disk.
sidebar_position 9

Running coding agents without exposing your API keys

Kiwi's credential model is built so that customer secrets are only ever in plaintext inside the one process that needs them — the daemon running the task — and never in the sandbox, never in the Control Plane's memory in BYOC, and never on disk unencrypted.

Two keypairs per daemon

On boot, a daemon generates two keypairs (pkg/crypto):

  • X25519 — for credential sealing. Customer credentials are encrypted to the daemon's X25519 public key, so only that daemon can open them.
  • Ed25519 — for heartbeat signing. The daemon signs its polls so the Control Plane can verify the request comes from the registered daemon.

How a credential travels

Customer credentials (an Anthropic, OpenAI or Gemini key, a git token) are stored by the Control Plane sealed to a specific daemon's X25519 public key — using a NaCl anonymous sealed box (an ephemeral sender keypair per seal, so there is no sender identity). At rest, that sealed blob is additionally encrypted by the configured key manager: a static key for dev/BYOC, or Cloud KMS envelope encryption for managed.

When a daemon leases work, it receives the sealed credentials in the poll response and opens them in memory with its X25519 private key. They are used to call the LLM provider and to open the PR — and are never written into the sandbox or persisted in plaintext.

sequenceDiagram
  autonumber
  actor Customer
  participant CP as Control Plane
  participant KMS as Key manager<br/>(static key / Cloud KMS)
  participant Daemon

  Note over Daemon: on boot, generate<br/>X25519 + Ed25519 keypairs
  Daemon->>CP: register (single-use join token)<br/>publish X25519 public key
  Customer->>CP: store credential
  CP->>CP: seal credential to daemon's<br/>X25519 public key (NaCl sealed box)
  CP->>KMS: encrypt sealed blob at rest
  Note over Daemon: later — leases a worker
  Daemon->>CP: poll (Ed25519-signed)
  CP->>KMS: decrypt at-rest layer
  CP-->>Daemon: sealed credential
  Daemon->>Daemon: OpenSealed with X25519 private key<br/>(in memory only)
Loading

BYOC vs managed — and the zero-knowledge caveat

The same sealing model runs in both deployment modes; what differs is who operates the machine that holds the private key.

Managed BYOC
Who runs the daemon Kiwi You, in your own cloud
Where credentials are unsealed Kiwi-operated host Your VPC
Can the Control Plane operator decrypt? Yes — Kiwi holds the private key No — the key never leaves your cloud
Zero-knowledge? No Yes

:::warning Managed is not zero-knowledge In BYOC, the daemon runs in your cloud and the Control Plane never sees plaintext credentials — that is a genuine zero-knowledge property. In managed mode, Kiwi operates the daemon and holds its private key, so Kiwi can decrypt. Do not treat managed mode as zero-knowledge. Zero-knowledge is a BYOC-only claim. :::

Defense in depth around the sandbox

The credential model is reinforced by the execution seam:

  • The Architect and Implementer run in the daemon, so model-generated code never sees the LLM key.
  • The sandbox is split in two, which makes the claim stronger than "the sandbox has no network": model-generated code never has network access, and the phase that does never holds a secret. Dependencies install with a network and an empty environment; verification has the credentials and no route off the machine.
  • On the shared Free tier, the host additionally blocks the cloud metadata endpoint, so no container can read the VM's service-account token.
  • Free-tier daemons each have their own keypair, so the per-org sealing boundary holds even when many daemons are packed onto a shared host.

What ran is on the record

Credential handling is a claim about what Kiwi does not do, which is the hardest kind to check. Every job therefore produces a verified execution record: a signed, hash-chained account of which model ran, on which provider, in which sandbox and network mode, and what each step of the loop did. In BYOC the daemon's signing key never leaves your cloud, so the execution half of the record is attested by something Kiwi does not hold.

Operational hardening

  • In production mode, KIWI_ENCRYPTION_KEY, KIWI_SERVER_TOKEN, and KIWI_CORS_ALLOWED_ORIGINS must be set explicitly.
  • For managed deployments, set KIWI_KMS_KEY to use Cloud KMS envelope encryption instead of a static key.
  • Set KIWI_VER_SIGNING_KEY to counter-sign execution records; without it they persist as unsigned rather than being signed by a throwaway key.
  • Daemons register with single-use join tokens; after registration the persisted identity key is sufficient.

There aren't any published security advisories