Skip to main content

Secrets Management

Control Zero offers two ways to handle LLM provider keys (OpenAI, Anthropic, Google, Cohere, and others). You choose the approach that fits your security requirements.

Two Modes

Mode 1: Policy-Only (Default)

You manage your own provider keys. Control Zero only handles policy enforcement. Your keys never touch our servers.

import controlzero
import openai

# You manage the OpenAI key yourself (env var, vault, etc.)
openai_client = openai.OpenAI(api_key=os.environ["OPENAI_API_KEY"])

# Control Zero only enforces policies
cz = controlzero.ControlZero(api_key="cz_live_...")
cz.initialize()

# Check policy before calling OpenAI
cz.enforce("llm.generate", "model/gpt-4")
response = openai_client.chat.completions.create(model="gpt-4", ...)

This is the default. No additional configuration is needed.

Mode 2: Secrets Vault (Optional)

Store your provider keys in Control Zero's encrypted vault. The SDK retrieves and decrypts them at runtime. No plaintext keys in your codebase or environment variables.

import controlzero

# Enable secrets -- the SDK fetches encrypted keys during initialize()
cz = controlzero.ControlZero(
api_key="cz_live_...",
secrets_enabled=True,
)
cz.initialize()

# Retrieve the decrypted key from memory
openai_key = cz.get_secret("openai")
anthropic_key = cz.get_secret("anthropic")

How the Vault Works

Storing a Secret

  1. Open the Control Zero dashboard and navigate to your project.
  2. In the Secrets Vault section, click Add Secret.
  3. Select the provider (OpenAI, Anthropic, Google, Cohere, or Custom).
  4. Paste your API key and give it a name (e.g., "Production OpenAI Key").
  5. Click Store Secret.

The plaintext value is shown exactly once. After that, only a masked prefix is displayed. The key is encrypted before it reaches our database.

Retrieving a Secret in the SDK

When secrets_enabled is set, initialize() fetches the encrypted secret bundle alongside the policy bundle. This is a single network call -- no additional requests.

After initialization, get_secret() is a zero-latency in-memory lookup. It does not make network calls and adds no overhead to your application's hot path.

Python

cz = controlzero.ControlZero(
api_key="cz_live_...",
secrets_enabled=True,
)
cz.initialize()

# By provider type
openai_key = cz.get_secret("openai")

# By name (as shown in the dashboard)
key = cz.get_secret_by_name("Production OpenAI Key")

Go

client := controlzero.NewClient("cz_live_...",
controlzero.WithSecrets(),
)
if err := client.Initialize(ctx); err != nil {
log.Fatal(err)
}

// By provider type
openaiKey, err := client.GetSecret(controlzero.ProviderOpenAI)

// By name
key, err := client.GetSecretByName("Production OpenAI Key")

Node.js

const client = new ControlZero({
apiKey: 'cz_live_...',
secretsEnabled: true,
});
await client.initialize();

// By provider type
const openaiKey = client.getSecret('openai');

// By name
const key = client.getSecretByName('Production OpenAI Key');

Security Model

Encryption

  • At rest: Secrets are encrypted with AES-256-GCM before storage. The encryption key is derived from your project API key using HKDF-SHA256. Control Zero servers never store plaintext secret values.
  • In transit: All communication between SDKs and the Control Zero API uses TLS 1.3.
  • In memory: The SDK decrypts secrets into process memory only. Decrypted values are never written to disk, logged, or included in error messages.

Key Derivation

The encryption key used for secrets is derived from your project API key, not stored separately. This means:

  • Only holders of the project API key can decrypt the secrets.
  • Rotating your project API key automatically invalidates the derived key.
  • The derivation uses HKDF-SHA256 with a fixed context string, ensuring the derived key is cryptographically independent from the API key itself.

Access Control

  • Secrets are scoped to a single project. They cannot be accessed across projects.
  • Storing and deleting secrets requires dashboard access or an organization-level API token.
  • SDK access to secrets requires the project API key and secrets_enabled to be set.

What We Cannot See

  • Plaintext secret values are never stored on our servers.
  • Our database contains only the AES-256-GCM ciphertext.
  • Our staff cannot decrypt your secrets without your project API key.

Performance Impact

The vault is designed for zero hot-path overhead:

OperationWhen It RunsNetwork CallLatency
initialize()Once at startupYes (bundled with policy fetch)~100-200ms
get_secret()Per usageNo (in-memory lookup)< 1 microsecond
check() / enforce()Per actionNo (local evaluation)< 1 microsecond

Secrets are fetched once during initialization as part of the same policy bundle request. After that, all operations are in-memory with no network overhead.

Supported Providers

ProviderType StringExample Key Prefix
OpenAIopenaisk-proj-...
Anthropicanthropicsk-ant-...
GooglegoogleAIza...
Coherecohereco-...
CustomcustomAny format

Choosing Between Modes

ConsiderationPolicy-Only (Default)Secrets Vault
Provider keys on your serversYesNo
Provider keys in Control ZeroNoYes (encrypted)
Environment variable managementYou handle itNot needed
Key rotationYou handle itVia dashboard
Setup complexityNoneOne-time per key
SDK configurationDefaultAdd secrets_enabled=True
Compliance (key centralization)Keys in your infraKeys in one encrypted vault

Next Steps