DEV Community

DemoJacob
DemoJacob

Posted on

Your AI Agent Knows Your Passwords — Here's How I Fixed It

When AI agents automate your browser, they need your login credentials in their context window. That means your passwords are sent to cloud LLM providers, stored in conversation logs, and potentially exposed via prompt injection attacks.

I built Cerberus KeyRouter to fix this.

The Problem

AI agents like OpenClaw, Claude Desktop, and Cursor can automate browsers — filling forms, clicking buttons, navigating pages. But when they hit a login page, the typical flow looks like this:

User: "Log into GitHub for me"
User: "Email: jacob@example.com"
User: "Password: MyS3cretP@ss"
Agent: "Logging in..."
Enter fullscreen mode Exit fullscreen mode

That password just traveled through:

  1. The LLM provider's servers (Anthropic, OpenAI, etc.) — logged, stored, potentially used for training
  2. API proxy services — if you're using a third-party API relay (common in many regions), your password passes through yet another unknown middleman
  3. Conversation history — sitting in plain text in your chat logs

And it gets worse. Prompt injection is a real attack vector: a malicious webpage can embed hidden instructions that trick the AI into leaking credentials it learned earlier in the conversation.

The Solution: Placeholder Pattern

The core idea is simple:

The AI writes the logic using placeholders. Real credentials are substituted locally and injected directly into the browser. The LLM never sees your actual password.

Instead of passing real credentials, the AI agent sends instructions like this:

{
  "vaultItem": "GitHub",
  "steps": [
    { "action": "fill", "selector": "#login_field", "value": "{{email}}" },
    { "action": "fill", "selector": "#password", "value": "{{password}}" },
    { "action": "click", "selector": "[type=submit]" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Cerberus KeyRouter — an MCP (Model Context Protocol) server running on your machine — intercepts this call, fetches the real credentials from your local Vaultwarden instance, replaces the {{placeholders}}, and executes the actions via Chrome DevTools Protocol (CDP).

The AI sees {{password}}. Your browser gets the real password. The LLM never knows the difference.

Architecture

AI Agent (OpenClaw, Claude Desktop, etc.)
  │
  │  MCP call: secure_login("GitHub", steps with {{placeholders}})
  │
  ▼
Login Router (localhost:8899)
  ├─ Authenticate via bearer token
  ├─ Fetch credentials from Vaultwarden
  ├─ Replace {{placeholders}} with real values
  ├─ Execute via Chrome CDP (localhost only)
  ├─ Clear credentials from memory
  └─ Return { status: "ok" } — no passwords in response

Vaultwarden (Docker, localhost:8443)
  └─ E2E encrypted password storage
Enter fullscreen mode Exit fullscreen mode

Everything runs locally. Passwords never leave your machine.

Security Model: Six Layers of Defense

1. Implicit allowlist — Vaultwarden only stores credentials for sites you've explicitly added. The AI can't log into arbitrary sites.

2. URL verification — Before injecting credentials, the router reads the browser's actual URL via CDP and matches it against the vault entry's URI. Phishing sites get rejected.

3. Bearer token auth — Each Vaultwarden account gets a unique token. No token, no access.

4. Rate limiting — 3 attempts per minute, 20 per hour per vault item. Consecutive failures trigger a cooldown.

5. Audit logging — Every login attempt is recorded (success, failure, rate-limited). Passwords are never logged. View at /audit.

6. Human-in-the-loop — High-risk accounts can require manual approval before each login. 50-second timeout, auto-reject if not approved.

Quick Start

git clone https://github.com/DemoJacob/cerberus-keyrouter.git
cd cerberus-keyrouter
cp .env.example .env
# Set VW_ADMIN_TOKEN in .env
docker compose up --build -d
Enter fullscreen mode Exit fullscreen mode

This gives you:

  • Vaultwarden at https://localhost:8443 — add your passwords here
  • Login Router at http://localhost:8899 — MCP server + admin panel

Open the admin panel at http://localhost:8899/admin, add your Vaultwarden account, and copy the generated bearer token.

Connect your AI agent via MCP:

{
  "mcpServers": {
    "cerberus": {
      "baseUrl": "http://localhost:8899/mcp",
      "headers": {
        "Authorization": "Bearer <your-token>"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Done. Your agent can now log into any site in your vault — without knowing a single password.

fill vs type

Most sites work with fill (sets value + dispatches events). React/SPA sites with controlled components may need type (key-by-key input with 50ms delay). Try fill first, switch to type if the form doesn't submit properly.

Multi-Step Login

Banks and other sites often split login across multiple pages. Handle this with separate calls:

// Step 1: username + next
{ "vaultItem": "MyBank", "steps": [
    { "action": "type", "selector": "#username", "value": "{{username}}" },
    { "action": "click", "selector": "#next" },
    { "action": "wait", "selector": "input[type=password]" }
]}

// Step 2: password + submit
{ "vaultItem": "MyBank", "steps": [
    { "action": "type", "selector": "#password", "value": "{{password}}" },
    { "action": "click", "selector": "#login" }
]}
Enter fullscreen mode Exit fullscreen mode

Real-World Usage

I've been running this with OpenClaw for daily tasks. Here's an actual example — my AI agent querying order history on zooplus.de, logged in via Cerberus:

The agent reports: "I didn't touch any plaintext passwords. I only passed the placeholders {{email}} and {{password}}. Cerberus fetched the credentials from your local Vaultwarden vault and filled them directly into the browser."

No password in the LLM context. No password in the logs. No password leaving my machine.

Technical Stack

  • TypeScript / Node.js — login router
  • Playwright-core — CDP browser automation
  • Vaultwarden — self-hosted Bitwarden (password storage)
  • MCP Protocol — Streamable HTTP transport
  • SQLite — config + audit log storage
  • AES-256-GCM — stored master password encryption
  • Docker Compose — one-command deployment

What's Next

  • Cookie caching (login once, reuse sessions)
  • Support for OpenClaw snapshot ref IDs (alongside CSS selectors)
  • Multi-agent framework support
  • SaaS version with zero-knowledge E2E encrypted backend

Try It

The project is open source under AGPL-3.0:

GitHub: github.com/DemoJacob/cerberus-keyrouter

Works on macOS and Linux. Docker + Chrome + any MCP-compatible AI agent.

Feedback, issues, and stars are all welcome. If you're building AI agents that need to handle authentication, I'd love to hear about your use case.

Top comments (9)

Collapse
 
itskondrat profile image
Mykola Kondratiuk

The placeholder pattern is smart but honestly what scares me more is the stuff that happens before you even get to login flows. I've been building side projects with AI assistance and caught it dumping .env contents into debug logs, copying API keys into comments "for reference", stuff like that. The credential exposure surface is way bigger than just the login page.

The prompt injection angle is the real nightmare scenario though. If a malicious page can extract creds from context, the whole "just paste your password" workflow that most people use with AI agents is basically an open door. Nice that you're handling TOTP too - that's usually where the local-first approach falls apart because people just give up and paste the code manually.

Collapse
 
demojacob profile image
DemoJacob

Totally agree on the .env and debug log leakage — the login page is just the most visible part of a much bigger surface.

The placeholder pattern isn't limited to passwords either. API keys, OAuth tokens — anything sensitive could go through the same {{placeholder}} → local substitution pipeline. Haven't built that layer yet, but the architecture supports it.

The bigger elephant in the room though: AI agents with shell access. Once an agent can run arbitrary commands on your machine, it can read keychains, dump process memory, sniff traffic — no real way to stop it. Cerberus solves the LLM context window problem, but the shell-level trust boundary is a much harder unsolved problem for the whole industry.

On TOTP — deliberate choice. Too many local-first tools handle passwords fine then expect you to manually paste the 2FA code.

Appreciate the real-world perspective.

Collapse
 
itskondrat profile image
Mykola Kondratiuk

The shell access thing is honestly a different beast. Placeholder pattern handles the context window leak cleanly, but once an agent can run arbitrary commands - reading ~/.zsh_history, dumping process memory - there's no semantic layer that saves you. I don't think anyone in the industry has a clean answer yet. 'Properly sandboxed agent' is still a fuzzy concept.

Collapse
 
cyber8080 profile image
Cyber Safety Zone

Wow — this was an eye-opener! 🚨 It’s easy to forget that AI tools can end up storing sensitive data like passwords if we’re not careful. Really appreciate the practical steps you shared to fix it — things like clearing history, tightening permissions, and isolating credentials are exactly the kind of actionable advice developers need today. Thanks for breaking it down clearly!

Collapse
 
demojacob profile image
DemoJacob

Thank you so much — really appreciate the kind words 🙏
I also think this can go far beyond passwords: protecting other sensitive data is a very interesting next step, especially for real-world commercial use cases.

Collapse
 
chovy profile image
chovy

Great approach with the placeholder pattern. The credential isolation problem extends beyond login automation though.

The same trust model issue shows up in social media automation. Most scheduling tools (Buffer, Hootsuite, Later) require uploading platform API keys to their cloud. One breach at the provider level and every connected account is compromised.

The Cerberus architecture — keeping secrets local, resolving on-device — is the right pattern for any automation touching sensitive tokens.

For social media specifically, defpromo takes a similar local-first approach: zero-cloud browser extension that automates engagement with API keys stored entirely on your machine. Same principle as your Vaultwarden + CDP pipeline, applied to marketing automation.

The broader lesson: any time a tool says "give us your credentials," ask where they live. Someone else's server = attack surface.

Collapse
 
demojacob profile image
DemoJacob

Good point — the credential isolation pattern definitely applies beyond login. API keys for social media, payment gateways, CI/CD secrets... anything that touches third-party automation has the same trust problem.

One thing I'd add: Cerberus goes a step further than just "keeping secrets local." The LLM itself never sees the real credentials — it only writes {{password}}, and the substitution happens at a layer below the AI. So even if the model gets prompt-injected or the conversation leaks, there's nothing sensitive to extract. That's the key difference from approaches where the agent has access to credentials but stores them locally.

Thanks for the thoughtful comment!

Collapse
 
theminimalcreator profile image
Guilherme Zaia

Impressive work on the Cerberus KeyRouter! The approach to prevent LLMs from accessing sensitive credentials while allowing seamless automation is essential, especially given the current security landscape. Are you considering implementing additional layers, like multi-factor authentication, to enhance security further? 🔐

Collapse
 
demojacob profile image
DemoJacob

Thanks! MFA is actually already part of the design — we support {{totp}} as a placeholder for two-factor authentication codes, so the router can grab a TOTP code from Vaultwarden and fill it in automatically, same as passwords. The AI never sees the actual code.

On top of that, there's a human-in-the-loop mode for high-risk accounts: the router sends you a confirmation request before executing the login, with a 50-second timeout that auto-rejects if you don't approve. Combined with rate limiting (3 attempts/min, 20/hour per vault item) and full audit logging, it's meant to cover the "what if something goes wrong" scenarios too.

Would love to hear if you try it out!