# Vault Bridge
> [English](README.md) · [简体中文](docs/README.zh-CN.md)
Open a **token-protected port** on your computer, then upload and download vault files from your phone over a URL.
One codebase plays two roles:
- **On desktop** it runs an HTTP server: opens the port, verifies the token, reads and writes your vault.
- **On mobile** it is a client only: browse files on the computer and download them, or send files from the phone back to the computer.
No cloud service, no account, no third-party server in the middle. Files travel directly between your own devices.
## Sponsor / 赞助
**English** — Vault Bridge is a spare-time project. If it saves you some hassle moving
files between your computer and phone, you can buy me a cup of tea.
**Entirely optional — every feature stays free.**
**中文** —— Vault Bridge 是业余时间的作品。如果它让你在手机和电脑之间传文件省了事,
欢迎扫码请我喝杯茶 —— **完全自愿,不影响任何功能**。
WeChat Pay / 微信支付
|
Alipay / 支付宝
|
Three things that help just as much as money / 不花钱也能帮上忙:
1. Star this repo / 给仓库点个 Star;
2. Vote for it in the [community directory](https://community.obsidian.md/plugins) once it is listed /
等它进入社区目录后点个赞;
3. Report what broke / 把遇到的问题反馈过来。
## Features
| | |
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **One-tap whole-vault sync** | On the phone, tap **⬇ 一键同步整个库到手机** right under the tabs: the entire vault comes over in one go, and later taps transfer only the files that changed. |
| **Update the plugin from the computer** | On mobile, **⇧ 更新插件** pulls `main.js` / `manifest.json` / `styles.css` from the desktop server and replaces them in place — no file manager, no manual copying. |
| **Actions reachable from Settings** | The 手机端 group starts with a **连接检查与一键同步** row: **测试连接(检查 200)** proves the address answers, **⬇ 一键同步整个库** syncs without opening the panel, **打开传输面板** opens the browser view. |
| **Download to phone** | Browse the remote vault and tap a file. Tapping a folder downloads it recursively, keeping the same relative structure so wiki-links and attachments keep working on mobile. |
| **Upload to computer** | Browse the phone vault and send files back. The landing folder is configurable. |
| **Works without the plugin** | The desktop server also serves a mobile-friendly web page, so any phone browser can transfer files with zero install. |
| **Built-in setup page** | `/setup` walks you through installing the mobile plugin and hands out the installation bundle — no cable, no cloud drive. |
| **MCP for Claude Code** | [`mcp/vault-bridge-mcp.mjs`](mcp/vault-bridge-mcp.mjs) exposes the same token-protected API as MCP tools (`vault_list` / `vault_read` / `vault_write` / `vault_mkdir` / `vault_stat` / `vault_health`), so an AI coding agent works through the plugin instead of bypassing it — same path sandbox, same access log. See [docs/claude-code.md](docs/claude-code.md). |
| **LAN first, public optional** | Same-WiFi transfers go straight over your local network. For 4G/5G there is a one-command HTTPS tunnel. |
## Requirements
Obsidian **1.13.0 or later**. The settings tab uses the declarative settings API
introduced in 1.13.0, which is what lets these settings show up in Obsidian's
settings search.
## Quick start
### 1. On the computer
```bash
npm install
npm run build
node tools/install.mjs --enable # install into your vault and enable it
```
Or copy `main.js`, `manifest.json` and `styles.css` into
`/.obsidian/plugins/vault-bridge/` and enable it under
**Settings → Community plugins**.
Reload Obsidian; the server starts automatically on port **8770**.
The plugin settings then show:
- the running status,
- a LAN address such as `http://192.168.1.44:8770/?token=…`,
- the access token.
Click the address to copy it.
### 2. On the phone
**Option A — browser, nothing to install.** Open the copied link on your phone.
Add it to your home screen and it behaves like an app.
**Option B — the mobile plugin.** On the phone open `http://:8770/setup`,
download the bundle, unzip it into `/.obsidian/plugins/vault-bridge/`,
then enable **Vault Bridge** in Obsidian and enter the address and token.
Step-by-step iOS instructions (中文): [docs/mobile-setup.md](docs/mobile-setup.md).
## Network
| Situation | Address | What to do |
| ----------------------------------- | ------------------------------- | ------------------------------------------- |
| Phone and computer on the same WiFi | `http://192.168.1.44:8770` | Nothing — it just works |
| Phone on 4G/5G | `https://xxx.trycloudflare.com` | Run `tools/tunnel.sh start` on the computer |
Public access must be **HTTPS**: iOS App Transport Security rejects plain HTTP requests
from the app, which is why the LAN address stops working outside your home network.
```bash
tools/tunnel.sh start # start the tunnel and print the public URL
tools/tunnel.sh status
tools/tunnel.sh stop
```
Quick-tunnel hostnames change on every restart. Update the address on your phone when that
happens; the token stays the same.
## Security
An open port gets probed, so every request is treated as hostile until proven otherwise.
- **The token is the only credential** — 32 URL-safe random characters, sent as `Authorization: Bearer` or `?token=`.
- **Constant-time comparison** — response timing cannot be used to guess the token character by character.
- **Rate limiting** — 8 failures from one IP within a minute triggers a 5-minute block.
- **Path sandbox** — every path from the network is normalized first; `../` escapes, absolute paths and drive letters are rejected.
- **The config directory is off-limits** — `.obsidian` cannot be read or written over the network, so tokens and plugin data cannot be exfiltrated or overwritten.
- **Writes can be disabled** — turn off "allow upload" and the phone becomes read-only.
- **Upload size cap** — 32 MB by default, configurable.
- **Access log** — see who did what and when in the settings tab.
Regenerating the token in the settings invalidates the old one immediately.
## FAQ
**The phone cannot connect.**
Open `http://:8770/api/health` in the phone browser.
JSON back means the network is fine. A 401 means the network is fine but the token is wrong.
Nothing at all means WiFi or firewall.
**Port already in use?** Change the port in the settings (1024–65535) and press "restart service".
**Where do downloaded files go?** Into `VaultBridge下载/` on the phone, mirroring the computer's
relative paths. Configurable in the settings.
## How one bundle runs on both platforms
Mobile Obsidian cannot load Node built-in modules. The build therefore:
- bundles with `platform: browser` and marks `http`/`os`/`fs` as external,
- runs server code only when `Platform.isDesktopApp` is true, resolving Node modules lazily
through a single `nodeRequire()` chokepoint.
`tools/verify-bundle.mjs` loads the real `main.js` the same way Obsidian does — injecting
`module`/`exports`/`require` through `new Function` — then starts the server and fires real HTTP
requests. It also asserts that on mobile the plugin **creates no server instance and binds no port**.
## Development
```bash
npm run build # bundle to main.js
npm run typecheck
npm run lint
npm test # 160 unit & integration tests
npm run test:coverage
npm run verify # 42 bundle-level assertions against the real artifact
npm run bench # end-to-end latency benchmark
```
### Layout
```
src/
├── main.ts plugin entry; dispatches the two roles by platform
├── settings.ts settings model and the desktop settings tab
├── shared/ used by both sides: types, path safety, token, protocol, transfer paths
├── server/ desktop: HTTP server, router, auth, vault ops, web UI, self-setup page
└── client/ mobile: API client and the transfer panel
tests/ vitest: attack surface, auth, routing, real-port integration
tools/ installer, tunnel, bundle verification, Obsidian host double
```
## Verifying a release
Release assets ship with a GitHub build provenance attestation, so you can confirm
that `main.js` was built from this repository by its release workflow:
```bash
gh attestation verify main.js --repo YaYII/obsidian-vault-bridge
```
On older `gh` versions (below 2.49) verify through the API instead:
```bash
D=$(sha256sum main.js | cut -d" " -f1)
gh api "repos/YaYII/obsidian-vault-bridge/attestations/sha256:$D"
```
The build is also reproducible: it embeds no timestamp or randomness, so rebuilding
from the same tag yields a byte-identical `main.js`.
## License
[MIT](LICENSE)