---
title: Secrets
description: Every place a secret could surface, and the specific mechanism that stops it.
---

Four categories of secret exist in a Veefore deployment. Each has a different
containment story.

| | What it is | Who holds it |
| --- | --- | --- |
| Platform keys | Data encryption, signing, session, audit chain | The deployment's environment |
| Agent private keys | One Ed25519 key per agent | The agent's own host |
| Credential material | Your downstream secrets | The credential broker |
| Human credentials | Passwords, session tokens, API keys | People and your secret store |

## Platform keys

```ini
VEEFORE_DATA_ENCRYPTION_KEY=…    # seals credential material
VEEFORE_SIGNING_PRIVATE_KEY=…    # signs agent passports
VEEFORE_SESSION_SECRET=…         # signs human sessions
VEEFORE_AUDIT_CHAIN_SECRET=…     # keys the audit hash chain
```

:::danger `.env.example` ships development keys. They are not secret.
They exist so the stack boots out of the box, and they are shared with everyone
who has ever cloned the repository. Replace all four before anything that
matters.
:::

Inject them from a secret manager, not from a file. And give each process only
what it needs:

| | Control plane | Gateway | Worker |
| --- | --- | --- | --- |
| `VEEFORE_DATA_ENCRYPTION_KEY` | yes | yes | yes |
| `VEEFORE_SIGNING_PRIVATE_KEY` | yes | yes | — |
| `VEEFORE_SESSION_SECRET` | yes | **no** | — |
| `VEEFORE_AUDIT_CHAIN_SECRET` | yes | yes | yes |

The gateway never issues a human session, so it does not need the session
secret. A secret a process does not need is a secret that process should not
have — it is one more place a compromise can read it from.

### Rotation

| Key | Consequence |
| --- | --- |
| `VEEFORE_SESSION_SECRET` | Everyone re-authenticates. Safe |
| `VEEFORE_SIGNING_PRIVATE_KEY` | Issued passports stop verifying. Issue new ones, let partners pick up the new published key, retire the old |
| `VEEFORE_DATA_ENCRYPTION_KEY` | Every credential must be re-sealed. Plan as a migration |
| `VEEFORE_AUDIT_CHAIN_SECRET` | Existing chain becomes unverifiable. Export and archive first |

The last two are projects, not config changes.

## Agent private keys

Generated **locally** by `veefore init` and never sent to Veefore. Only the
public half crosses the network, so there is no moment at which Veefore could
have logged, cached or leaked the private one.

Written to `.env.local` at mode `0600`, between managed markers:

```ini
# Veefore MANAGED BEGIN — do not edit between the markers
VEEFORE_AGENT_PRIVATE_KEY=MC4CAQAwBQYDK2VwBCIEI…
# Veefore MANAGED END
```

:::security The key is written once and is not recoverable
Veefore stores the public key and nothing else. Lose `.env.local` and you do not
recover the key — you rotate to a new one with
`veefore agents rotate-key`, which also invalidates every session the old key
had open.

`veefore doctor` checks the file permissions. A key readable by another process
on the host is a key that process can act with, and that is a real finding.
:::

If you omit `public_key` at registration, Veefore generates the pair and returns
the private half **once**. It is not stored and not recoverable. Use that path
only when the agent runs somewhere that cannot generate a key.

## Credential material

The agent never holds it. [→ Credential broker](/concepts/credentials)

Five independent mechanisms keep it contained:

**1. No endpoint returns it.** `GET /v1/credentials` returns metadata only —
key, name, type, provider, status, last rotated, lease count. The repository
layer does not select the sealed column into API responses.

**2. Redaction on ingest.** Credential-shaped values, private keys, session
tokens and anything listed in a tool's `sensitive_parameters` are redacted
before a log line is constructed.

:::security There is no code path where a secret is written and later filtered
Redaction happens on the way in, not on the way out. Which means log shipping,
log retention and log backups cannot leak what was never written.

An output filter would be strictly weaker: it only protects the paths it covers,
and a new log statement is a new path.
:::

**3. Audit by reference.** Credential leases are recorded by id, never by value.
Masked parameter paths are masked in audit too, so a `MASK` decision redacts the
value in the trail as well as in the forwarded request.

**4. Error messages never contain secret values.** Nor key material, internal
hostnames, or stack detail. When you need to know *why* a decision went a
certain way, read the action record — behind authentication.

**5. The quality audit.** `npm run quality:audit` fails the build on a committed
secret: private key blocks, high-entropy assignments to secret-named variables,
and recognisable provider key formats. The CLI smoke test additionally asserts
that no command prints a secret.

### Get the material keys right

Each credential type reads specific keys and discards the rest.

:::danger This used to fail two layers from its cause
Put the secret under a name the broker does not read and Veefore would seal it
happily, permit the action happily, broker nothing usable, and your downstream
would return `401`. Nothing in the Veefore logs looked wrong, because from
Veefore's point of view nothing was.

Now refused at registration and rotation with
`INVALID_CREDENTIAL_REFERENCE`. `BASIC_AUTH`, `MTLS_CERTIFICATE` and
`SIGNING_KEY` are exempt because their material is legitimately multi-field.

[Full table](/build/broker-a-credential)
:::

## Human credentials

**Passwords** are never stored in plaintext and never logged. The CLI reads them
without echoing and stores only the resulting session token, in
`~/.veefore/credentials.json` at mode `0600`.

**Session tokens** have a TTL and an idle timeout. High-impact operations need
step-up regardless of session age, so a stolen session cannot revoke an agent.

**API keys** are shown exactly once; only a hash is stored. The field is
`token`, not `secret`, and there is no endpoint that returns it again.

:::warning Step-up permissions are unavailable to API keys
Step-up requires a human re-authenticating, and an API key has no human. A key
holding `policy:activate` still cannot activate a policy.

Which means **CI cannot activate a policy on its own** — deliberate, because
activation moves the authorization boundary for every agent in scope. It is a
decision, not a build step. [→ Gate your pipeline](/build/ci-gate)
:::

## Files that contain secrets

| File | Mode | Gitignored |
| --- | --- | --- |
| `.env` | yours to set | yes |
| `.env.local` | `0600` | yes |
| `~/.veefore/credentials.json` | `0600` | n/a |
| `.veefore-real.env` | `0600` | yes |
| `.veefore-demo-keys.env` | `0600` | yes |
| `veefore.config.json` | — | **no, and that is correct** |

`veefore.config.json` holds the agent id, the key id and the URLs. No secrets.
Commit it.

## If a secret leaks

1. **Rotate in Veefore.**
2. **Revoke at the source.** Veefore rotating does not tell your provider to stop accepting the old value.
3. Work out the exposure from the lease records.
4. Open an incident with the window.
5. Close the route it leaked through.

[→ Credential leak runbook](/operate/runbook-credential-leak)

## Checklist

- [ ] All four platform keys replaced from `.env.example` defaults
- [ ] Platform keys injected from a secret manager, not a file
- [ ] Each process has only the keys it needs
- [ ] All four backed up alongside the database, and a restore tested
- [ ] `.env`, `.env.local`, `.veefore-real.env` gitignored
- [ ] Agent keys generated locally; `public_key` supplied at registration
- [ ] No agent holds a long-lived downstream key — all in the broker
- [ ] `sensitive_parameters` set on every tool that handles PII
- [ ] `npm run quality:audit` in CI
- [ ] Rotation on a schedule for agent keys and credentials

## Related

:::cards

:::card Credential broker | /concepts/credentials
The model and its guarantees.
:::

:::card Broker a credential | /build/broker-a-credential
Moving a key out of an agent.
:::

:::card Configuration | /operate/configuration
The four keys, and rotating them.
:::

:::card Credential leak | /operate/runbook-credential-leak
When one gets out.
:::

:::
