trackmcp
Back to directory
jochenseeber

playwright-secure-mcp

View on GitHub

Extend the Playwright MCP to allow safe password handling using 1Password

1 stars CrystalOthers Updated Jul 30, 2026

Documentation

playwright-secure-mcp

A Crystal MCP server that transparently proxies the upstream

Playwright MCP server

(`@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

Secret tools.

  • 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.

ToolRequired argumentsResult
`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):

bash
brew install jochenseeber/tap/playwright-secure-mcp

This 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

bash
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

OptionDefaultMeaning
`--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

json
{
    "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

bash
rake spec
rake lint

Frequently 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

Run your own MCP server? See who uses it and what to fix.

Measure it with TrackMCP