Agents fill the form. They never see the password.
Threat model · Roadmap · Issues · License
agent.call("ap_fill_login", { credential_id: "..." })
↓
AgentPwd decrypts in memory → injects into the browser via CDP / AppleScript
↓
agent.receives({ status: "success" }) ← the agent never sees the plaintext
You give your AI agent browser access. Sooner or later it needs to log into something.
A normal password manager hands the secret over — and now it's in the LLM's context window. Logged, cached, potentially leaked into telemetry. One prompt injection away from being exfiltrated.
AgentPwd is built differently. Every credential operation is shaped do something with the credential — fill a form, inject as an env var, generate a TOTP. The agent gets back {status: "success"}. Plaintext flows from the encrypted vault to the browser through the AgentPwd process, never through the LLM.
Think of it as 1Password's op run, except the fill itself stays out of the context — not just the env var.
git clone https://github.com/uvollmer/agentpwd && cd agentpwd
npm install && npm run build
# Create your default vault — master key goes into the OS keychain
node dist/cli/index.js init
# Add a credential interactively (hidden password input)
node dist/cli/index.js add --site github.com --username me@example.com
# Or auto-generate a strong one
node dist/cli/index.js add --site aws.amazon.com --username admin --generate
# Inspect (metadata only)
node dist/cli/index.js listWire the MCP server into your agent. For Claude Code, drop this into .mcp.json:
{
"mcpServers": {
"agentpwd": {
"command": "node",
"args": ["/path/to/agentpwd/dist/mcp/server.js"]
}
}
}Then ask your agent to log in somewhere. It'll call ap_fill_login(credential_id="...") and the form fills — without the LLM ever seeing what got typed.
A hosted version where credentials never reach the agent's machine at all is on the roadmap.
The MCP surface. Ten tools. The "No" column is the whole point.
| Tool | Returns plaintext to the LLM? |
|---|---|
ap_create_vault |
No |
ap_list_vaults |
No |
ap_create_credential |
No |
ap_list_credentials |
No — IDs, sites, usernames |
ap_delete_credential |
No |
ap_fill_login |
No — injected straight into the browser |
ap_fill_field |
No |
ap_set_totp |
No |
ap_fill_totp |
No — code injected, not returned |
ap_run |
No — output is scrubbed for the password and common encodings |
Every fill tool accepts an optional cdp_url so agents driving remote browsers pass their endpoint directly.
Two injection paths, one abstraction.
| Target | What to do |
|---|---|
| Local Chrome on macOS (already open) | Enable View → Developer → Allow JavaScript from Apple Events. AgentPwd uses AppleScript out of the box. |
| Local Chrome anywhere | Launch with --remote-debugging-port=9222. AgentPwd auto-detects. |
| Browserbase, Anchor, Browserless | Pass their connectUrl / cdp_url as the cdp_url parameter. |
| OpenClaw managed sessions | Same — pass the CDP endpoint. |
| Browser Use, Playwright, Puppeteer | CDP under the hood; same. |
The property-descriptor + input/change event dispatch logic is identical across paths — frameworks (React, Vue, Angular) see the value update like real user input.
One-page recipes per target — concrete wiring, sample tool calls, target-specific caveats.
- Claude Code —
.mcp.jsonsetup, AppleScript vs CDP paths, optional hooks - Claude for Chrome — running alongside Anthropic's extension on your local Chrome
- OpenClaw — pass their session endpoint as
cdp_url - Browserbase — pulling
connectUrlfrom their session API - Anchor Browser — same shape with their
cdp_url - Hermes (Nous Research) — stub, help wanted
Before every fill, AgentPwd reads the page URL and matches its hostname against the credential's stored site. Mismatches are refused with an explicit error.
| Active tab | Credential | Result |
|---|---|---|
https://github.com/login |
github.com |
✅ filled |
https://login.github.com |
github.com |
✅ filled (subdomain) |
https://fake-github.com |
github.com |
❌ refused |
https://github.com.evil.com |
github.com |
❌ refused |
Read the threat model before relying on this for anything sensitive — it's the load-bearing doc.
Short version: AgentPwd defends well against the intended path leaking plaintext into the LLM. It does not, in v1, defend against a malicious agent with shell access. Such an agent can read the master key from the OS keychain directly and decrypt the vault. v2 (hosted vault + remote browser, see roadmap) closes that gap by removing the credentials from the agent's machine entirely.
Crypto: @noble/ciphers (AES-256-GCM) + @noble/hashes (Argon2id) + keytar (OS keychain).
Landing in v1:
- Share flow — a one-time link for a human to type a password they only know. Zero-knowledge: client-side Web Crypto with a per-request key, served over a cloudflared tunnel.
ap setupwizard — first-run UX: vault init, MCP config, platform guidance.- Wait-for-navigation in
ap_fill_login— shrink the post-fill DOM exposure window to near-zero by blocking onPage.frameNavigatedbefore returning. - Agent recipes — one-page guide per target: Claude Code, Claude for Chrome, OpenClaw, Hermes, Browserbase, Anchor.
Bigger work for v2, gated on traction from the v1 OSS launch:
- Hosted vault + remote browser — credentials never reach the agent's machine. The hosted service decrypts in its own memory and pushes via CDP to the remote browser (Browserbase, Anchor, etc.). A locally-compromised agent finds nothing to extract.
- Chrome extension — bridge for local Chrome under the hosted-vault model. Meaningfully better than a standalone daemon for that case.
TypeScript + Node 20+. MCP SDK. SQLite via better-sqlite3. chrome-remote-interface for CDP. Vitest for tests.
Early days. Feedback and small focused PRs welcome — for non-trivial changes, file an issue first. For security reports, email instead of opening a public issue.
MIT — see LICENSE.