Skip to content

Commit d383e58

Browse files
committed
docs: rewrite README — vault + form + MCP, nothing aspirational
1 parent 0ff7a65 commit d383e58

1 file changed

Lines changed: 97 additions & 124 deletions

File tree

README.md

Lines changed: 97 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -2,189 +2,162 @@
22

33
[![tests](https://github.com/daslabhq/kern/actions/workflows/test.yml/badge.svg)](https://github.com/daslabhq/kern/actions/workflows/test.yml)
44

5-
The agent credential manager. Your agents use APIs without ever seeing the keys.
5+
Encrypted secrets in git with a TypeScript SDK. Agents manage your credentials — you just approve.
66

77
```bash
8-
# store a credential
9-
kern secret add GITHUB_TOKEN
10-
11-
# start the proxy — agents connect via MCP
12-
kern proxy
8+
npm install kern
139
```
1410

15-
An AI agent in Claude Code, Cursor, or any MCP client calls `kern_fetch("https://api.github.com/user/repos")`. Kern matches the URL to a stored credential, injects the token, makes the request, returns the response. **The credential never enters the LLM's context.**
16-
17-
## The problem
11+
## 30-second setup
1812

19-
Today you paste API keys into `.env` files and hope the LLM doesn't leak them. It does — GitGuardian found 29 million hardcoded secrets in public GitHub in 2025. AI-assisted commits leak at double the base rate.
20-
21-
Even if you're careful, every `process.env.GITHUB_TOKEN` in your agent's code means the raw credential flows through the LLM's context window. One prompt injection and it's gone.
22-
23-
## How kern solves it
13+
```bash
14+
kern identity init # create your age keypair
15+
mkdir secrets
16+
kern identity pubkey >> secrets/.recipients
17+
kern secret add GITHUB_TOKEN # paste into terminal
18+
```
2419

25-
Kern sits between your agent and the APIs it calls. Credentials are encrypted in an age vault. The proxy injects them at request time. The agent sees results, never keys.
20+
Your secret is age-encrypted, committed to git, useless to anyone not in `.recipients`.
2621

27-
```
28-
Agent: "fetch my GitHub repos"
29-
30-
kern_fetch("https://api.github.com/user/repos")
31-
32-
kern matches api.github.com → GITHUB_TOKEN
33-
kern injects: Authorization: Bearer ghp_...
34-
kern makes the HTTP request
35-
36-
Agent gets: [{ "name": "my-repo", ... }]
37-
(no token in the response, no token in context)
38-
```
22+
## Use from code
3923

40-
### Configuration
24+
```typescript
25+
import { loadIdentityFromHost, openVault } from "kern";
4126

42-
```yaml
43-
# kern.yaml
44-
credentials:
45-
- secret: github_token
46-
match: "https://api.github.com/**"
47-
header: "Authorization: Bearer {value}"
48-
49-
- secret: openai_key
50-
match: "https://api.openai.com/**"
51-
header: "Authorization: Bearer {value}"
52-
53-
- secret: slack_token
54-
match: "https://slack.com/api/**"
55-
header: "Authorization: Bearer {value}"
56-
57-
- secret: postgres_url
58-
match: "postgres://**"
59-
connection: true
27+
const vault = openVault({ identity: await loadIdentityFromHost() });
28+
const token = await vault.get("github_token");
6029
```
6130

62-
No per-API code. kern matches URLs and injects headers. Add a new service in one line.
31+
One dependency. Works on Bun, Node 20+, Deno.
6332

64-
## MCP server
33+
## Use from Claude Code
6534

66-
Add kern to Claude Code, Cursor, or any MCP client:
35+
Add kern as an MCP server:
6736

6837
```json
6938
{
7039
"mcpServers": {
7140
"kern": {
7241
"command": "npx",
73-
"args": ["kern", "proxy"]
42+
"args": ["kern", "mcp"]
7443
}
7544
}
7645
}
7746
```
7847

79-
Kern exposes two tools:
48+
Then:
8049

81-
- **`kern_services`** — list available services (names + URL patterns, no credentials)
82-
- **`kern_fetch`** — make an authenticated HTTP request (kern injects the credential)
50+
```
51+
You: "Add my GitHub and OpenAI keys to this project"
8352
84-
The agent sees what services are available, makes requests through kern, and gets results. Zero credentials in the conversation.
53+
Claude → kern_add("github_token")
54+
→ browser opens kern's local form
55+
→ you paste the token there (not in chat)
56+
→ kern encrypts it into the vault
57+
→ "github_token added ✓"
8558
86-
## Vault
59+
Claude → kern_add("openai_key")
60+
→ same flow
61+
→ "openai_key added ✓"
8762
88-
Credentials are stored in an [age](https://age-encryption.org/)-encrypted vault committed to git. Useless without the private key. One environment variable (`KERN_AGE_KEY`) unlocks everything in production.
63+
You: "What secrets do we have?"
8964
90-
```
91-
secrets/
92-
├── .recipients who can decrypt (age public keys)
93-
├── github_token.age encrypted, safe to commit
94-
├── openai_key.age
95-
└── slack_token.age
65+
Claude → kern_list
66+
→ "2 secrets: github_token, openai_key"
9667
```
9768

98-
### CLI
69+
The credential goes: your browser → kern's local server → encrypted vault. The LLM never sees it. This uses MCP's URL-mode elicitation — the standard way to handle sensitive input.
9970

100-
```bash
101-
kern identity init # create an age keypair
102-
kern identity pubkey # print your public key
71+
## Local form server
10372

104-
kern secret add GITHUB_TOKEN # encrypt and store
105-
kern secret list # show available secrets
106-
kern secret rotate SLACK_TOKEN # replace a value
107-
kern secret rewrap # re-encrypt for current recipients
108-
```
73+
For adding secrets through a browser form (standalone or via MCP):
10974

110-
### SDK
75+
```bash
76+
kern serve
77+
# → http://localhost:9271
78+
# → open /add?name=GITHUB_TOKEN to add a secret
79+
```
11180

112-
```ts
113-
import { loadIdentityFromHost, openVault } from "kern";
81+
Slick dark form, paste your credential, click encrypt. Done.
11482

115-
const id = await loadIdentityFromHost();
116-
const vault = openVault({ identity: id });
83+
## CI — one secret unlocks everything
11784

118-
const key = await vault.get("github_token");
85+
```yaml
86+
# GitHub Actions
87+
env:
88+
KERN_AGE_KEY: ${{ secrets.KERN_AGE_KEY }}
11989

120-
vault.list();
121-
await vault.put("new_key", "value");
122-
await vault.rewrap();
90+
steps:
91+
- run: |
92+
TOKEN=$(npx kern secret get github_token)
12393
```
12494
125-
## Why not SOPS / Vault / 1Password?
95+
One env var in CI. One `.recipients` entry for your CI's public key. Every secret available.
12696

127-
SOPS encrypts secrets but has no proxy — the agent still sees the credential at runtime.
97+
## Team — add a member in one command
12898

129-
HashiCorp Vault has a proxy but requires a server.
99+
```bash
100+
# Andre generates his keypair
101+
kern identity init
102+
kern identity pubkey # → age1abc...
130103
131-
1Password has agent support but it's SaaS — your credentials live on someone else's infrastructure.
104+
# You add him
105+
echo "age1abc..." >> secrets/.recipients
106+
kern secret rewrap # re-encrypts everything for the new set
107+
git commit -am "add Andre"
132108
133-
Kern is the only tool that does all three: encrypts secrets in git, proxies API calls without exposing credentials, and runs locally with zero infrastructure.
109+
# Andre clones, everything works
110+
```
134111

135-
| | SOPS | Vault | 1Password | kern |
136-
|---|---|---|---|---|
137-
| Encrypted in git | yes | no | no | yes |
138-
| No server required | yes | no | no | yes |
139-
| Credential proxy | no | yes | yes | yes |
140-
| TypeScript SDK | no | no | no | yes |
141-
| Runs locally | yes | no | no | yes |
142-
| Open source | yes | yes | no | yes |
112+
Remove someone: delete their key from `.recipients`, rewrap, rotate any secrets they had.
143113

144-
## How it works under the hood
114+
## CLI
145115

146-
1. **Identity** — every machine gets an age keypair (`kern identity init`). The public key goes in `.recipients`. Anyone in the list can decrypt.
116+
```bash
117+
kern identity init # create age keypair
118+
kern identity pubkey # print public key
119+
120+
kern secret add NAME # encrypt and store
121+
kern secret get NAME # decrypt to stdout
122+
kern secret list # show names (no values)
123+
kern secret rotate NAME # replace a value
124+
kern secret delete NAME # remove
125+
kern secret rewrap # re-encrypt for current recipients
147126
148-
2. **Vault** — secrets are age-encrypted files. `kern secret add` encrypts to all recipients. Committed to git, auditable in history, useless to non-recipients.
127+
kern mcp # start MCP server (Claude Code, Cursor)
128+
kern serve # start local form server
129+
```
149130

150-
3. **Proxy**`kern proxy` starts a local MCP server. When an agent calls `kern_fetch(url)`, kern:
151-
- Matches the URL against `kern.yaml` patterns
152-
- Decrypts the matching credential from the vault
153-
- Injects it as an HTTP header
154-
- Makes the request
155-
- Returns the response (credential stripped)
131+
## How it works
156132

157-
4. **Signing** (remote mode) — when the vault and proxy are on different machines, kern signs authorization claims instead of proxying directly. The remote executor verifies the signature and injects the credential. Same age keypair, same trust model.
133+
Secrets are [age](https://age-encryption.org/)-encrypted files in a `secrets/` directory. Each file is one secret. A `.recipients` file lists the age public keys that can decrypt.
134+
135+
```
136+
secrets/
137+
├── .recipients age1... (one per line)
138+
├── github_token.age
139+
├── openai_key.age
140+
└── prod/
141+
├── .recipients narrower set
142+
└── database_url.age
143+
```
144+
145+
Standard age v1 format. Interoperable with the `age` CLI, `rage`, any age library.
158146
159147
## Environment
160148
161149
| Variable | Purpose | Default |
162150
|---|---|---|
163-
| `KERN_AGE_KEY` | age private key for decryption | `~/.kern/key` |
164-
| `KERN_VAULT_DIR` | vault root directory | `./secrets` |
165-
166-
One env var in production. Local dev reads from `~/.kern/key`.
167-
168-
## Install
169-
170-
```bash
171-
npm install kern
172-
```
173-
174-
One dependency (`age-encryption`). ESM-only. Works on Bun, Node 20+, Deno.
151+
| `KERN_AGE_KEY` | age private key | `~/.kern/key` |
152+
| `KERN_VAULT_DIR` | vault directory | `./secrets` |
175153
176-
## Roadmap
154+
## Why kern
177155
178-
- **Audit ledger** — append-only log of every proxied request
179-
- **Approval flow** — push notification before high-risk API calls
180-
- **Remote signing** — kern on your phone signs, executor in the cloud acts
181-
- **OS Keychain** — store the age key in macOS Keychain / Windows Credential Manager
156+
SOPS does encrypted secrets in git, but it's a Go CLI — you shell out to it. No TypeScript SDK, no MCP server, no local form.
182157
183-
## Testing
158+
Vault and 1Password have SDKs, but they need a server or SaaS account.
184159
185-
```bash
186-
bun test ./test/smoke.ts
187-
```
160+
kern is the only tool that does encrypted-secrets-in-git with a native TypeScript SDK and agent integration. 424 lines of vault code, one dependency, MIT.
188161
189162
## License
190163

0 commit comments

Comments
 (0)