playwright-secure-mcp
Extend the Playwright MCP to allow safe password handling using 1Password
Documentation
playwright-secure-mcp
A Crystal MCP server that transparently proxies the upstream
(`@playwright/mcp`) and adds secure password handling. Its goal: **a resolved
secret value never accidentally reaches the LLM.**
How it works
- The binary speaks MCP JSON-RPC 2.0 over stdio to the client (the LLM host)
and spawns `@playwright/mcp` as a stdio child process, forwarding nearly all
messages untouched.
- It adds four secret tools to the upstream `tools/list`: three discovery tools
that list or find 1Password LOGIN items usable on the current browser page,
and `browser_type_secret`, which types a field of a chosen item into the
page. Closing the browser (`browser_close`) empties the local item cache. See
- Every message flowing back to the client is passed through a redactor that
replaces each resolved secret — including its URL-encoded, Base64,
HTML-escaped, and JSON-escaped variants — with the literal token
`«REDACTED»`. Secrets are caught wherever they appear: page snapshots,
network request dumps, console messages, and error text.
Secret tools
Discovery: list or find items for the current page
Three tools look up 1Password LOGIN items and return only those usable on the
current browser page: the proxy reads the page's `location.href` itself (the
caller never supplies a URL) and keeps an item only when one of its URLs
matches the page by host and path prefix. If the current URL cannot be
determined, discovery fails with an error. Results are a JSON array of item
identities plus non-secret field metadata — `vault`, `item`, `title`, `urls`,
`tags`, `fields` (id, label, type, purpose, section), and `sections` — never a
field value. All three accept an optional `vault` (ID or name) to scope the
search.
| Tool | Required arguments | Result |
|---|---|---|
| `browser_list_items` | _(none)_ | All LOGIN items usable on the current page |
| `browser_find_items_by_name` | `item` (title or ID) | Matching items, filtered to the current page |
| `browser_find_items_by_tag` | `tag` | Items carrying the tag, filtered to the current page |
Typing: `browser_type_secret`
Mirrors the upstream `browser_type` tool, but instead of a literal `text` value
it takes the 1Password coordinates of the secret:
- Required: `element`, `ref` (as in `browser_type`), `vault` (1Password vault
ID), `item` (1Password item ID), `field` (e.g. `username` or `password`)
- Optional: `submit`, `slowly` (as in `browser_type`)
The proxy resolves the field from the cached item (fetching the item from
1Password on demand when it is not cached), decrypts the value locally, and
issues an internal `browser_type` call to the upstream server with the resolved
value. Typing is refused unless the current page is in the item's URL set (the
same host + path-prefix match as discovery), and refused when the current page
URL cannot be determined.
Cache lifetime
Discovered items — with their field values encrypted — are cached in memory,
write-once. Calling the upstream `browser_close` tool empties this cache (the
close is still forwarded to the browser as usual); otherwise it lives for the
process lifetime.
Workflow: find, then type
1. Navigate to the login page, then call a discovery tool — e.g.
`browser_list_items` — to obtain the `vault` and `item` IDs of an item
usable on that page.
2. Call `browser_type_secret` with those IDs and the `field` to type.
Install
Homebrew (macOS and Linux)
Install from the Homebrew tap. The formula installs the prebuilt binary from
the latest GitHub release (the macOS builds are signed and notarized):
brew install jochenseeber/tap/playwright-secure-mcpThis puts `playwright-secure-mcp` on your `PATH`. It still needs the 1Password
CLI (`op`) and a Playwright MCP server — see Requirements.
To build from source instead, see Build.
Requirements
- Crystal `>= 1.20`
- The 1Password CLI (`op`), signed in
- `pnpm` or `npm` to download `@playwright/mcp` on demand, or a pre-installed
Playwright MCP server binary
Build
rake setup
rake build`rake build` writes a debug binary to
`bin/-/playwright-secure-mcp` (e.g.
`bin/darwin-arm64-system-dynamic-debug/…`) and a `bin/playwright-secure-mcp`
symlink to the latest build. Use `rake "build[release]"` for a release binary.
Run `rake -T` to list all available tasks.
Options
| Option | Default | Meaning |
|---|---|---|
| `--package-manager` | `pnpm` | `pnpm` (`pnpm dlx`), `npm` (`npx -y`), or `none` (pre-installed) |
| `--mcp-version` | `latest` | `@playwright/mcp` version tag/range (ignored when `none`) |
| `--mcp-bin` | `mcp-server-playwright` | Pre-installed binary; implies `--package-manager none` |
| `--command` | _(none)_ | Explicit upstream command override |
| `--op-command` | `op` | 1Password CLI binary |
| `--account-from-git` | _(none)_ | Read the account email from `DIR/.git/config` (`user.email`) |
| `--account` | _(none)_ | 1Password account (shorthand, sign-in email, or account ID) |
| `--account-email` | _(none)_ | 1Password account email |
| `--token-tag` | _(none)_ | 1Password item tag whose `credential` field holds a service-account token |
| `--require-hardware-key` | _(off)_ | Refuse to start without Secure Enclave/TPM key protection |
| `--version` | _(none)_ | Print the version and exit |
| `-- ` | _(none)_ | Extra args forwarded to the upstream server |
The three account options resolve to a single account passed to `op` via
`--account`; when more than one is given, precedence is `--account-from-git` >
`--account-email` > `--account`. With `--token-tag`, the resolved account is
used once at startup (interactive `op`) to fetch the tagged item's `credential`
field, and that value is then used as `OP_SERVICE_ACCOUNT_TOKEN` for all
subsequent secret resolution — in that mode `--account` is not passed to
`op read`. Without `--token-tag`, each `op read` uses the resolved account
directly.
MCP client configuration
{
"mcpServers": {
"playwright": {
"command": "/path/to/bin/playwright-secure-mcp",
"args": ["--", "--headless"]
}
}
}Security model
- The resolved secret travels `op` → this process → upstream child → browser.
It is never present in anything the LLM sent, and never present un-redacted
in anything the LLM receives.
- Revealed items are cached in-memory for the process lifetime (or until the
browser is closed via `browser_close`) in an obfuscated vault: each field
value is AES-256-CBC encrypted under a random per-process data key with a
fresh random IV per entry. The data key itself is hardware-protected when
possible — see Cache key protection.
- Caveat: the vault is obfuscation / defense-in-depth, not a security
boundary. With the in-memory fallback tier the encryption key lives in the
same process memory as the ciphertext, so it defeats casual heap inspection,
`strings`-style scanning, and accidental plaintext logging — but not an
attacker with full process-memory access. A hardware-backed tier removes the
long-lived key from process memory, but see the limitations below.
Cache key protection
At startup the proxy picks the best available protection tier for the vault's
AES-256 data key and logs the choice:
1. Secure Enclave (macOS, hardware): an ephemeral, non-extractable P-256
key is generated inside the Secure Enclave, and the data key is
ECIES-wrapped under it. The wrapped key is unwrapped in the enclave per
crypto batch and the plaintext key is zeroed afterwards; the long-lived key
never exists in process memory or on disk.
2. TPM 2.0 (Linux, hardware): the data key is sealed inside the platform
TPM via the tpm2-tss ESYS library over `/dev/tpmrm0` and unsealed
transiently per crypto batch, then zeroed. The binary links tpm2-tss, which
is assumed present on the host.
3. Kernel keyring (Linux, not hardware): the data key is stored in the
kernel keyring and AES runs in the kernel via an AF_ALG socket, so the key
never re-enters process memory — but it is kernel-backed, not
hardware-backed. Requires Linux ≥ 5.4.
4. In-memory (fallback): a plain per-process key, with a startup warning
that hardware-backed protection is unavailable.
On Linux the order is TPM → keyring → in-memory. Pass `--require-hardware-key`
to fail closed: the proxy refuses to start unless a hardware-backed tier
(Secure Enclave / TPM) initializes. The kernel keyring tier does not
satisfy `--require-hardware-key`.
Deployment requirement (macOS): the Secure Enclave is only usable when the
distributed binary is code-signed with the appropriate entitlement (Secure
Enclave / keychain access). An unsigned binary cannot generate an enclave key
(Security.framework fails with OSStatus -26276) and falls back to the
in-process key — or refuses to start under `--require-hardware-key`.
Limitations:
- The unwrapped data key is in process memory transiently per crypto batch,
then best-effort zeroed.
- Decrypted secret plaintext still transits process memory during redaction and
typing (out of scope; would require a sidecar).
- Per-message hot-path cost with the Secure Enclave is ~2–20 ms (one key unwrap
per message batch).
Tests
rake spec
rake lintFrequently asked questions
What is playwright-secure-mcp?
playwright-secure-mcp is Extend the Playwright MCP to allow safe password handling using 1Password
How do I install playwright-secure-mcp?
Open the GitHub repository and follow its README. Most MCP servers are added to your client's MCP config, then called by your agent.
Is playwright-secure-mcp open source?
Yes — it is hosted on GitHub at https://github.com/jochenseeber/playwright-secure-mcp and has 1 stars.
Related MCP tools
Model Context Protocol Servers
Pre-indexed code knowledge graph, auto syncs on code changes, for Claude Code, Codex, Gemini, Cursor, OpenCode, AntiGravity, Kiro, CoPilot, and Hermes Agent — fewer tokens, fewer tool calls, 100% local
an open source, extensible AI agent that goes beyond code suggestions - install, execute, edit, and test with any LLM
The easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:
The Open-Source Multimodal AI Agent Stack: Connecting Cutting-Edge AI Models and Agent Infra
Cognee is the open-source AI memory platform for agents. Give your AI agents persistent long-term memory across sessions with a self-hosted knowledge graph engine.
Run your own MCP server? See who uses it and what to fix.
Measure it with TrackMCP