Every secret Company Agents stores on disk is encrypted with a master key. Every integration credential, every API token, every OAuth refresh token is stored in a single encrypted secret table in the database. The orchestrator decrypts secrets at the moment of use and never logs them. This guide is about the layers: the master key, the secret store, the integration credentials, and the rotation story for each.

The master key

The master key is a 32-byte AES-256 key. Everything else is encrypted with it. Default location:
~/.company-agents/instances/default/secrets/master.key
File permissions are enforced on startup: the orchestrator refuses to start if the file is world-readable. On Unix, the file is created 0600. You can move the key elsewhere:
MASTER_KEY_PATH=/path/to/master.key
Or provide it inline from a secret manager:
MASTER_KEY_SOURCE=vault://kv/data/company-agents/master-key
Supported sources:
  • file://path/to/key (default)
  • env://VAR_NAME (for dev only; env vars leak into logs)
  • vault://... (HashiCorp Vault)
  • aws-secretsmanager://arn (AWS Secrets Manager)
  • gcp-secretmanager://projects/.../secrets/... (GCP Secret Manager)
  • azure-keyvault://vault/secret (Azure Key Vault)
If the master key is unavailable at startup, the orchestrator will not start. There is no fallback. This is deliberate.

The encrypted secret store

Secrets live in the secrets table in the database. Each row is:
id              uuid primary key
company_id      uuid references companies(id)
name            text   -- e.g., "anthropic_api_key"
value           bytea  -- AES-256-GCM encrypted
nonce           bytea
created_at      timestamptz
updated_at      timestamptz
last_used_at    timestamptz
The value column is encrypted with the master key, using a fresh nonce per write. Decryption happens in the orchestrator process, in memory, at the moment of use. The plain value never hits disk and never leaves the process. Secrets are scoped to a company, not global. Two companies on the same install cannot read each other’s secrets.

Integration credentials

When you connect an integration (Slack, GitHub, Stripe, whatever), the OAuth or API-token flow ends in a call to the secret store. The resulting secret is indexed by the integration ID so the adapter can fetch it on demand. You do not touch the encrypted table by hand. The integrations page at Company → Integrations is the only normal way to create, view the status of, or delete an integration credential. “View” shows metadata (created at, last used at, scopes) but never the value.

Agent tokens

Each agent also gets its own identity token, used when the agent’s adapter talks to the MCP server. Agent tokens are stored the same way: encrypted with the master key, indexed by agent ID, rotated on demand. Agent tokens are scoped: a token issued for agent A cannot be used to read memory or call tools on behalf of agent B. The MCP server checks the token on every call.

Rotation

Rotating an integration credential

From the dashboard: Integrations → (integration) → Rotate. This kicks off the provider’s rotation flow (a new OAuth refresh, or prompting you to paste a new API key) and swaps the stored value. Any in-flight runs continue to use the old value until they finish; new runs use the new value. From the CLI:
company-agents integration rotate --id <integration-id>

Rotating an agent token

company-agents agent rotate-token --id <agent-id>
In-flight runs are unaffected (they carry their own lease and identity). The next run uses the new token.

Rotating the master key

This is the most sensitive rotation and the one you want to get right. The procedure:
company-agents key rotate
This:
  1. Generates a new 32-byte key
  2. Decrypts every row in the secrets table with the old key
  3. Re-encrypts every row with the new key
  4. Writes the new key to the configured source
  5. Marks the old key as archived (kept for a grace period so backups taken before the rotation can still be restored)
During rotation, the orchestrator is read-only for writes to the secrets table, which usually means a handful of seconds. Normal operations continue. After rotation, delete the archived old key once you have verified that all recent backups have been re-encrypted or superseded.

Secrets in backups

Encrypted secrets are included in backups. The master key is not automatically included. This is on purpose: a backup that includes both the encrypted data and the key is only as secure as the place you put the backup. If you want self-contained backups, you have two options:
  • Export with --with-master-key --passphrase <passphrase>, which re-encrypts the master key with a passphrase you provide. Restores require the passphrase.
  • Back up the master key separately, to a different location, with a different access control, so that losing the backup archive does not mean losing the secrets.
We recommend the second option.

What is not encrypted

The database itself (tables other than secrets) is not encrypted by Company Agents. Task contents, comments, memory, cost entries, and audit events are stored in plain text. If you need at-rest encryption for the whole database, enable it at the Postgres layer (Postgres transparent data encryption, or full-disk encryption underneath). The application layer does not try to double-encrypt.

Next