Start here
Secrets with tvault
Inject only the credentials each process needs, without copying them into the repository, temporary files, or logs.
Reviewed 2026-07-23
Chalupa separates configuration from secrets. chalupa.yml, .env.example,
and the runbooks describe names and contracts; tvault stores encrypted
values. A process receives a secret only while running the command that needs
it.
Secret map
| Key | Consumer | Scope |
|---|---|---|
DIGITALOCEAN_API_TOKEN |
Local Pulumi tasks | Operator-requested provisioning and destruction. It never reaches the droplet or control plane. |
CHALUPA_INGEST_KEY |
Agent, task report, and Chalupa |
Signs heartbeats, events, and suite runs. |
DATABASE_URL |
Chalupa | Pooled Neon/Postgres connection. |
CLOUD_ADMIN_TOKEN |
Chalupa | Creates the private administrator session. |
CRON_SECRET |
Scheduler and Chalupa | Authorizes reconciliation and retention jobs. |
DIGITALOCEAN_READ_TOKEN |
Cost reconciliation | Read-only inventory access. It cannot create or destroy resources. |
The current release uses one fleet-wide ingestion key. Treat it as a shared credential: rotating it requires updating the control plane and recreating or reconfiguring every agent.
Diagnose without reading values
Secret names are metadata. Verify availability without unlocking or printing values:
tvault doctor --project chalupa --json
tvault list --project chalupa --json --names-only
tvault status --json also reports whether the vault and local agent are
available. None of these commands returns secret values.
Create generated credentials
Send random credentials directly to tvault over stdin. Redirecting the command result keeps operational output separate from secret-management output:
openssl rand -hex 32 |
tvault set --project chalupa CLOUD_ADMIN_TOKEN --stdin >/dev/null
openssl rand -hex 32 |
tvault set --project chalupa CHALUPA_INGEST_KEY --stdin >/dev/null
openssl rand -hex 32 |
tvault set --project chalupa CRON_SECRET --stdin >/dev/null
DATABASE_URL and DIGITALOCEAN_READ_TOKEN come from their providers. Send
them through stdin or import them from a controlled file. Do not place them in
command arguments when your shell records history.
Run with least privilege
Repository tasks already pass through fixed profiles in
src/cli/with-tvault.ts:
| Workflow | Profile boundary |
|---|---|
task data-up, task up, task down, task data-nuke |
Provisioning token, plus the ingest key only when Chalupa reporting needs it. |
task report |
Ingest key only. |
task cloud:db:migrate |
Database URL only. |
task cloud:staging-check |
The five control-plane runtime credentials, never the provisioning token. |
Do not invoke the internal underscore-prefixed lifecycle tasks directly; the public tasks establish the credential boundary.
For backward compatibility, only the provisioning profile can read
DIGITALOCEAN_API_TOKEN from ~/.config/secrets/env after tvault is missing
or cannot return that key. The parser treats the file as data and never
evaluates shell syntax. Database, admin, ingest, cron, and read-only provider
credentials never use this fallback. Move the provisioning key into
tvault and treat the file as a temporary migration path.
Prefer tvault run --only to exporting an entire project. Values exist only in
the subprocess environment:
tvault run \
--project chalupa \
--only DATABASE_URL,CLOUD_ADMIN_TOKEN,CHALUPA_INGEST_KEY,CRON_SECRET,DIGITALOCEAN_READ_TOKEN \
-- bun run dev
Run that command from cloud/. For a local tour without a database, set
CHALUPA_DATA_MODE=demo and inject only CLOUD_ADMIN_TOKEN.
Do not use eval $(tvault env) as the normal workflow. It expands both the
lifetime and scope of credentials in your shell. Do not persist tvault env
output in .env.local.
tvault:// templates
When a command needs to rename a key, a committed template may contain a reference, never the value:
DIGITALOCEAN_TOKEN=${tvault://chalupa/DIGITALOCEAN_API_TOKEN}
tvault run --env-file <template> -- <command> resolves the reference at
runtime. Its dotenv parser does not evaluate shell substitutions or commands.
Deliver secrets to the platform
A deployment receives the same keys through the platform's encrypted secret store. tvault can remain the local source of truth, but the platform keeps its own encrypted copy. Use a stdin-based workflow and confirm key names, not values.
After configuring staging:
- confirm that all five control-plane runtime credentials are present;
- run the database migration before accepting traffic;
- check readiness without returning configuration details;
- sign and submit one synthetic ingestion request;
- record who performed the synchronization and when.
Never reuse DIGITALOCEAN_API_TOKEN as the control plane's read-only token.
The control plane must receive the narrowest provider scope available.
Rotation and revocation
Rotate one credential at a time and keep a recovery path:
- create the new version in tvault;
- update the remote consumer;
- verify readiness and the affected workflow;
- restart or replace agents when the HMAC key changes;
- revoke the previous value at the provider;
- inspect metadata with
tvault history --project chalupa <KEY>.
Rotating CLOUD_ADMIN_TOKEN invalidates existing sessions. Rotating
CRON_SECRET requires updating the scheduler. Rotating the read-only token
must not affect the local provisioning token.
Rules for agents
- Use
tvault list --names-onlyfor discovery; do not loop overget. - Inject values into a trusted subprocess with an explicit allowlist.
- Never surface stdout from a command that might echo its environment.
- Do not write secrets to
/tmp, artifacts, screenshots, or messages. - Do not unlock and relock the vault around every operation.
- Stop when creating, rotating, or synchronizing a value needs new authority.