Sallyport
Description
# Sallyport [](https://github.com/ginkida/sallyport/actions/workflows/ci.yml) [](https://github.com/ginkida/sallypor…
About
# Sallyport [](https://github.com/ginkida/sallyport/actions/workflows/ci.yml) [](https://github.com/ginkida/sallyport/actions/workflows/codeql.yml)…
Details
- Author
- ginkida
- Downloads
- 263
- Categories
- Developer Tools, Other, AI
Jump to
- HMAC-SHA256 signed frames with replay protection (nonce cache, timestamp drift ≤ 30 s)
- Domain allowlist in chrome.storage.local, patterns like example.com or *.example.com
- evaluate is opt-in per domain; other tools use structured CDP only
- Defense-in-depth: fill refuses <input type=password> unless allowPassword=true
- Operational visibility: every tool call is audited (last 500 entries) with one-click pause
- No content scripts or <all_urls> permissions; uses debugger API exclusively
- Per-tab accessibility refs (@e1, @e2), serialized tool calls via daemon-side lock
Setting up with Highlight
This MCP is not yet compatible with Highlight’s one-click setup. However, you can still use it with Highlight by following these steps:
- Download and install Highlight from highlightai.com/download
- Navigate to the plugins tab and select "Add Custom Plugin"
-
Configure the plugin with the settings below
Plugin Name
SallyportCommand (node, npx, python, etc.)Please refer to the README for specific instructions on how to obtain API keys or other required environment variables.
- Enable "Start Automatically" if you want the plugin to start when Highlight launches
From the repository
Install the daemon via pip install sallyport, build and load the unpacked Chrome extension from this repo, then pair the extension with the daemon using the generated secret. Register Sallyport as an MCP server in Claude Code and add domains to the extension’s allowlist to start automating.
Claude Desktop / Cursor
Paste into your MCP client config file to install this server.
{
"mcpServers": {
"sallyport": {
"sallyport": {
"command": "sallyport-daemon"
}
}
}
}
McpServers
{
"sallyport": {
"command": "sallyport-daemon"
}
}
Sallyport
A secure browser-automation bridge between Claude Code (or any MCP client) and
your Chrome. An alternative to Kimi WebBridge with explicit security
boundaries instead of implicit ones.
Claude Code ── MCP/stdio ──▶ daemon ── WS+HMAC ──▶ extension ── CDP ──▶ Chrome
| Status | Number |
|---|---|
| Daemon tests (pytest) | 439 |
| Extension tests (vitest) | 594 |
| Lint / typecheck (ruff, mypy, eslint, prettier, tsc) | all green |
What's in the box
| Path | What it is |
|---|---|
| extension/ | MV3 Chrome extension (TypeScript, esbuild, vitest). Loads as an unpacked extension. |
| daemon/ | Python MCP server. Speaks MCP on stdio to Claude Code, hosts a WS server on 127.0.0.1:10086 for the extension. |
| fixtures/ | Cross-language canonical-JSON / HMAC vectors shared by both test suites. |
| .pre-commit-config.yaml | Fast lint/format checks before commit. |
| .github/workflows/ci.yml | Same checks plus full tests on push/PR. |
Security model
A deeper threat model + known limitations lives in SECURITY.md.
The short version: the original Kimi extension trusts any process that can
reach 127.0.0.1:10086, which on a shared/compromised machine means
everything. Sallyport changes the default in five places:
1. HMAC-SHA256 on every frame. A 32-byte random secret lives in
~/.config/sallyport/secret (chmod 600) and is generated on first run. Both
sides sign every WS frame and verify timestamp drift (≤ 30 s) and nonce
freshness (rolling cache of 4096 nonces — replay-protected). A
cross-language test pin in pytest + vitest guarantees the canonical-JSON
and MAC bytes stay byte-for-byte compatible.
2. Domain allowlist enforced in the extension. Tools refuse to run on any
URL whose host isn't in chrome.storage.local.sallyport_allowlist. Patterns
are example.com, .example.com, or https://x.com/path/. Bare is
rejected by the validator.
3. evaluate is opt-in per domain. Even on an allow-listed domain,
arbitrary JS is refused unless that entry has allowEvaluate: true. Other
tools (click, fill, read_text, …) use structured CDP calls only.
4. Defense-in-depth on inputs. fill refuses <input type=password>
unless allowPassword=true. The daemon refuses to bind to anything that
isn't a loopback address. WS frames over 16 MiB are dropped (1009).
5. Operational visibility. Every tool call (and its outcome — ok or
error) is appended to chrome.storage.local.sallyport_audit (last 500
entries), browsable and JSON-exportable from the popup. One-click Pause
in the popup stops the WS connection and rejects all tool calls.
Other deliberate choices:
- No content-script injection, no <all_urls> content scripts. Permissions
are only what the debugger API needs (tabs, activeTab, debugger,
storage, alarms).
- Per-tab accessibility refs (@e1, @e2). Snapshotting tab A cannot
invalidate refs for tab B, and a ref scoped to A cannot resolve to a node
in B.
- MCP-side tool calls are serialised by a daemon-side lock so Claude can't
accidentally race state on the extension.
- The daemon shuts down cleanly on stdin EOF (Claude Code closing) or
SIGINT/SIGTERM: pending calls fail with ExtensionNotConnected, the
client gets a 1001 close, no orphan tasks.
What the extension still trusts: anyone with read access to
~/.config/sallyport/secret. The browser debugger is, ultimately, the browser
debugger — this bridge limits which domains it operates on and who* can
drive it.
Setup
1. Build the extension
The extension is not on PyPI — pip install sallyport (step 2) gives you
only the daemon. The extension lives in this repo's extension/ directory, so
you need a checkout to build it:
git clone https://github.com/ginkida/sallyport
cd sallyport/extension
npm install
npm run build
The output lands in extension/dist/. Load it as an unpacked extension:
1. chrome://extensions
2. Enable Developer mode
3. Load unpacked → pick extension/dist
Pin the toolbar icon.
2. Install the daemon
Sallyport needs Python ≥ 3.10 (it uses match statements and X | Y type
syntax). Check with python --version first.
pip install --user sallyport
Or from source (for development): cd daemon && pip install --user -e .
This installs the sallyport-daemon command on your PATH. Verify it landed there:
which sallyport-daemon # should print a path; if not, add your Python
# user-scripts dir (e.g. ~/.local/bin) to PATH
The first time something runs it, the daemon will:
- Generate a 32-byte secret in ~/.config/sallyport/secret (chmod 600).
- Print the base64 secret to stderr — paste it into the extension popup.
- Start listening on 127.0.0.1:10086 and speak MCP on stdio.
Then run the built-in setup check, which validates the install and prints the
exact block to paste into the popup:
sallyport-daemon doctor
It checks your Python version, the secret file and its permissions, and that
the port is free — then prints the pairing secret and the remaining steps.
Run it any time a connection won't come up. To just re-print the secret:
sallyport-daemon --show-secret
3. Register with Claude Code
Add an MCP server entry — either edit ~/.claude/mcp.json directly, or:
```sh
Sign in to leave a review
Use Google, GitHub, or an email account so ratings stay tied to real people.
No reviews posted yet.





