API Keys and Secrets, Explained for Beginners
In short
An API key, a token or a database connection string is a secret: whoever knows it acts on your behalf and spends your money. There is exactly one rule — a secret never goes into code, config files or git. A leaked key gets found by bots in a public repo within minutes, while rotating it takes a minute. The engine bakes this in structurally: only placeholders like ${GITHUB_MCP_PAT} end up in the package, and the real values live in environment variables on your own machine (usually a .env file listed in .gitignore). A leaked secret is treated as compromised — you revoke it and issue a new one, and the code stays untouched. This article covers the types of secrets, a worked example of connecting GitHub, and five habits worth building early.
What it is
Many of the tools an agent connects to want proof that it is really you: GitHub, a database, Sentry, a payment provider. That proof is a secret — a password-like string the service issued specifically to you. The kinds you will run into:
- API key / token — a long string like
sk_live_...orghp_.... Anyone who knows it acts as you. - Database connection string (
postgresql://user:password@host/db) — the password is embedded inside, so the whole string is a secret. - OAuth — instead of a key, you log in through the browser once and the service hands the agent temporary access (often safer: no permanent key is ever created).
The rule is short and simple: a secret is like your front door key. Don't write it on the door. The "door" here is your code, config files, logs, screenshots — and above all, anything that goes into git.
Why be this strict
A leaked key is a compromised key. Once a key lands in a committed file, bots find it in a public repo within minutes and start spending your money (inference, cloud) or reading your data. That's why the engine holds a hard invariant: "a secret never lands in a file as a value; a leaked secret is treated as compromised — rotate it." And this principle isn't just written down in the rules — it's built into how the compiler assembles the package.
Placeholder versus value — the core idea
A placeholder is a stand-in name like ${DATABASE_URI_RO} or ${GITHUB_MCP_PAT}. It says "the real value gets substituted here on your machine," but it contains no value itself. The engine spreads only placeholders through the package. The real value you supply separately — in environment variables on your own computer (usually a .env file that sits in .gitignore and never travels anywhere).
An analogy: the engine package is a form that says "write your password here," not the password itself. You can show that form around, forward it, commit it — there's no password in it.
Worked example: connecting GitHub
Input. You want the agent to work with your repositories, so you enable the github MCP server. It needs a GitHub personal access token.
What the engine does:
- An entry appears in the spec's list of required secrets (
secretsManifest) — only the nameGITHUB_MCP_PAT, a "required" flag, and a link to the docs page where you create the token. - At build time,
.mcp.jsongets a header with a placeholder, not with the token:
"headers": { "Authorization": "Bearer ${GITHUB_MCP_PAT}" }
- In the README and INSTALL, the engine prints a human-readable line: "
GITHUB_MCP_PAT(required) — GitHub personal access token with the right scopes (repo/read)." Values: nowhere.
What you do (once, on your side):
- Go to GitHub → Settings → create a token with minimal permissions (for instance,
repo:readonly, if the agent just needs to read — that's the "principle of least privilege"). - Put it in your environment:
export GITHUB_MCP_PAT=ghp_your_token(or as a line in a local.env).
Result. Through ${GITHUB_MCP_PAT} the agent substitutes your token from the environment and works with the repositories. Meanwhile, the files you commit or forward contain only ${GITHUB_MCP_PAT} — the token physically never entered the artifact. And if the token does get exposed one day, you go to GitHub, revoke the old one, create a new one; no code changes, the placeholder is the same.
Five habits worth building early
- Secrets go in the environment or
.env, never in code. And make sure.envis in.gitignore. - Least privilege. If read access is all you need, don't hand over a token that can write or delete.
- Read-only wherever possible. For databases the engine defaults to read-only mode and a separate role — the agent can't break anything.
- OAuth beats a permanent key. Many presets (Vercel, Atlassian, Notion, Canva) authorize in the browser, so no permanent key is ever created.
- Exposed means rotate, immediately. Revoking and reissuing is a one-minute job; the fallout from a leaked key is not.
An honest boundary
The engine package is just plain text files on your disk: they can be opened and read, and that's fine — there are no third-party secrets in them, only your placeholders. The automatic "no secret leaked into the diff" check before handoff is prescribed by the engine as a rule (secrets-hygiene, mode always); enforcing it hard, by exit code, currently works only in Claude Code via a hook — in other environments it's a strict instruction.