Secrets
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
iniVEEFORE_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
`.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
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
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.
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.
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.
#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.
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
#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
- Rotate in Veefore.
- Revoke at the source. Veefore rotating does not tell your provider to stop accepting the old value.
- Work out the exposure from the lease records.
- Open an incident with the window.
- Close the route it leaked through.
#Checklist
- [ ] All four platform keys replaced from
.env.exampledefaults - [ ] 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.envgitignored - [ ] Agent keys generated locally;
public_keysupplied at registration - [ ] No agent holds a long-lived downstream key — all in the broker
- [ ]
sensitive_parametersset on every tool that handles PII - [ ]
npm run quality:auditin CI - [ ] Rotation on a schedule for agent keys and credentials