Skip to main content

🔐 UnlockBridge

UnlockBridge is a human-controlled bridge between an untrusted browser-based AI assistant and local MCP tools for working with files.

The model may propose operations; a human decides whether they are executed. File writes go through a separate human gate: before committing, you see the exact diff and explicitly click Apply or Reject.

  • Works through a browser extension.
  • Requires no API keys.
  • Uses no cloud intermediary.
  • The MCP server intentionally operates in read-only mode.
  • Current adapter: chat.z.ai / GLM.
  • Other websites require a separate DOM adapter; see ARCHITECTURE.md, §9.
  • Current platform: Windows + Firefox.

The model proposes; the human decides.
No blind trust. By default, no write is executed without an explicit human decision.

Interface

| All right with bridge+server | All right with bridge+server | | Review diff | Review diff | | File creation confirmation | File creation confirmation | | File&dirs creation confirmation | File creation confirmation |

⚠️ Before creating an issue, read CONTRIBUTING_RU.md.
Issues without the required logs and trace artifacts may be closed.
For threat model, supported versions, and private vulnerability reporting, see SECURITY.md.


🛡️ Security Model

Trust boundary

Browser AI / web chat                 Untrusted side
        │
        │ Structured operation proposal
        ▼
UnlockBridge extension + Native Host  Policy, nonce, limits, routing
        │
        │ Prepare + diff for writes
        ▼
Human                                 Apply / Reject
        │
        │ Only after Apply
        ▼
Local filesystem                      Allowed workspaces only

What is guaranteed

| Operation | May be proposed | Human required | Restrictions |

by the model
read_file, read_many, list_dir, stat, search Yes No Workspace allowlist, policy checks, hard-deny for secret files
write_file Yes Yes, always by default Strict parser, nonce, origin, routing guard, 2PC, diff, hash check, atomic replace, audit
mkdir Yes Yes Workspace boundary, budgets, explicit confirmation
delete, rename, execute No — Not supported by design

Reading

Reading is restricted by workspace allowlists, policy checks, and a hard deny for known secret files: .env*, *.pem, *.key, *.p12, *.pfx, *.sqlite*, id_rsa*, and .bridge_token.

This is access control, not a guarantee that no sensitive information exists inside an allowed workspace. Do not place secrets in a workspace that should not be available to the browser-based model.

Writing

write_file passes through nine independent protection layers:

  1. Strict block parser: exactly one [mcp-call] block and nothing after it.
  2. Nonce session, deduplication, and rate limits.
  3. Origin verification.
  4. Routing guard: write-like tools cannot be proxied around the human gate.
  5. 2PC prepare: a file snapshot and diff are stored in SQLite.
  6. The human sees the exact diff and clicks Apply or Reject.
  7. Hash triage at commit: external changes are detected; silent overwrites are not allowed.
  8. Atomic write: tmp → fsync → replace.
  9. Audit log and budgets: every decision is recorded and task limits apply.

Important limitations

UnlockBridge does not evaluate the quality or security of code proposed by the model. It controls the path to the filesystem; it does not replace code review, testing, backups, or a sandbox.

Use a working copy of the project, check the diff before approving it, and test accepted changes. The local operating system, browser profile, Native Host, and physical access to the computer are considered trusted parts of the threat model.

🔒 Assuming that the local operating system, browser, extension, and Native Host have not been compromised, the browser-based model does not receive the commit token and cannot bypass the popup through the MCP protocol. Even a prompt-injected model cannot write a file without your explicit Apply click.

Defense in depth

server.py intentionally operates in read-only mode. Even direct MCP clients (such as Cherry Studio or IDE agents) cannot write files through the MCP server: all writes go exclusively through the UnlockBridge human gate.

The policy guard blocks write-like tool names and checks readOnlyHint for every proxied call.

The project was inspired by real MCP integration risks, including CVE-2025-68143, CVE-2025-68144, and CVE-2025-68145 involving a Git MCP server, as well as the data-exfiltration scenarios described by Invariant Labs.

The complete threat model, limitations, and live proofs are available in SECURITY.md.


🚀 Quick Start: Windows + Firefox

Requirements

  • Python 3.10+; tested with Python 3.12.
  • Firefox.
  • Git.
  • Windows with PowerShell.

Chrome is not supported yet. Chrome support is listed in TODO.md.

1. Clone and install dependencies

git clone [https://github.com/romantick13/UnlockBridge.git](https://github.com/romantick13/UnlockBridge.git)
cd UnlockBridge

python -m venv .venv
.\.venv\Scripts\Activate.ps1

pip install -r requirements.txt
pip install -r requirements-bridge.txt

⚠️ Always inspect requirements*.txt before installing.
The minimum verified dependency set is fastapi, uvicorn, sse-starlette, pyyaml, and requests. No telemetry is used.

2. Workspace and ACL

Working with a copy of the project rather than the original is strongly recommended.

The human gate records every approved write, but a mistakenly approved model change can still damage a real project. Recommended workflow:

Project copy → sandbox workspace → review diff → test → manual synchronization or Git

Create the local configuration files:

copy config\acl.example.yaml config\acl.yaml
copy mcpbridge\bridge_config.example.json mcpbridge\bridge_config.json

Configure mcpbridge/bridge_config.json:

{
  "workspaces": {
    "project": "D:/Projects/MyAppTest"
  },
  "mcp_url": "http://127.0.0.1:8790/sse"
}
  • config/acl.yaml defines the paths that the MCP server is allowed to read.
  • Hard-deny rules for known secrets are enforced in code independently of the ACL.
  • mcpbridge/bridge_config.json is the only source of truth for bridge configuration.
  • Use forward slashes / in JSON paths to avoid escaping problems.
  • config/bridge_config.json is not used.
  • Save YAML and JSON files as UTF-8; ANSI/CP1251 encoding causes decoding errors.

3. Start the read-only MCP server

In a separate PowerShell window, from the repository root:

.\start_server.cmd

To stop it completely:

.\stop_server.cmd

start_server.cmd sets MCP_REQUIRE_TOKEN=1. stop_server.cmd terminates the process that owns the port; graceful shutdown may hang while SSE connections are still active.

Expected output:

UnlockBridge file server (READ-ONLY): http://127.0.0.1:8790/sse
Auth: ON (Bearer)
ACL: D:\UnlockBridge\config\acl.yaml (edit paths before first real use!)
Writes: NOT served here — go through the UnlockBridge bridge (human gate).
Uvicorn running on http://127.0.0.1:8790

The server creates a bearer token in config/.bridge_token. The MCP server is intentionally read-only: writes are performed only through the bridge and the human gate.

4. Register the Native Host

In a new PowerShell window:

cd mcpbridge
.\install_host.ps1

If PowerShell blocks script execution:

Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\install_host.ps1

Success criterion:

Registered native host:
HKCU:\Software\Mozilla\NativeMessagingHosts\com.example.mcp_bridge

5. Load the extension in Firefox

  1. Open about:debugging#/runtime/this-firefox.
  2. Click Load Temporary Add-on….
  3. Select mcpbridge/ext/manifest.json.
  4. Open chat.z.ai.
  5. Refresh the page with F5.
  6. Click the UnlockBridge icon.

Check the status in the popup:

● connected
reconnects: 0
Server: connected

The nonce is displayed in masked form. Insert the contract using the button in the popup, compare the nonce in the inserted contract with the nonce shown in the popup, and then click Send yourself in the chat.

If WAITING is displayed, wait up to 15 seconds — this is the reconnection interval. If the status does not change, check the server and mcpbridge/host_debug.log.


🧭 How It Works

1. The model proposes an operation

The model generates a strictly structured block:

[mcp-call]
{
  "nonce": "…",
  "task_id": "task-1",
  "call_id": "call-5",
  "tool": "write_file",
  "args": {
    "ws": "project",
    "path": "notes.md",
    "content": "…"
  }
}
[/mcp-call]

2. The bridge validates the request

  • content.js strictly parses the response: one [mcp-call] block and no text after it.
  • background.js checks the nonce, duplicate call_id values, limits, and origin.
  • The Native Host applies policy and routes the request securely.

3. Read or prepare a write

  • Read operations are returned subject to policy and ACL.
  • A 2PC transaction is created for a write: snapshot, diff, and SQLite record.
  • The file on disk is not changed at this stage.

4. The human makes the decision

The popup shows the exact diff. You choose:

  • Apply — execute the prepared operation.
  • Reject — cancel the operation; nothing is changed on disk.

5. The result is returned to the chat

The committed or rejected result is inserted into the browser chat's input field. You click Send yourself.

Tool contracts

The model receives tool descriptions from an automatically generated skill.

Tools Route Purpose
read_file, list_dir, stat, search, write_file, mkdir Gateway / Native Host Single-file reads and all writes through policy and the human gate
read_many MCP server, read-only Batch reading of up to 10 files; an absolute workspace_root is required

🧪 Installation Test

After inserting the contract using the popup button, try the following:

  1. List files: Show the contents of the current workspace.
  2. Read a file: Read the first 500 bytes of README.md.
  3. Safe write:
    Create a file named test_unlock.md with the text 'Hello from UnlockBridge'.
  4. Check the diff in the popup and click Reject first. The file must not appear.
  5. Repeat the operation and click Apply.
  6. Check the result:
    Run stat on test_unlock.md.

size and sha256 should confirm that the file was created.


⚙️ Configuration

The main configuration file is:

mcpbridge/bridge_config.json
Field Purpose Default
workspaces Map of name → absolute path. The path must exist. {}
db_path Path to the SQLite database for prepared transactions. bridge.db
mcp_url MCP server SSE endpoint. http://127.0.0.1:8790/sse
mcp_token_file Path to the bearer token created by the server. ../config/.bridge_token
mcp_timeouts connect_s, sse_read_s, post_connect_s, post_read_s, call_s. See example config
mcp Bridge limits: retry, watchdog, cleanup, and transaction max age. Safe values from the code
mcp_expected_tools Allowlist of proxied MCP tools. []: all tools that pass the policy guard
budgets Task limits: max_commits, max_files, max_bytes, max_dirs. See example config
ttl Prepared transaction TTL in seconds. 600

allow_auto_approve

If allow_auto_approve exists in your version, keep it set to false.

Setting it to true disables one of the key UnlockBridge guarantees: a write operation may proceed without showing the popup and without an explicit human decision. This mode is suitable only for isolated tests with disposable data. Do not use it with untrusted models or real projects.

{
  "allow_auto_approve": false
}

ACL

config/acl.yaml defines which paths the MCP server may read.

Even if the bridge allows an operation, the MCP server may reject it according to the ACL. Keep acl.yaml and bridge_config.json consistent.


🎯 End-to-End Example

In a live session, an untrusted model planner:

  • Detected a byte-level mismatch by comparing the current size and sha256 with a snapshot from an earlier read_many, without rereading the file.
  • Diagnosed a CRLF/LF difference: the original Windows file contained 0D 0A and a trailing newline, while write_file received an LF version.
  • Proposed a byte-exact correction through the same human gate.
  • Required separate human approval for both the destructive and restoring write; both transactions were recorded in the audit log.
  • Received denials when attempting an absolute path and ../ traversal: the policy guard blocked the requests before ACL and filesystem access.

This demonstrates that the model may propose and revise a plan, but it does not receive permission to modify files autonomously.


❓ FAQ

Why Firefox only? What about Chrome?

Chrome support is planned; see TODO.md. The current security model has been tested on Firefox with a persistent background. The project does not ship an untested implementation merely for formal cross-browser support.

How is this different from Claude Code or Copilot?

UnlockBridge works with browser-based chats and does not require an API key. It does not provide a model and does not replace an IDE agent; its purpose is to give an untrusted browser model controlled, limited, and auditable access to local files through a human gate.

Currently, the chat.z.ai adapter is included. Other websites require their own DOM adapter; see ARCHITECTURE.md, §9.

What if prompt injection makes the model delete files?

There is no delete tool by design. Writes go through the human gate, while reads are restricted by the workspace allowlist and secret deny-list. See the complete threat model in SECURITY.md.

Is this an official MCP project? Is it connected to Anthropic?

No. UnlockBridge is an independent project and is not affiliated with Anthropic, Z.AI, or Mozilla.

Why can directories be created during write_file?

write_file may create missing parent directories. This is shown in the confirmation diff as Directories to be created.

Use mkdir to create an empty directory. One call creates one level and requires confirmation.

Can the model read files outside the workspace?

No. Absolute paths and ../ traversal are blocked by policy before filesystem access. An additional restriction is enforced by config/acl.yaml.

Does it work with Perplexity, DeepSeek, or Claude.ai?

Currently, only chat.z.ai is supported. Each website requires its own DOM adapter. The template is adapters/base.js; the current implementation is adapters/zai.js.

Pull requests with adapters are welcome. The planned integrations are listed in TODO.md.


🐞 Troubleshooting

The popup shows disconnected or No such native application

The Native Messaging registry entry is missing or damaged.

cd mcpbridge
.\install_host.ps1 -Force

Then fully restart Firefox.

The popup shows Server: WAITING

The MCP server is not running, the port is incorrect, or the token does not match.

Check the following:

  1. Is .\start_server.cmd running?
  2. Does mcp_url in mcpbridge/bridge_config.json match the server?
  3. Does mcpbridge/host_debug.log contain MCP connect failed?

FileNotFoundError: bridge_config.json

The bridge configuration is missing or was deleted.

copy mcpbridge\bridge_config.example.json mcpbridge\bridge_config.json

Then configure workspaces. The bridge intentionally exits when the configuration is missing: there is no safe default workspace.

ACL UNREADABLE ... deny-by-default

acl.yaml was probably saved as ANSI/CP1251.

Save the file as UTF-8. Until this is fixed, the server correctly fails closed and rejects all requests.

'utf-8' codec can't decode byte ...

The project file or acl.yaml is saved as ANSI/CP1251. Convert it to UTF-8 using VS Code, Notepad++, or FAR Manager.

Duplicate call_id values

The model reused an identifier, or the page was refreshed during streaming. Ask the model to generate a new call_id or click Rotate nonce in the popup.

Deep diagnostics

Open:

Firefox → about:debugging → Inspect → background → Console

Run:

copy(JSON.stringify(await __dumpTrace(), null, 1))

This exports the last 300 trace events from the ring buffer:

parse → validate → route → execute → reply → deliver

📚 Documentation


📜 License and Authors

License: MIT.

UnlockBridge is an independent project and is not affiliated with Anthropic, Z.AI, or Mozilla.

Developed by ThreeI (ТриИ). The project is developed with AI assistance under human control: AI reviewers propose and challenge design and code decisions, all changes pass the complete test suite, and final security decisions are made by the project owner.

Special thanks to the early testers who insisted on a public release despite my initial desire to keep the tool private. 😎

Metadata

Release files for unlockbridge 0.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for unlockbridge 0.0.1
File Size Uploaded
unlockbridge-0.0.1.tar.gz 17.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for unlockbridge 0.0.1
File Interpreter ABI Platform
unlockbridge-0.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 26.5 kB

Release files / unlockbridge-0.0.1.tar.gz

Download URL unlockbridge-0.0.1.tar.gz
Size 17.3 kB
Tags Source
SHA-256 checksum
How to use checksums
e5e602e097ea0b426bb082f3f225ad0cd63141874cfef91bad6514e7d708e152
BLAKE2b-256 checksum
How to use checksums
5dbba88ed7c72141b789d9f58f5f2b7759e467a031911cad29c3e8895cda4796
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.2

Release files / unlockbridge-0.0.1-py3-none-any.whl

Download URL unlockbridge-0.0.1-py3-none-any.whl
Size 9.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d31eda9142078b7584e5a974e48ca79e1e9028f0452488a4025fa54839f7842a
BLAKE2b-256 checksum
How to use checksums
7c1a3f07c8a4a1e3dd4758baecc02c668f9a69d84dbafbcd7716897496a200b8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.2

Release history Release notifications | RSS feed

This release

0.0.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page