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
- Open the Control Zero dashboard and navigate to your project.
- In the Secrets Vault section, click Add Secret.
- Select the provider (OpenAI, Anthropic, Google, Cohere, or Custom).
- Paste your API key and give it a name (e.g., "Production OpenAI Key").
- 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_enabledto 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:
| Operation | When It Runs | Network Call | Latency |
|---|---|---|---|
initialize() | Once at startup | Yes (bundled with policy fetch) | ~100-200ms |
get_secret() | Per usage | No (in-memory lookup) | < 1 microsecond |
check() / enforce() | Per action | No (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
| Provider | Type String | Example Key Prefix |
|---|---|---|
| OpenAI | openai | sk-proj-... |
| Anthropic | anthropic | sk-ant-... |
google | AIza... | |
| Cohere | cohere | co-... |
| Custom | custom | Any format |
Choosing Between Modes
| Consideration | Policy-Only (Default) | Secrets Vault |
|---|---|---|
| Provider keys on your servers | Yes | No |
| Provider keys in Control Zero | No | Yes (encrypted) |
| Environment variable management | You handle it | Not needed |
| Key rotation | You handle it | Via dashboard |
| Setup complexity | None | One-time per key |
| SDK configuration | Default | Add secrets_enabled=True |
| Compliance (key centralization) | Keys in your infra | Keys in one encrypted vault |
Next Steps
- Follow the Quick Start guide, which includes an optional secrets step.
- See the Python SDK, Go SDK, or Node.js SDK for full API reference.
- Review the API Reference for programmatic secret management.