buildd
Concepts

Secrets & Credentials

How Buildd stores and delivers credentials to workers securely

Secrets & Credentials

Overview

Buildd can manage credentials on behalf of workers so they don't need local API keys. Secrets are encrypted at rest and delivered inline during task claiming — workers receive decrypted values over HTTPS without any intermediate storage.

How It Works

1. User stores a secret in Buildd (dashboard settings)
2. Secret is encrypted with AES-256-GCM and persisted to Postgres
3. Worker calls POST /api/workers/claim
4. Server decrypts matching secrets and includes them in the claim response
5. Runner injects secrets into the subprocess environment
6. Claude Code reads ANTHROPIC_API_KEY / CLAUDE_CODE_OAUTH_TOKEN as usual

Secrets never touch disk on the runner. They exist only in the subprocess environment for the duration of the task.

Secret Types

PurposeEnv VarDescription
anthropic_api_keyANTHROPIC_API_KEYClaude API key for the worker subprocess
oauth_tokenCLAUDE_CODE_OAUTH_TOKENClaude Code OAuth token (seat-based billing)
mcp_credentialUser-defined (via label)Credentials for MCP server connections

MCP Credentials

MCP credentials use the label field as the environment variable name. For example, a secret with label DISPATCH_API_KEY is injected as DISPATCH_API_KEY=<value> into the worker's environment, making it available to any MCP server configuration that references that variable.

Encryption

All secret values are encrypted before storage using:

  • Algorithm: AES-256-GCM (authenticated encryption)
  • Key derivation: scrypt with a random 16-byte salt per secret
  • Authentication: GCM auth tag prevents tampering
  • Key source: ENCRYPTION_KEY environment variable (minimum 32 characters)

The stored format is base64(salt + iv + authTag + ciphertext). Each secret gets its own random salt and IV, so identical plaintext values produce different ciphertext.

Scoping

Secrets are scoped to a team and optionally to an account:

secrets
  id, teamId, accountId, workspaceId,
  purpose, label, encryptedValue

During task claiming, the server looks up secrets for the claiming account's team and includes matching anthropic_api_key, oauth_token, and mcp_credential entries in the response.

Runner Behavior

The runner uses server-managed secrets as a fallback — local credentials always take priority:

if runner has local ANTHROPIC_API_KEY → use it
else if claim response includes serverApiKey → inject it

This means existing runners with their own API keys continue working unchanged. Server-managed secrets only activate when the runner has no local credentials configured.

Data Model

-- Encrypted secrets store
secrets
  id            uuid PRIMARY KEY
  team_id       uuid REFERENCES teams(id)
  account_id    uuid REFERENCES accounts(id)     -- nullable
  workspace_id  uuid REFERENCES workspaces(id)   -- nullable
  purpose       text   -- anthropic_api_key | oauth_token | mcp_credential | custom
  label         text   -- env var name (required for mcp_credential)
  encrypted_value text -- AES-256-GCM ciphertext
  created_at    timestamptz
  updated_at    timestamptz

-- Unique constraint: one secret per account + purpose + label
UNIQUE (account_id, purpose, label)

API

The secrets API supports both session cookies and API key (Bearer bld_xxx) authentication.

Store a Secret

POST /api/secrets
Authorization: Bearer bld_xxx
Content-Type: application/json

{
  "purpose": "mcp_credential",
  "label": "dispatch-api-key",
  "value": "dsp_xxx..."
}

List Secrets (Metadata Only)

GET /api/secrets
Authorization: Bearer bld_xxx

Returns secret records without values — encryptedValue is never exposed via API.

Delete a Secret

DELETE /api/secrets?id=<secret-id>
Authorization: Bearer bld_xxx

Via MCP

Agents can manage secrets programmatically through the manage_secrets MCP action:

# List all secrets (metadata only)
buildd action=manage_secrets params={ action: "list" }

# Store an MCP credential
buildd action=manage_secrets params={ action: "set", label: "dispatch-api-key", value: "dsp_xxx..." }

# Delete a secret
buildd action=manage_secrets params={ action: "delete", secretId: "uuid" }

This enables agents to self-configure their MCP access — for example, a Chief of Staff role can store credentials for the dispatch and buildd MCP servers without requiring manual setup.

On this page