Encrypted config. Credentials that expire.
GATillSecrets is where your configuration and secrets live — encrypted at rest under a key only your org holds, versioned so every change is reversible, and pulled at boot over one authenticated call. It carries two kinds of secret under one model: static config (a value you set and version) and dynamic secrets (a short-lived credential leased on demand and auto-revoked on expiry). Same workspace, same login, same audit log as the rest of TillDev.
Two kinds of secret, one model
Every secret is a KEY — env-var-style, like STRIPE_KEY or DATABASE_URL — that lives in one environment. What differs is its kind:
| Kind | What it holds | How you read it |
|---|---|---|
| Static config | A value you set. Stored as an immutable, envelope-encrypted version; changing it allocates a new version and rollback re-points to an older one. | Returned in a pull, decrypted. |
| Dynamic secret | A reference to a backend (Postgres today). No value is stored — a fresh, short-lived credential is minted on demand and auto-revoked on expiry. | Leased from the backend service, once. |
Both sit in the same hierarchy: your org owns projects, a project has environments (dev / staging / prod), and an environment holds the keys. A service token is scoped to a project — and optionally pinned to one environment — so a prod token can never read dev.
org
└─ project "acme-api"
└─ environment dev · staging · prod
└─ KEY STRIPE_KEY, DATABASE_URL, …
├─ static → immutable versions (v1, v2, v3 …) + a "current" pointer
└─ dynamic → a backend reference; credentials are leased, never storedPull the environment at boot
Apps don’t ship secrets in their bundle or a .env file — they pull the environment at startup with a ts_… service token. On Node the one-liner injects everything into process.env:
// The very top of your server entry, before you read any config.
import { load } from '@tilldev/secrets-node'
// Reads TILLSECRETS_TOKEN (+ optional TILLSECRETS_URL / TILLSECRETS_ENV),
// pulls the environment, and injects each KEY into process.env.
await load()
// process.env.STRIPE_KEY is now populated — nothing was committed to disk.Under the hood every SDK does the same thing: a single POST /api/secrets/pull that returns the decrypted { secrets, dynamic } map for the token’s environment. The three packages — @tilldev/secrets-node, @tilldev/secrets-edge, and the @tilldev/secrets-core they both wrap — are covered in the config guide. Start from the Quickstart for the full loop.
Credentials that expire on their own
A dynamic secret never has a stored value. You register a backend (a Postgres database, today) once, then declare a key that references it. When an app needs a credential it leases one: TillSecrets opens a connection to your database, runs CREATE ROLE … VALID UNTIL with your grants, and hands back a username and password that stop working when the lease expires — dropped automatically. We store a reference to the lease, never the credential.
That means a leaked database password has a blast radius measured in minutes, and every app instance gets its own credential. Full walkthrough in Dynamic secrets.
Sync to your own platform
If a workload can’t call the pull API — a build step, a platform that only reads its own env vars — TillSecrets can push an environment’s values into your own platform instead. The targets are vendor-agnostic:
Cloudflare · Vercel · Railway · GitHub · AWS SSM · dotenv
Pick a target, and TillSecrets writes the environment’s current values to it — so the source of truth is still TillSecrets, but your platform sees plain env vars. No lock-in: the same environment can fan out to more than one target. See Sync targets.
Encrypted end to end
This is the whole pitch, so it’s worth stating plainly:
- Envelope encryption. Every value is
AES-256-GCMencrypted under a data key (DEK) unique to your org, which is itself wrapped by a master key (KEK). Postgres holds only ciphertext; plaintext is assembled in memory only on an authorized read. - Everything is audited. Every read, write, rollback, token mint or revoke, and lease issue or revoke is appended to a tamper-evident log.
- Tokens are bearer-only.
ts_…tokens are stored sha256-hashed, never appear in a URL, and are revocable and optionally expiring.
ts_… token can decrypt an environment. Keep it in a server-side environment variable — never in a browser bundle, an app binary, or a committed file. Prefer a read-scoped token pinned to one environment for anything an app boots with.The full model — the DEK/KEK split, the audit actions, SSRF guards on dynamic backends — is in Security.
Start with the Quickstart, then read the Config store, Dynamic secrets, and Security. TillSecrets is part of TillDev — one workspace, one login, one audit log. Prefer the pitch? See the product page.