zerocreds
v0.3.0
Published
ZeroCreds — MCP server for credential isolation in LLM agents. Bots use passwords without seeing them.
Downloads
89
Maintainers
Readme
ZeroCreds
MCP server for credential isolation in LLM agents. Your bot uses passwords and API keys — but never sees them.
Without ZeroCreds With ZeroCreds
User → "password: MyP@ss!" → LLM User → ●●●●●● → ZeroCreds → encrypted disk
LLM → vault_login("jira") → ZeroCreds → Chrome
LLM now has your password LLM sees only { status: "ok" }
in context, logs, history Password never enters LLM contextWhy
AI agents are getting real access to real systems. They log into websites, call APIs, manage infrastructure. The standard pattern is dangerous:
"Here's my Stripe key: sk-live-abc123, please check my charges"That API key is now in the LLM's context window, conversation logs, provider's training pipeline (maybe), and any tool that reads the conversation. One leaked prompt — and your credentials are exposed.
ZeroCreds solves this with a simple principle: the agent operates credentials, but never sees them.
┌─────────────────────────────────────────────────────────────┐
│ │
│ Credential lifecycle │
│ │
│ User types password │
│ │ │
│ ▼ │
│ ┌───────────┐ AES-256-GCM ┌───────────────┐ │
│ │ Browser │ ──────────────────► │ Encrypted │ │
│ │ form │ │ store (.json) │ │
│ └───────────┘ └───────┬───────┘ │
│ │ │
│ LLM calls vault_login() │ │
│ │ │ │
│ ▼ ▼ │
│ ┌───────────┐ ┌───────────┐ │
│ │ ZeroCreds │ ◄────── │ Decrypt │ │
│ │ server │ │ in memory │ │
│ └─────┬─────┘ └───────────┘ │
│ │ │
│ ▼ │
│ ┌───────────┐ │
│ │ Chrome │ Fill form via CDP │
│ │ DevTools │ Password in browser only │
│ └─────┬─────┘ │
│ │ │
│ ▼ │
│ { status: "ok" } ◄── only this goes │
│ back to LLM │
│ │
└─────────────────────────────────────────────────────────────┘How It Works
5 MCP tools. The agent calls them like any tool. The difference — credential data never appears in the response.
| Tool | Agent calls | Agent sees | What actually happens |
|------|------------|------------|----------------------|
| vault_add | vault_add("jira") | { status, site_id } | Browser form opens, user enters password, encrypted to disk |
| vault_login | vault_login("jira") | { status, page_title } | Decrypt, fill login form via Chrome CDP, clear password field |
| vault_api_request | vault_api_request("stripe", url) | { status, body } | Decrypt API key, inject into headers, sanitize response |
| vault_list | vault_list() | [{ siteId, type }] | List credential metadata — no secrets |
| vault_status | vault_status("jira") | { active, lastUsed } | Metadata + audit count — no secrets |
Scenarios
1. First-time Login
The agent needs credentials it doesn't have. It asks the user to add them via a secure form — then logs in via Chrome.
User AI Agent ZeroCreds Chrome
│ │ │ │
│ "Log me into │ │ │
│ Jira" │ │ │
├───────────────────►│ │ │
│ │── vault_list() ────►│ │
│ │◄── [] (empty) ─────┤ │
│ │ │ │
│ │── vault_add ───────►│ │
│ │ ("jira") │ │
│ │ │ │
│ ┌────────────────────────────┐ │ │
│ │ Browser form opens │ │ │
│ │ localhost:9900/add │ │ │
│ │ │ │ │
│ │ Email: [[email protected]] │ │ │
│ │ Password: [●●●●●●●●●●] │ │ │
│ │ URL: [jira.com] │ │ │
│ │ │ │ │
│ │ [Add to Vault] │ │ │
│ └────────────┬───────────────┘ │ │
│ │ │ │
│ └── POST ────────────────►│── encrypt │
│ │── save to disk │
│ │◄─ { status: ok } ───┤ │
│ │ │ │
│ │ Password is NOT │ │
│ │ in this response │ │
│ │ │ │
│ │── vault_login ─────►│── decrypt ──┐ │
│ │ ("jira") │◄────────────┘ │
│ │ │── fill email ────►│
│ │ │── fill pass ────►│
│ │ │── click submit ──►│
│ │ │── clear pass ───►│
│ │ │◄─ page loaded ───┤
│ │◄─ { status: ok, ───┤ │
│ │ title: "Jira" } │ │
│ │ │ │
│◄── "You're logged │ │ │
│ into Jira!" │ │ │2. API Key Proxy
The agent makes API calls. ZeroCreds injects the key into headers and scrubs it from the response.
AI Agent ZeroCreds Stripe API
│ │ │
│── vault_api_request ►│ │
│ service: "stripe" │── decrypt API key │
│ url: "/v1/charges" │ │
│ │── GET /v1/charges ────────►│
│ │ Authorization: │
│ │ Bearer sk-live-**** │
│ │◄── { data: [...] } ───────┤
│ │ │
│ │── scan response for │
│ │ leaked key │
│ │ (replace with ***) │
│ │ │
│◄── { status: ok, ───┤ │
│ body: "..." } │ │
│ │ │
│ API key NOT in │ │
│ this response │ │3. Returning User
Credentials already stored — the agent goes straight to login:
User AI Agent ZeroCreds Chrome
│ │ │ │
│ "Open GitHub" │ │ │
├───────────────────►│ │ │
│ │── vault_list() ────►│ │
│ │◄─ [{ siteId: ─┤ │
│ │ "github", │ │
│ │ active: true }] │ │
│ │ │ │
│ │── vault_login ─────►│── decrypt ───┐ │
│ │ ("github") │◄─────────────┘ │
│ │ │── CDP login ─────►│
│ │ │◄─ success ───────┤
│ │◄─ { status: ok } ───┤ │
│ │ │ │
│◄── "Done!" │ │ │4. Instant Revocation
Remove access — the agent can no longer use the credential:
Admin (CLI) ZeroCreds AI Agent
│ │ │
│── zerocreds remove │ │
│ "jira" │── delete + audit log │
│◄── "Removed" │ │
│ │ │
│ │ ... later ... │
│ │ │
│ │◄── vault_login ──────┤
│ │ ("jira") │
│ │── { FAIL: ──►│
│ │ "not found" } │5. Tamper-Proof Audit Trail
Every credential use is logged with a SHA-256 hash chain:
~/.zerocreds/audit.jsonl
┌─────────────────────────────────────────────────────────────┐
│ evt_001 │ credential.created │ jira │ success │ hash_1 │
│ │ │ │ │ │ │
│ evt_002 │ credential.used │ jira │ success │ ▼ │
│ │ bot: claude │ │ prevHash: hash_1 │
│ │ │ │ │ hash_2 │
│ │ │ │ │ │ │
│ evt_003 │ credential.used │ jira │ success │ ▼ │
│ │ bot: claude │ │ prevHash: hash_2 │
│ │ │ │ │ hash_3 │
└─────────────────────────────────────────────────────────────┘
Tamper with any entry → chain breaks → detected
$ zerocreds audit
Chain integrity: VALID (3 entries)6. Web Chat Integration
For browser-based AI chats, the credential form appears inline — no new tab:
Web Chat (browser) ZeroCreds (server)
┌──────────────────────┐ ┌──────────────────┐
│ import { VaultUI } │◄══ WS ═════►│ WebSocket :9901 │
│ from 'zerocreds/web'│ │ │
│ │ │ AI agent calls │
│ ┌────────────────┐ │◄── event ───│ vault_add() │
│ │ Vault │ │ │ │
│ │ Password: ●●●● │ │ │ │
│ │ [Grant Access] │ │── POST ────►│ encrypt + save │
│ └────────────────┘ │ │ │
└──────────────────────┘ └──────────────────┘
Password goes via HTTP POST (not WebSocket).
WebSocket is only for signaling "credential needed" / "credential saved".Quickstart
# Clone and build
git clone https://github.com/Chill-AI-Space/vault-mcp.git
cd vault-mcp && npm install && npm run build
# Register with Claude Code
claude mcp add -s user zerocreds -- node ~/vault-mcp/dist/index.js
# Use: "Log me into GitHub"
# → vault_add("github") → browser form → you enter password
# → vault_login("github") → Chrome logs in
# → Agent sees { status: "ok" } — never the passwordOr add credentials via CLI:
zerocreds add --site github --email [email protected] --url https://github.com/login
# Password is prompted interactively (masked with *)Web SDK (for browser-based AI chats)
import { VaultUI } from 'zerocreds/web';
const vault = new VaultUI({
vaultUrl: 'http://localhost:9900', // default
wsUrl: 'ws://localhost:9901', // default
onRequest: (siteId) => console.log(`Credential needed: ${siteId}`),
onSaved: (siteId) => console.log(`Saved: ${siteId}`),
});
vault.connect();
// When AI agent calls vault_add() → modal appears in your page
// No new browser tab. Password submitted via HTTP POST to vault.Also works as a script tag:
<script type="module">
import { VaultUI } from './node_modules/zerocreds/dist/web/vault-ui.js';
new VaultUI().connect();
</script>Bundle size: ~6KB, zero runtime dependencies, Shadow DOM (no CSS conflicts).
CLI
zerocreds add # Interactive credential entry
zerocreds list # List credentials (no secrets)
zerocreds remove <site_id> # Remove credential
zerocreds audit [site_id] # Audit log + chain integrity
zerocreds dashboard # Web dashboard on localhost:9900Architecture
zerocreds/
├── src/
│ ├── index.ts ── CLI or MCP mode (auto-detect)
│ ├── server.ts ── MCP server with 5 tools
│ ├── cli.ts ── CLI commands
│ ├── tools/
│ │ ├── vault-add.ts ── Browser form OR WebSocket modal
│ │ ├── vault-login.ts ── Decrypt → Chrome CDP → fill form
│ │ ├── vault-api.ts ── Decrypt → inject headers → sanitize
│ │ ├── vault-list.ts ── Metadata only
│ │ └── vault-status.ts ── Metadata + audit stats
│ ├── ws/
│ │ └── server.ts ── WebSocket server (127.0.0.1:9901)
│ ├── web/
│ │ ├── vault-ui.ts ── Web SDK (WS client + modal)
│ │ └── modal.ts ── Shadow DOM modal (dark theme)
│ ├── store/
│ │ ├── encrypted-store.ts ── AES-256-GCM, unique IV per credential
│ │ └── keychain.ts ── Master key management
│ ├── browser/
│ │ └── cdp-bridge.ts ── Playwright CDP bridge
│ ├── audit/
│ │ └── logger.ts ── JSONL + SHA-256 hash chain
│ └── dashboard/
│ ├── server.ts ── HTTP + WS bootstrap
│ ├── index.html ── Dashboard UI
│ └── add.html ── Credential entry form
├── dist/web/
│ └── vault-ui.js ── Bundled Web SDK (~6KB)
└── test/ ── 36 testsConfiguration
| Variable | Default | Description |
|----------|---------|-------------|
| ZEROCREDS_MASTER_KEY | auto-generated | Encryption key (scrypt → 32 bytes). VAULT_MASTER_KEY also accepted for backward compatibility. |
| ZEROCREDS_CDP_URL | http://localhost:9222 | Chrome DevTools endpoint. VAULT_CDP_URL also accepted. |
Storage
~/.zerocreds/
├── credentials.json ── AES-256-GCM encrypted (unique IV per entry)
├── audit.jsonl ── Append-only, SHA-256 hash chain
└── .master-key ── Master key (mode 0600, auto-generated)On first run, if ~/.vault-mcp/ exists and ~/.zerocreds/ does not, the data directory is automatically migrated.
Security
Protects against Does NOT protect against
───────────────── ────────────────────────
✓ LLM context leakage ✗ Compromised host (root access)
✓ Plaintext credential storage ✗ Malicious MCP client
✓ Audit log tampering ✗ Browser-level memory attacks
✓ Accidental exposure in logs ✗ Network MITM to target sitesSee SECURITY.md for full threat model.
Testing
npm test # 36 tests
npm run test:watchLicense
MIT
