Skip to main content
Glama

agent-auth

Zero-knowledge credential injection for AI agents. Your agent authenticates to websites and APIs without ever seeing a password, TOTP code, or API key.

Agent says:  fill #email with {{email}}, fill #password with {{password}}, click Sign In
agent-auth:  resolves {{email}} and {{password}} from encrypted vault, injects into browser
Agent gets:  "Login completed." (never sees the real values)

Why

AI agents are getting good at browsing the web and calling APIs. But authentication is a wall — you either hand the agent your password (dangerous) or do it yourself every time (defeats the purpose).

agent-auth sits between the agent and the browser. The agent describes what to do with placeholder tokens. agent-auth resolves the real credentials from a local encrypted vault and injects them directly into the browser via CDP. The agent never touches the secret material.

Works with any MCP-compatible agent: Claude Code, Claude Desktop, OpenCode, Cursor, or your own.

How It Works

AI Agent                        agent-auth                       Browser
   |                               |                               |
   |  "Log into AWS"               |                               |
   |  steps: [                     |                               |
   |    fill #email {{email}}      |                               |
   |    fill #pass  {{password}}   |  1. Decrypt from local vault  |
   |    fill #totp  {{totp}}       |  2. Generate TOTP from seed   |
   |    click Submit               |  3. Inject via CDP  --------->| Form filled
   |  ]                            |  4. Zero memory               |
   |                               |                               |
   |  "Login completed" <--------- |                               |
   |                               |                               |
   |  (never saw any secrets)      |  (secrets wiped from RAM)     |

Quick Start

# Clone and install
git clone https://github.com/ex-nihilo-labs/agent-auth.git
cd agent-auth && bun install

# Create your vault (you'll set a passphrase)
bun run src/index.ts init

# Add a credential
bun run src/index.ts add github \
  --username "you@example.com" \
  --password "your-password" \
  --domains "github.com"

# Add one with TOTP
bun run src/index.ts add aws-root \
  --username "admin@company.com" \
  --password "hunter2" \
  --totp "JBSWY3DPEHPK3PXP" \
  --domains "signin.aws.amazon.com,console.aws.amazon.com"

# See what's stored (names only — no secrets shown)
bun run src/index.ts list
# → github (domains: github.com)
# → aws-root (domains: signin.aws.amazon.com, console.aws.amazon.com)

Connect to Your Agent

agent-auth is an MCP server. Add it to your agent's config:

Claude Code / Claude Desktop

Add to .mcp.json in your project root (or ~/.claude/settings.json for global):

{
  "mcpServers": {
    "agent-auth": {
      "type": "stdio",
      "command": "bun",
      "args": ["run", "/path/to/agent-auth/src/index.ts", "serve"],
      "env": {
        "AGENT_AUTH_CDP_URL": "http://localhost:9222"
      }
    }
  }
}

The passphrase is read from your OS keychain automatically (stored during init). For development, you can set AGENT_AUTH_PASSPHRASE in the env block instead.

Other MCP Agents

Any agent that speaks MCP over stdio can use agent-auth. Start the server:

bun run src/index.ts serve

It exposes three tools over stdin/stdout JSONRPC.

MCP Tools

Tool

What the agent sends

What the agent gets back

secure_login

Service name + browser steps with {{placeholders}}

"Login completed" or error

auth_api

Service name + HTTP request details

API response body (credentials redacted)

list_credentials

(nothing)

Service names and allowed domains only

secure_login

Browser-based authentication. The agent describes the login flow as steps:

{
  "service": "github",
  "url": "https://github.com/login",
  "steps": [
    { "action": "fill", "selector": "#login_field", "value": "{{email}}" },
    { "action": "fill", "selector": "#password", "value": "{{password}}" },
    { "action": "click", "selector": "input[type='submit']" },
    { "action": "wait", "selector": ".logged-in", "timeout": 5000 }
  ]
}

Step actions: fill, type (character-by-character for SPAs), click, wait, select.

auth_api

Authenticated HTTP requests. Six injection methods:

{
  "service": "openai",
  "url": "https://api.openai.com/v1/models",
  "method": "GET",
  "injection": "bearer"
}

Injection methods: bearer, header, query, basic, json_body, form.

list_credentials

Returns service names and allowed domains. Never returns passwords, TOTP seeds, or API keys.

Security Model

The core guarantee: credentials never appear in MCP responses. They flow from vault to browser/HTTP and are zeroed from memory immediately after.

Layer

Implementation

Encryption at rest

AES-256-GCM, 12-byte random nonce per field

Key derivation

Argon2id (3 iterations, 64MB memory, 4 parallelism)

Master key storage

OS keychain (macOS Keychain, Linux secret-tool) with encrypted file fallback

Memory hygiene

All credentials as Buffer/Uint8Array, zeroed with buf.fill(0) after use. Never converted to JS strings (immutable, can't be wiped).

Domain allowlist

Deny-by-default. Each credential lists which domains it can be injected into.

Redirect protection

Domain re-verified after every navigation step. Aborts if redirect leaves the allowlist.

Human approval

First use of each credential+domain pair sends a push notification (Pushover) with a 4-digit code. 50-second window.

Rate limiting

3 requests/minute, 20/hour. Persisted in SQLite across restarts.

Audit trail

Append-only JSONL log. All credential values masked.

No cloud

Everything local. Vault never synced, uploaded, or phoned home.

CLI Reference

All CLI commands are human-only — never exposed via MCP.

agent-auth init                   # Create vault, set passphrase
agent-auth add <service>          # Add credential (interactive or with flags)
agent-auth list                   # List services (names only)
agent-auth remove <service>       # Delete a credential
agent-auth domains <service>      # View/edit allowed domains
agent-auth approve <code>         # Approve a pending auth request
agent-auth unlock                 # Unlock vault for current session
agent-auth lock                   # Lock vault, clear key from memory
agent-auth serve                  # Start MCP server (stdio)

Flags for add: --username, --password, --totp (base32 or otpauth:// URI), --domains (comma-separated), --notes.

Environment Variables

Variable

Purpose

Default

AGENT_AUTH_CDP_URL

Chrome DevTools Protocol endpoint for browser injection

(none — browser tools disabled)

AGENT_AUTH_PASSPHRASE

Vault passphrase (dev/CI only — use keychain in production)

(prompt or keychain)

AGENT_AUTH_NO_KEYCHAIN

Skip OS keychain, use file-only key storage

false

AGENT_AUTH_PUSHOVER_TOKEN

Pushover app token for approval notifications

(approval disabled)

AGENT_AUTH_PUSHOVER_USER

Pushover user key

(approval disabled)

Architecture

agent-auth/
├── src/
│   ├── index.ts              # CLI dispatcher + MCP serve
│   ├── mcp/                  # MCP server (3 tools over stdio JSONRPC)
│   ├── browser/              # CDP injection via Playwright (fill/type/click/wait/select)
│   ├── placeholder/          # {{email}}, {{username}}, {{password}}, {{totp}} resolution
│   ├── vault/                # AES-256-GCM encrypted SQLite + Argon2id KDF
│   ├── totp/                 # TOTP generation via otpauth
│   ├── proxy/                # HTTP credential injection (6 methods)
│   ├── approval/             # Human approval gate + Pushover
│   ├── security/             # Rate limiter, input validator, domain allowlist
│   ├── audit/                # Append-only JSONL audit log
│   └── cli/                  # Human-only credential management
└── tests/                    # 65 tests across 6 suites

Vault location: ~/.agent-auth/vault.db (SQLite, mode 0600)

Dependencies (intentionally minimal):

  • @modelcontextprotocol/sdk — MCP protocol

  • playwright-core — CDP browser automation (no bundled browser)

  • bun:sqlite — Built-in SQLite (zero deps)

  • otpauth — TOTP generation (5KB, pure JS)

  • @noble/hashes — Argon2id KDF (audited, pure JS)

  • zod — Input validation

Testing

bun test              # 65 tests, 6 suites
bun run typecheck     # TypeScript strict mode

Tests use AGENT_AUTH_NO_KEYCHAIN=1 to avoid macOS Keychain permission dialogs in CI.

Acknowledgments

agent-auth was inspired by these projects:

  • AgentSecrets (MIT) — Crypto envelope design (AES-256-GCM + Argon2id), domain allowlists, HTTP credential proxy pattern.

  • Virtual FIDO (MIT) — Passkey/WebAuthn emulation architecture. Planned for future passkey support.

Roadmap

  • Passkey/WebAuthn support via Virtual FIDO signing

  • npx agent-auth / global install

  • Credential import from 1Password, Bitwarden (CLI export)

  • Browser profile persistence (stay logged in across sessions)

  • Multi-page login flow templates (common services)

Contributing

PRs welcome. The codebase is small (~1,500 lines) and intentionally simple.

Ground rules:

  • No cloud features. The vault is local-only. This is not negotiable.

  • No new runtime dependencies without justification. We have 5 — that's enough.

  • Credentials must never be converted to JS strings. Buffer/Uint8Array only.

  • Tests required for any new functionality.

# Development
bun install
AGENT_AUTH_NO_KEYCHAIN=1 bun test    # Run tests
bun run typecheck                     # Type check

License

MIT — Ex Nihilo Labs, 2026