TILLSECRETS · TILLDEV

Config, versioned.
Secrets, sealed.

One encrypted store for the values your app boots with. Versioned config you can pull into any environment and roll back in one command, and short-lived credentials leased on demand and revoked on expiry. Every value is envelope-encrypted — Postgres only ever holds ciphertext.

LIVE · sealing + leasing
aes-256-gcm · per-org data key · versioned · audited · dynamic-lease
WHAT’S INSIDE

One store, two kinds of secret.

Static configGA

Versioned config values scoped org → project → environment → secret → version. Pull them into any app at boot, or write them from the CLI.

› Read the docs
Dynamic secretsGA

Short-lived credentials leased on demand and auto-revoked on expiry: Postgres, MySQL and Redis users, AWS credentials, your own endpoint, API keys, SSH key pairs and TillForge tokens.

› Read the docs
Secretless brokerBeta

Let an AI agent USE a secret it never learns. It works with a secretref:// handle; the broker materialises the real value only at the egress boundary, with your consent, against an allow-listed host — and audits every use.

› Read the docs
Presigned callsBeta

Approve one exact request now; a machine runs it later with a single-purpose token. A droplet can delete itself without ever holding your cloud token, and the token can be bound to that droplet’s address and host key. With a dead-man timer it deletes itself even if the job hangs.

› Read the docs
Secret requestsBeta

Ask a person for a value without a chat or a terminal in between. They open a link, confirm a code and paste it into the vault; your workflow gets a handle. A vendor without an account can send one through a single-use drop link, held until you accept it.

› Read the docs
Agent tokensBeta

Give an AI agent its own token: it spends secrets inside the refs you pick, under a daily budget, and can never reveal one or consent for itself.

› Read the docs
MCP serversBeta

Give Claude, Cursor or VS Code their API keys without a plain-text config. The config names the secret; the server gets the value when it starts.

› Read the docs
SSH agentBeta

Your SSH key stays in the vault. ssh, git and your AI tools sign through the agent, and each use waits for your passkey or is allowed for a while.

› Read the docs
ApprovalsBeta

Revealing a value or running a command with secrets waits for your passkey, or a tap on your phone matched to the approval page. An agent on your login can ask, and carries on the moment you approve; only you can approve. Trust a terminal for a day and everyday secrets stop asking.

› Read the docs
Sealed secretsBeta

Seal a secret nobody should read. Apps and the broker keep using it; no one can reveal it, owners included, unless two owners agree.

› Read the docs
Spend anomaliesBeta

Each agent is compared with its own last two weeks. A new host, a new secret, a burst of spends or refusals, or an hour it is never awake reaches you within a minute, by email or through a TillPulse alert rule.

› Read the docs
CanariesBeta

Decoy AWS keys, Postgres connection strings and Stripe keys that sit among the real ones. Nothing should use them, so the first use, in TillDev or against AWS or the database host, alerts every owner and admin.

› Read the docs
Leaked valuesBeta

A push, a commit or a TillPulse event carrying one of your vault values is caught by value, whatever it looks like: the push is refused, the event redacted, the admins told. A generated secret can rotate itself.

› Read the docs
Post-quantum keysBeta

Make ML-DSA, SLH-DSA, ML-KEM and X-Wing key pairs, or a hybrid ML-DSA-65 + Ed25519 signing key, in the vault. You get the public key and its fingerprint, in the PEM forms OpenSSL reads.

› Read the docs
Encrypted deliveryBeta

Pulls and leases come back encrypted to a post-quantum key inside TLS. Bind a token to a host key and a copy of it anywhere else gets nothing, and your admins are told.

› Read the docs
Workload identityBeta

CI jobs, cloud functions, pods and servers read secrets as what they are: a GitHub, GitLab, Bitbucket, CircleCI, Buildkite, Semaphore, HCP Terraform or Spacelift run, a Google, Azure, AWS or Kubernetes identity, a Fly.io, Vercel, Deno Deploy or Modal app, or a host key. Nothing is stored; each sign-in gets a short-lived token.

› Read the docs
Workload SVIDsBeta

A workload identity proves who it is to your own services with a short-lived JWT-SVID, or an X.509-SVID for mutual TLS. Your workspace is its own SPIFFE trust domain with a public bundle, and its signing key and CA never leave the vault.

› Read the docs
Crypto inventoryBeta

See which suite protects each store in your workspace and rotate its data key in one step. A public inventory, also a CycloneDX CBOM, says what protects what in every product, classical parts included.

› Read the docs
Sensitivity tiersGA

Mark one secret restricted: only a token pinned to its environment pulls it, only the owner reveals it, and CI gets it on protected branches only.

› Read the docs
Envelope encryptionGA

Every value is AES-256-GCM encrypted under a per-org data key that is itself wrapped by a master KEK. The database only ever sees ciphertext.

› Read the docs
Versioning & rollbackGA

Every write is a new version. See the history of a value, diff environments, and roll a bad deploy back to the last-known-good in one command.

› Read the docs
Revoke on deleteGA

Delete or rotate a secret that holds a TillDev token, and revoke that token where it was issued in the same step. A deleted token stops working.

› Read the docs
Sync targetsGA

Push an environment’s values to your own platform — Cloudflare, Vercel, Railway, GitHub, or AWS SSM. Vendor-agnostic, no lock-in.

› Read the docs
Audited readsGA

Reveal and pull are audited. Plaintext is assembled only in memory on an authorized read, and every one lands in the shared TillDev audit log.

› Read the docs
THE SHAPE OF IT

Config that lives. Secrets that don’t.

Most apps need both: long-lived config values you set once and read at boot, and credentials you’d rather never store at all. TillSecrets keeps both under one roof, one token, one audit log.

STATIC CONFIG · DOPPLER SHAPE

Values you set and read

Versioned, envelope-encrypted config scoped org → project → environment → secret → version.

Pulled into any app at boot with the SDK or the CLI — or synced to your own platform.

Every write is a new version, so rollback is a command, not an incident.

DYNAMIC SECRETS · VAULT SHAPE

Credentials you never keep

Short-lived credentials leased on demand from an upstream backend, auto-revoked on expiry.

Postgres, MySQL and Redis users, AWS credentials, API keys, SSH key pairs and TillForge tokens, each minted per lease.

TillSecrets stores a lease reference, never the credential itself.

THE CONFIG LAYER

Pull it in at boot.

Config is scoped org → project → environment — dev, staging, prod — then down to each secret and each version. load() pulls the right environment into process.env, decrypted in memory, at startup.

Every write is a new version. When a deploy ships a bad value, roll the secret back to the last-known-good with tilldev secrets rollback — no redeploy, no digging through a chat log for the old value.

Apps authenticate with a service token (ts_…) — bearer-only, stored hashed, scoped to one project and environment.

Config & versioning guide →
ts · boot.ts
// Boot: pull this environment's config into process.env.
import { load } from '@tillstack/secrets-node'

// Reads the service token (ts_…) from the environment, fetches
// this project + environment, decrypts each value in memory, and
// injects it into process.env — nothing is written to disk.
await load()

console.log(process.env.STRIPE_KEY) // set, decrypted, in-memory only
OR STRAIGHT FROM THE CLI
bash · terminal
# Set a value in prod — versioned + envelope-encrypted server-side.
tilldev secrets set STRIPE_KEY --env prod

# Pull the whole environment (to .env, or straight into your shell).
tilldev secrets pull --env prod

# Ship a bad value? Roll it back to the previous version.
tilldev secrets rollback STRIPE_KEY --env prod
CIPHERTEXT, NOT SECRETS

The database never sees
a plaintext value.

Every value is AES-256-GCM encrypted under a per-org data key, and that data key is itself wrapped by a master KEK — envelope encryption. Postgres only ever holds ciphertext; a database dump is a wall of noise.

Plaintext is assembled only in memory, on an authorized read, and every reveal and pull is written to the audit log. You always know what was read, by which token, and when.

THE SECURITY MODEL
Envelope-encrypted

AES-256-GCM under a per-org data key, wrapped by a master KEK.

Ciphertext at rest

Postgres stores only encrypted bytes — never a readable value.

In-memory only

Plaintext is assembled on an authorized read and never persisted.

Bearer tokens, hashed

Service tokens are ts_… — bearer-only, stored as hashes, scoped and revocable.

Every read audited

Reveal and pull land in the shared TillDev audit log, with the acting token.

SSRF-guarded upstreams

Dynamic backends are reached through SSRF guards — no pivot to internal metadata.

NO LOCK-IN

Or push it to your own platform.

You don’t have to read config through TillSecrets at runtime. A sync target pushes an environment’s values into the platform you already deploy on, so the values live where your app expects them. TillSecrets stays the source of truth; the sync is one-way and vendor-agnostic.

Cloudflare
Workers + Pages secrets
Vercel
Project env vars
Railway
Service variables
GitHub
Actions secrets
AWS SSM
Parameter Store

Set a value once in TillSecrets, sync it out to any of these — no rewriting the same secret into five dashboards, and no proprietary runtime to depend on.

THE DYNAMIC LAYER

Credentials that expire.

The safest secret is the one that doesn’t exist yet. A dynamic secret is leased on demand from an upstream backend, valid for a short TTL, and auto-revoked upstream when it expires. A leaked lease is a dead lease in minutes.

Ask a Postgres, MySQL or Redis backend for a user and get one scoped to your grants, or ask AWS for a role session or a throwaway IAM user. TillSecrets can also mint API keys, SSH key pairs and TillForge tokens, or call your own endpoint with a signed request, so a CI job can push for an hour and hold nothing after.

TillSecrets stores only a lease reference — never the credential — and reaches the upstream through SSRF guards. A drop the upstream doesn’t confirm is retried until it goes through.

Dynamic secrets guide →
ts · report.ts
// Lease a short-lived Postgres role on demand.
import { createClient } from '@tillstack/secrets-node'

const { credential } = await createClient().lease({ secretKey: 'REPORTING_DB', ttlSeconds: 900 })
// credential.username / credential.password are valid for 15 minutes,
// then revoked upstream automatically.

const db = connect({ user: credential.username, password: credential.password })
// TillSecrets kept a lease reference — never the credential itself.
THE AI-ERA LAYERBETA

Use a secret without knowing it.

Handing a key to an AI agent used to mean pasting the plaintext where the agent can read it — so it lands in the model’s context, the transcript, and every log. The broker inverts that. The agent works with a stable secretref:// handle; the real value is materialised only at the outbound egress boundary — with your consent, against an allow-listed host — and never returns to the agent.

Consent, not custody

The first spend of a reference against a host is an OAuth-style approval — once, this session, or TTL-boxed. After that the agent spends it repeatedly, no re-paste. Revoke any time; it takes effect immediately.

Anti-phishing by construction

Each secret carries the only hosts it may ever reach, and optionally the methods and paths — enforced above consent, so a tricked or over-eager agent still can’t redirect a key to an attacker’s endpoint. Every destination is SSRF-guarded.

Spent from your machine

Run the broker on the local-agent bridge and the value is injected into the outbound bytes on your own host — the plaintext never enters the model, the chat, or a log. Every spend is audited; never the value.

Approve now, run later

Presign one exact call and hand a machine a token that can run only that call, a bounded number of times, while you still have access. Bind it to the machine’s address and key, and a leaked token is useless anywhere else. Or put it on a timer: TillDev runs it at a set time, or when the machine stops checking in. Nothing it holds can read the secret.

Secretless broker guide →
AGENTsecretref://…/STRIPE_SECRET_KEYworks with a handle
api.stripe.com•••• value injectedonly the outbound bytes
THE BROKER · FAIL-CLOSEDvalue never returns ⊘
01ssrf02resolve03entitle04allowlist05consent06claim07inject08scrub

resolve → entitlement → allowlist → consent → claim → inject → response-scrub → audit. The plaintext lives only for the microseconds of the request, and is re-tokenised out of any echo before the agent sees it.

NOT A SILO

Same workspace. Same login.

TillSecrets isn’t a separate vault to run — it’s the config-and-credentials layer of the TillDev workspace you already have, with the same org, the same members and roles, and the same audit log.

01 · RECORDS

One audit log

Every reveal, pull, version, sync, and lease writes to the shared TillDev audit log, next to your Pulse, Auth, and Shield events — one history, not five.

02 · SHIPS WITH

The same shell

One login, one bill, one set of members and roles. Add TillSecrets to a workspace that already runs TillPulse and it inherits the org and billing you already have.

03 · FEEDS

The rest of the family

The config your apps boot with and the credentials they lease live where the rest of TillDev can see the audit trail — secrets aren’t a blind spot in your workspace.

TURN IT ON

Get your config out of a chat thread and your credentials out of a static string. Versioned, encrypted, and audited — one workspace away.

PART OF TILLDEV

One of seven.

You don't have to use the rest. But they fit together — same workspace, same audit log, same shortcut to switch between them. Add what you need when you need it.