Secretsminter
FreeNot checkedA self-hosted MCP server that mints, places, and rotates cloud secrets (e.g., Cloudflare, GitHub, Supabase) without exposing secret values to the agent, using a
About
A self-hosted MCP server that mints, places, and rotates cloud secrets (e.g., Cloudflare, GitHub, Supabase) without exposing secret values to the agent, using a signed destination allow-list and self-maintaining rotation.
README
You're probably as sick as I am of drilling around in the web UIs of Cloudflare, GitHub, and Supabase — hunting for tokens and configuring security in a UI that changes as often as Katy Perry changes clothes.
Secret minting made easy. Mint new secrets, manage tokens, manage rotation — and do it all from the comfort of your very own, self-hosted MCP server. Simply point your AI at this repo, tell it to read it, and have it follow the instructions.
And please — do a deep security audit, post your findings, PR to the repo, make suggestions, help make it better. Say goodbye to token and secret headaches.
A value-blind secrets-minter that stands up self-maintaining secret rotation — and then gets out of the way.
secretsminter lets an AI agent (or a pipeline) provision cloud secrets — mint them, place them where
they're consumed, and wire their recurring rotation — over the Model Context Protocol (MCP),
without any secret value ever entering the agent's context, the chat, or a log line.
Start here → docs/USING.md — the ordered, honest first-run guide (addressed to the AI doing the setup, readable by a human over its shoulder): clone → build → scaffold the signed manifest → provide ephemeral-first bootstraps → register the MCP server → first
plan/ approvedmint_and_place.
Quickstart (≈60 seconds)
The copy-paste happy path from a clean clone to your agent running its first (dry-run) plan. No
hand-rolled launcher — secretsminter init prints the exact registration line. Examples are generic;
substitute your own org/project (your-org, example-web, acme).
# 1. Clone + build (green is the gate).
git clone https://github.com/scorellis/secretsminter && cd secretsminter
npm ci && npm run build
# 2. Scaffold the trust root: an Ed25519 keypair + a signed, empty (default-deny) manifest.
# Writes the SECRETSMINTER_* paths into ./.secretsminter/.env AND prints the `claude mcp add` line for step 5.
node packages/daemon/dist/cli.js init --dir ./.secretsminter
# 3. Allow one destination (re-signs the manifest). Nothing off this list can ever be placed.
node packages/daemon/dist/cli.js allow --dir ./.secretsminter \
--store cloudflare-pages --project example-web --env production --name "R2_*"
# 4. Add your provider bootstraps to ./.secretsminter/.env (init already wrote the SECRETSMINTER_* paths
# there; git-ignored — secret VALUES enter here, never in the agent chat). See docs/USING.md §4.
# 5. Register the server with your MCP client (paste the exact line `init` printed):
claude mcp add secretsminter -- node /ABS/PATH/packages/daemon/dist/cli.js --env-file /ABS/PATH/.secretsminter/.env
From a clone the CLI isn't on your PATH, so commands use
node packages/daemon/dist/cli.js. Prefer a shortsecretsminter? Runnpm linkonce, then drop the prefix. Theclaude mcp addlineinitprints uses an absolutenode …/cli.js, so registration needs no global install regardless.
6. Reload/restart your MCP client — servers load at startup, so it will not connect until you do.
7. Ask your agent to run plan for the destination you allow-listed — a dry-run that mints nothing
and confirms the manifest check. Full walkthrough, per-provider bootstraps, and troubleshooting:
docs/USING.md (§5 covers registration + "it's not connecting"). Point at the
secretsminter daemon bin, not the demo secretsminter-mcp, which refuses every mutation by design.
Philosophy: builders, not maintainers. The acceptance test for every feature is "does it eliminate ongoing maintenance, or create it?" A rotation that needs a human or an AI to remember to run it has already failed. secretsminter's job is to stand the loop up once and let it run itself. Every place a human could touch a secret is tracked in docs/MANUAL-STEPS.md, and each eliminated step is backed by a test — so manual toil can't creep back in.
Why this exists
Most secret-management pain isn't cryptography — it's toil. A token set by hand, once, in a place nobody wrote down; a rotation everyone means to automate and never does; a credential pasted into a chat and now needing to be rolled. secretsminter turns "provision + keep fresh" into a primitive an agent can call:
- Mint a fresh cloud credential (e.g. a Cloudflare R2 API token).
- Place it where it's consumed (e.g. a GitHub Actions secret), value-blind.
- Rotate it on a schedule the broker owns — or, better, mint a short-lived credential that self-expires so there's nothing to rotate at all.
Set it up once, then walk away — the secret stays fresh with no one tending it. The mint → place → verify → rotate loop is built and runs as a daemon. Live-run status, precisely: Cloudflare R2 temporary-credential minting is live-verified (2026-07-31, proven with a signed S3 HEAD) and Cloudflare Pages env-var placement has had a live run (2026-08-05); GitHub and Supabase are real, complete code paths that are unit-tested against fakes but have NOT yet been run against the live vendor (their live runs need your credentials). See the story ledger for the per-provider state.
You run it yourself. secretsminter is your own small broker — a Docker container or service in your environment, seeded once with your cloud bootstrap keys and a signed policy file. Because it holds your crown-jewel credentials, it is deliberately not a hosted service and stores no tokens: nothing you'd have to trust a third party with. The payoff is that the whole "rotate the token, then update it in four places, did I miss one?" class of toil disappears — your AI agent asks the broker to provision, and the broker mints, places, and keeps everything fresh, without ever handing a value to the agent. See Run it (Docker) below, and docs/USING.md for the full operator walkthrough.
The two invariants (read these before anything else)
secretsminter's security rests on two invariants, and the second is the load-bearing one:
Value-blind (necessary). The broker mints and places secrets but never returns a value to the caller. Tools return only non-secret confirmations like
{ ok, provider, action, secret_id, placed_at }. This stops a prompt-injected agent from reading a secret. It is enforced as a mechanism — positive-whitelist serialization at the tool boundary, a secret-scrubber on every error and log path, stdin-only secret transit, and a mint-only tool surface — not as a hopeful convention. (OWASP LLM06.)Destination allow-list + signed manifest (the real trust root). Value-blindness does nothing against the more dangerous move: a prompt-injected agent directing a live secret to an attacker — "mint a token and place it into
attacker/evil's secrets" exfiltrates a working credential the agent never sees. So the true containment is a server-side allow-list of destinations, declared in a signed, agent-un-writable manifest. Placements must match the manifest; anything else is refused, default-deny. Adding a destination is an out-of-band, human-approved change. (OWASP LLM08, Excessive Agency.) Scope note (honest): the manifest pins destinations; a request's scopes are recorded but NOT yet enforced at the providers (a minted credential today carries the bootstrap's scope). Provider-side scope enforcement is a roadmap item (story 0015).
If you take one thing from this README: value-blindness is not the security model. The signed destination allow-list is. The two-tier design (below) adds a module boundary that contains a prompt-injected agent; making it a true process/trust boundary is on the roadmap.
Architecture (v1)
- Two-tier design (a module boundary today). The agent-facing MCP server (
@secretsminter/mcp) holds no master keys directly. It validates, checks the manifest, and requests actions from an internal minting core (@secretsminter/core) that holds the keys and independently re-enforces the allow-list and approval. Because the MCP tier can only invoke the registered tools — and every one of them transits the core's gates — a prompt-injected agent is contained: it can only ask for what the manifest already permits. Honest scope: today both tiers run in one process (daemon), so this is a module boundary, not a process/trust boundary. It contains a prompt-injected agent; it does not by itself contain code-execution/RCE of the MCP tier, which would sit in the same process as the keys. Splitting the core into its own process/Worker is the roadmap item that makes it a real trust boundary (story 0014, ADR-0009). - Narrow, pre-defined tools.
list_secrets,status,plan(dry-run, the default),mint_and_place,rotate,revoke. There is no generic "call this API" tool, and no tool accepts a caller-supplied secret value. The kill switch and approval mechanism are deliberately not tools — they live out-of-band, where a prompt-injected agent can't reach them. - Providers vs. stores. A
Providermints/rotates/revokes/verifies at a vendor; aStoreplaces a minted secret at a destination. v1: Cloudflare, GitHub, Supabase. - Self-maintaining rotation. An explicit, persisted, resumable state machine
(
PENDING → MINTED → PLACED → VERIFIED → REVOKED → DONE) in which the old credential is retained until the new one passes a functional health probe — revoke is structurally unreachable before verify.NEVER_ROTATE(by name and class) protects keys like an updater signing key whose public half is baked into shipped builds; a self-lockout guard stops the broker rotating its own hands off.
See docs/adrs/ for the full decision record and SECURITY.md for the threat model.
Run your own secretsminter (Docker)
secretsminter runs as a container you host — it holds your bootstrap keys, so it lives in your trust boundary (a small always-on box: a cheap VM, a home server, or alongside an existing service). It stores no tokens; you inject the bootstraps at runtime.
1. Configure. Put your bootstraps + config in a git-ignored .env (full recipe:
docs/USING.md). Only the vendors you set are enabled:
# --- a signed, agent-un-writable manifest = the trust root (which destinations are allowed) ---
SECRETSMINTER_MANIFEST=/etc/secretsminter/manifest.json
SECRETSMINTER_MANIFEST_SIG=/etc/secretsminter/manifest.sig
SECRETSMINTER_MANIFEST_PUBKEY=/etc/secretsminter/manifest.pub
# --- provider bootstraps (set only what you use) ---
SECRETSMINTER_CF_ACCOUNT_ID=... ; SECRETSMINTER_CF_BUCKET=... ; SECRETSMINTER_CF_PARENT_ACCESS_KEY_ID=... ; SECRETSMINTER_CF_API_TOKEN=...
SECRETSMINTER_GH_APP_ID=... ; SECRETSMINTER_GH_INSTALLATION_ID=... ; SECRETSMINTER_GH_PRIVATE_KEY="-----BEGIN...\n...\n-----END..."
SECRETSMINTER_SB_PROJECT_REF=... ; SECRETSMINTER_SB_PAT=...
# --- out-of-band controls + durable state (all optional) ---
SECRETSMINTER_APPROVALS=/var/lib/secretsminter/approvals.json # unset ⇒ deny-all (nothing mutating runs)
SECRETSMINTER_KILL_SWITCH=/var/lib/secretsminter/kill # create this file to halt everything
2. Run it as an always-on service (rotation loop keeps your secrets fresh 24/7):
docker compose up -d # state persists on a named volume; restarts on reboot
3. Point an AI agent at its MCP tools — run it interactively over stdio and add it to your MCP client (e.g. Claude Code) config:
// mcp client config
{ "mcpServers": { "secretsminter": { "command": "docker", "args": ["run", "-i", "--rm", "--env-file", ".env", "secretsminter"] } } }
The agent then has list_secrets, status, plan, mint_and_place, rotate, revoke — and manages
a target repo's secrets (e.g. your-org/your-app), never secretsminter's own. In production, inject
the bootstraps from your platform's secret manager rather than a .env file.
Interface note: today the MCP is served over stdio (the agent launches the container). A network (HTTP) transport for a remote always-on MCP is a roadmap item (ADR-0013). The rotation loop runs persistently regardless.
Status — honest
The security core, the MCP server, the sinks, the scheduler, durable state, the daemon, and all three providers are built. Live-run status, precisely: Cloudflare R2 temp-credential minting is live-verified (signed S3 HEAD, 2026-07-31) and Cloudflare Pages env placement has had a live run (2026-08-05); GitHub and Supabase are complete code paths, unit-tested against fakes, but not yet run against the live vendor. It is not yet independently security-reviewed — don't trust it with standing production credentials until it is.
| Piece | Status |
|---|---|
The security core (@secretsminter/core) — value-blind + SecretValue, allow-list, signed manifest, NEVER_ROTATE, rotation state machine, broker pipeline |
built — part of a 172-test suite (adversarial suite + local coverage gate) |
The live MCP server (@secretsminter/mcp) — six narrow tools wired to the broker |
built + integration-tested |
| Out-of-band approval + fail-closed kill switch · audit + alert sinks + drift | built |
| Self-maintaining rotation loop + scheduler · durable state (survives restart) | built |
The daemon (@secretsminter/daemon) — composition root + rotation loop + MCP serving |
built |
| Cloudflare provider (R2 temp creds) | built + LIVE-VERIFIED (0003) |
| Cloudflare Pages env-var store | built + unit-tested; one live env-write run (0013; redeploy-and-reconcile pending) |
| GitHub provider + Actions store (sealed-box) · Supabase provider | built + unit-tested against fakes; NOT yet run against the live vendor (0004, 0005) |
No vaporware: a capability that isn't built says so, in code (NotWiredError) and in its story
(docs/stories/). Visibility: private during hardening; opens for public review
once independently security-reviewed.
Develop
npm install
npm run build # tsc --build (all packages, strict)
npm test # vitest
npm run test:coverage # + the coverage gate (runs locally — the repo has no CI of its own)
secretsminter deploys in isolation: it stores no tokens and has no GitHub Actions of its own — see AGENTS.md.
We want your scrutiny
This is a security tool. The intent is to open it so it can be attacked in review before it is trusted in production — it is private for now, while the P0 controls are hardened, and will be made public once they are built and verified. The standing stance holds for anyone with access today: if you are a human or an AI reading this repo, please see AI_REVIEW.md — we actively invite deep review, threat models, and PRs, and we ask that you not rely on any feature marked at-risk or not-yet-wired.
Prior art & where we differ
secretsminter is an incremental wedge in a dense field, not a first mover. Be clear-eyed about the neighbors:
- HashiCorp Vault — the mature standard for dynamic secrets (short-lived, generated on demand) and now an official Vault MCP server. Vault is vastly more battle-tested, has a real policy engine, broad backend coverage, HA, and audit devices we do not.
- Infisical — the closest in spirit: it already does secret mint + sync to many destinations (including an agent story), with a hosted product, RBAC, and a large integration catalog we lack.
- CB4A — the IETF draft
draft-hartman-credential-broker-4-agents, which frames the broker/agent separation we mirror. It is a spec direction, not a competing product. agentsecrets/mcp-secrets-vault— MCP-native, zero-knowledge-style vaults that deliberately expose noget(); the nearest twins to our value-blind stance.
Our wedge: value-blind mint + place + rotate exposed as a single, agent-callable provisioning primitive, gated by a signed destination allow-list — the agent can stand a credential up where it is consumed and keep it fresh, but can never read it and can never direct it off the allow-list. The "place" step (writing into a GitHub Actions secret or a Cloudflare Pages env var) as a first-class, allow-list-gated action is where we differ from a pure dynamic-secrets engine.
Where they are stronger: every tool above is more mature, more broadly integrated, and independently reviewed. Vault and Infisical are production systems; secretsminter is an early, not-yet-independently-reviewed project. Choose it for the specific agent-provisioning shape above, not as a Vault/Infisical replacement.
License
Apache-2.0 — permissive, with an explicit patent grant. See docs/adrs/0007-apache-2-and-ip-posture.md for the licensing + intellectual-property rationale.
Installing Secretsminter
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/scorellis/secretsminterFAQ
Is Secretsminter MCP free?
Yes, Secretsminter MCP is free — one-click install via Unyly at no cost.
Does Secretsminter need an API key?
No, Secretsminter runs without API keys or environment variables.
Is Secretsminter hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Secretsminter in Claude Desktop, Claude Code or Cursor?
Open Secretsminter on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
GitHub
PRs, issues, code search, CI status
by GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
by mcpdotdirectCompare Secretsminter with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All development MCPs
