Metadata-Version: 2.4
Name: unlockbridge
Version: 0.0.1
Summary: Give any browser AI safe, human-approved access to your local files. No API keys, no cloud, no copied prompts.
Author: ThreeI
License: MIT
Project-URL: Homepage, https://github.com/romantick13/UnlockBridge
Project-URL: Repository, https://github.com/romantick13/UnlockBridge
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# 🔐 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](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](assets/popup-diff1.png)      |
| Review diff                         | ![Review diff](assets/popup-diff2.png)                       |
| File creation confirmation          | ![File creation confirmation](assets/popup-diff3.png)        |
| File&dirs creation confirmation     | ![File creation confirmation](assets/popup-diff4.png)        |


> ⚠️ Before creating an issue, read [CONTRIBUTING_RU.md](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.md).

---

## 🛡️ Security Model

### Trust boundary

```text
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](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](TODO.md).

### 1. Clone and install dependencies

```powershell
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:

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

Create the local configuration files:

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

Configure `mcpbridge/bridge_config.json`:

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

```powershell
.\start_server.cmd
```

To stop it completely:

```powershell
.\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:

```text
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:

```powershell
cd mcpbridge
.\install_host.ps1
```

If PowerShell blocks script execution:

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

Success criterion:

```text
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](https://chat.z.ai).
5. Refresh the page with `F5`.
6. Click the UnlockBridge icon.

Check the status in the popup:

```text
● 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:

```text
[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:

```text
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.

```json
{
  "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](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](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](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](TODO.md).

---

## 🐞 Troubleshooting

### The popup shows `disconnected` or `No such native application`

The Native Messaging registry entry is missing or damaged.

```powershell
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.

```powershell
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:

```text
Firefox → about:debugging → Inspect → background → Console
```

Run:

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

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

```text
parse → validate → route → execute → reply → deliver
```

---

## 📚 Documentation

- [ARCHITECTURE.md](ARCHITECTURE.md) — architecture, protocol, and adapters.
- [SECURITY.md](SECURITY.md) — threat model, 12 attack vectors, and limitations.
- [CONTRIBUTING_RU.md](CONTRIBUTING_RU.md) — issues, traces, and contribution rules.
- [TODO.md](TODO.md) — roadmap and planned integrations.

---

## 📜 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. 😎
