nxfu/binthere

GitHub: nxfu/binthere

binthere 是一个基于 Cloudflare Workers 的零知识端到端加密阅后即焚笔记分享服务,笔记在客户端加密后上传,阅读一次即销毁。

Stars: 30 | Forks: 1

binthere wordmark

阅后即焚,安全密封。

零知识、端到端加密的笔记,阅读一次后自动销毁。

在线体验 · 安装 CLI · 文档 · 报告 bug

binthere 是一个零知识、端到端加密的 pastebin。写一段笔记,生成链接, 分享出去——笔记在被阅读的瞬间便会自动销毁。你的浏览器会在数据**离开** 设备之前,使用 AES-256-GCM 对所有内容进行加密,因此服务器端永远只能 保存无法读取的密文。你可以把它想象成一个用于销毁文本的自毁信封: 用来传递密钥、凭证、私人消息或代码片段。

Node.js JavaScript HTML CSS ESLint

## 为什么选择 binthere? binthere 是受 [PrivateBin](https://privatebin.info) 零知识模型启发而进行的一次干净彻底的重构—— 它采用了现代化的 Web Crypto、严格的 CSP、原子化的阅后即焚以及完善的测试套件,同时去 除了约 700 KB 的 jQuery/Bootstrap/zlib-WASM 负担。它作为单个 Cloudflare Worker 运行 (静态资源 + KV + Durable Object),因此托管成本极低,且无需维护任何服务器。 - **无需账号,没有追踪。** 粘贴,分享,搞定。不需要注册,也没有任何 分析程序在监视你的操作。 - **任何人都无法恢复丢失的链接。** 即使是平台运营方也做不到——系统里没有可供查找的 key, 也没有用于检索 paste 的索引。根据设计,链接是 key 的唯一副本。 - **诚实的限制。** 服务器仍然能看到 IP、时间和密文大小(它是私密的, 而非匿名的),并且像所有浏览器内加密一样,它必须信任站点提供的代码——完整的 威胁模型记录在 [`SECURITY.md`](./SECURITY.md) 中,并在 [局限性](#limitations) 中进行了总结。 如果你想要一个只需一条命令即可部署、无需自行打补丁、备份或持续维护在线状态的 paste 服务, 请选择 binthere。 ## 工作原理 整个设计依赖于一个核心机制:**解密 key 存放在哪里**。它被放置在 URL 的 fragment 中——即 `#` 之后的部分——浏览器永远不会将这部分发送到任何服务器。 ``` you type ──▶ browser encrypts (AES-256-GCM) ──▶ Worker stores ciphertext only │ │ key stays in the URL #fragment KV or Durable Object │ │ recipient opens link ─▶ browser fetches ciphertext ─▶ browser decrypts ─▶ plaintext ``` 1. **你编写了一条笔记。** 你的浏览器会生成一个随机的 256 位 key,并在 发起任何网络请求之前,使用 AES-256-GCM 在本地对笔记进行加密。 2. **仅上传密文。** key 永远不会被发送;它在 `#` 之后被附加到你的链接中。服务器存储的是一个它根本无法读取的不透明数据块。 3. **你分享链接。** 它同时包含了笔记的 id 和 key (`…/p/#`)——这个链接本身*就是*读取该笔记的凭证。你还可以选择添加 密码:密码会被混入 key 派生过程中,因此单独的链接或密码都 无法解密。 4. **接收者打开链接。** 他们的浏览器会获取密文,从 fragment 中读取 key,并在本地进行解密。服务器在整个过程中永远看不到明文。 每条笔记都是**一次性查看**的:第一个阅读者会以原子方式消耗它(即使在并发点击的情况下 也只会产生唯一一个成功者——这由 Durable Object 保证),之后的所有人都会 收到 `410 Gone`。未阅读的笔记无论如何都会在 24 小时后自动删除。 ## 功能 | 功能 | 详情 | | --- | --- | | 零知识 | 加密和解密仅在你的设备上(浏览器或 [CLI](#cli))发生;服务器仅存储不透明的密文和非机密元数据。 | | 可选密码 | 叠加在 URL key 之上——两者单独都无法解密。 | | 阅后即焚 | 每条笔记都执行严格的、原子化的单消费者读取(通过 Durable Object)。第一个阅读者会获取内容;其他人都会得到 `410 Gone`。 | | 自动过期 | 笔记会在 24 小时后自动删除。 | | 安全渲染 | 自动检测的语法高亮和安全的 Markdown 子集(无原始 HTML,链接经过净化)。所有渲染仅通过 DOM 构建完成——绝不使用 `innerHTML`。 | | 分享工具 | 复制链接、二维码、删除链接。 | | 极简表面 | 严格的 CSP、自托管字体、无第三方脚本、无分析程序、无账号。 | ## 对比 所有这些都是可靠的零知识 paste/机密共享工具——区别主要在于它们 的托管方式和优化方向: | 项目 | 服务器 | 存储 | 显著特征 | | --- | --- | --- | --- | | **binthere** | Cloudflare Worker(无服务器,无源站服务器) | Workers KV + Durable Object | 包含测试向量的固定 spec、原子化的阅后即焚、无客户端框架或构建步骤 | | [PrivateBin](https://privatebin.info) | PHP | 文件系统 / DB / S3 | 成熟,支持多种格式、讨论和 i18n | | [Yopass](https://github.com/jhaals/yopass) | Go | Memcached / Redis | 专注于机密共享,拥有 CLI 客户端 | | [cryptgeon](https://github.com/cupcakearmy/cryptgeon) | Rust | Redis | 文件分享,查看次数限制 | ## 快速开始 要求 Node.js ≥ 20(`.nvmrc` 中固定为 22)。 ``` npm install npm run dev # wrangler dev → http://127.0.0.1:8787 ``` KV、Durable Object 和速率限制都在本地进行了模拟——开发时 无需 Cloudflare 账号。 | 命令 | 描述 | | --- | --- | | `npm run dev` | 在 `http://127.0.0.1:8787` 启动本地开发服务器 | | `npm test` | 完整的 Vitest 套件:在真实的 `workerd` runtime 中测试 Worker/前端,然后在 Node 中测试 CLI 套件 | | `npm run test:cli` | 仅运行 CLI 套件(`cli/`,纯 Node 环境) | | `npm run test:watch` | 在 watch 模式下运行测试 | | `npm run test:coverage` | 带有覆盖率报告的测试 | | `npm run lint` | ESLint 9(扁平化配置) | | `npm run kv:create` | 创建 `PASTES` KV 命名空间(+ 预览环境) | | `npm run deploy` | 部署到 Cloudflare | CI 会运行 lint、逐字节的测试向量比对以及完整的测试套件。 ## CLI 官方命令行客户端位于 [`cli/`](./cli) 中,并作为 [`binthere`](https://www.npmjs.com/package/binthere) 发布到 npm。它实现了与 Web 客户端相同的固定协议 ——加密在本地进行,仅上传密文,并且笔记具有 与网站相同的**单次阅读 / 24 小时**生命周期。零运行时依赖 (仅使用 Node ≥ 20 的内置模块)。 ``` npm install -g binthere # or try it without installing anything: npx binthere ``` 直接运行 `binthere` 会打开一个交互式的全屏菜单;它也可以在 pipeline 中组合使用 (`git diff | npx binthere` 会在 stdout 输出一个分享 URL)。完整的命令参考、交互式模式指南 及安全说明,请参阅 [`cli/README.md`](./cli/README.md)。 ## 部署 [![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/nxfu/binthere) 上面的按钮会克隆仓库,并根据 [`wrangler.toml`](./wrangler.toml) 中声明的内容配置所有资源——包括静态资源、`PASTES` KV 绑定、 `BurnPaste` Durable Object + 迁移,以及 `CREATE_RL` 速率限制器——部署在 你自己的 Cloudflare 账号下。导入器会创建全新的资源,并重写*你*本地 配置副本中的资源 id;仓库中已提交的 id 属于原始部署环境,它们是 标识符,而非机密信息。 如果一键部署路径出现问题,下面的手动方法是绝对可靠的备用方案: ``` npm run kv:create # create your own PASTES KV namespace (+ preview) # 将打印出的 id / preview_id 粘贴到 wrangler.toml 中 npm run deploy # creates the Worker, Durable Object, and rate limiter ```
自托管检查清单 - 将 `wrangler.toml` 中的 KV `id` / `preview_id` 替换为你自己的(干净的模板位于 [`wrangler.toml.example`](./wrangler.toml.example) 中)。 - 更新硬编码的规范 URL:`public/index.html` 中的 `og:url` / `og:image` 以及 `public/.well-known/security.txt` 中的 `Canonical` 指向 `binthere.gaury.dev`; 页脚和 `security.txt` 中的 `Policy` 指向 `github.com/nxfu/binthere`。 - 使用占位符 KV id 即可运行 `npm run dev`——本地环境会模拟 KV。 - **关于 `never` 过期的成本说明:** 官方客户端总是创建 24 小时的阅后即焚 笔记,但传输格式(以及 API)接受 `expire: "never"`。这样的 paste 在 存储时没有 KV TTL,也没有 Durable Object alarm——一份永久保存且未被阅读的销毁笔记 在基于 SQLite 的 DO 存储计费下会产生微小的长期成本。如果你向第三方客户端暴露了 `never`,请决定是限制它还是接受这一既定成本。
## 架构 | 组件 | 作用 | | --- | --- | | 静态资源 (`public/`) | SPA 前端,由 Worker 直接提供 | | Worker (`src/index.js`) | `/api/*` paste API——存储密文,强制执行大小/速率/销毁限制 | | KV (`PASTES`) | 普通 paste,具有原生的 TTL 过期功能 | | Durable Object (`BurnPaste`) | 阅后即焚 paste,原子化的单消费者读取 | | 速率限制绑定 | 缓解创建 paste 时的滥用行为(fail-open 机制) | 请求路径请参阅 [`ARCHITECTURE.md`](./ARCHITECTURE.md),确切的加密协议和 paste 格式 v1(包括固定的测试向量)请参阅 [`SPEC.md`](./SPEC.md)。
HTTP API API 仅处理密文——加密在客户端 `POST` 之前完成, key fragment 永远不会出现在任何请求中。详情请见 [`SPEC.md`](./SPEC.md) §10。 | 方法 & 路径 | 用途 | 成功 | 错误 | | --- | --- | --- | --- | | `POST /api/paste` | 创建一个 paste(格式 v1 JSON) | `201` | `400` 无效 · `413` 过大 · `429` 触发速率限制 | | `GET /api/paste/:id` | 获取一个 paste(消耗一次销毁机会) | `200` | `404` 不存在/已过期 · `410` 已销毁 | | `GET /api/paste/:id?meta=1` | 查看销毁头信息但不消耗 | `200` | `404` 不存在 · `410` 已销毁/已过期 | | `DELETE /api/paste/:id` | 配合 `X-Delete-Token` header 进行删除 | `200` | `400` 缺少 token · `403` token 错误 · `404` 不存在 | 删除 token 存放在 header 中——绝不在 URL 中——因此它不会出现在请求日志中; 服务器仅存储并比对其 SHA-256 值。
项目布局 ``` public/ static frontend (CSP-clean; served by Workers Static Assets) index.html css/styles.css js/*.js fonts/*.woff2 img/ (favicon.svg + png fallbacks + wordmark[-dark].svg) _headers robots.txt favicon.ico opengraph.png .well-known/security.txt src/ index.js Worker: /api/paste routing + asset fallback burn-do.js BurnPaste Durable Object (atomic burn-after-read) lib/ ids, storage routing, rate-limit wrapper test/ vitest suites (run in workerd) + genvectors.mjs (vector regenerator) + vectors.expected.txt (pinned vector output, diffed in CI) tools/ verify-vectors.py — independent Python cross-check of the frozen vectors cli/ the npm-published CLI client (own package.json + Node-environment tests; vendor/ mirrors public/js/{bytes,format,crypto,qrcode}.js, drift-tested) SPEC.md SECURITY.md ARCHITECTURE.md CHANGELOG.md CONTRIBUTING.md CODE_OF_CONDUCT.md LICENSE ``` `public/js/{bytes,crypto,format,markdown}.js` 是共享的:浏览器将它们作为静态 资源导入,而 Worker 打包了相同的文件,因此 paste 格式具有单一事实来源。
## 局限性 其中大多数是刻意的范围选择,而不是 bug。在依赖 binthere 之前请了解它们: - **并非匿名或无元数据。** 服务器能看到 IP、时间、密文大小以及 非机密的 `adata`(IV、KDF 参数、格式标志)。它唯一不能读取的是你的*明文* ([`SECURITY.md`](./SECURITY.md) §3)。 - **无法防御被入侵的部署环境。** 解密由服务器传递的 JavaScript 执行, 因此恶意或被黑客入侵的主机可能会提供泄露你 key 的代码。浏览器内的端到端(E2E) 加密必须信任源站([`SECURITY.md`](./SECURITY.md) §4)。 - **丢失链接即丢失笔记。** 没有账号,也没有服务器端索引——id + key 仅存在于 你分享的 URL 中。任何人,包括你自己,都无法恢复或列出 paste。
局限性 - **销毁密码可被离线暴力破解。** 非消耗性的窥探(peek)会返回被包装的 key,因此可以在单次读取之前验证密码——已经掌握该 URL 机密的人可以在不消耗笔记的情况下猜测弱密码。请使用强密码 ([`SPEC.md`](./SPEC.md) §8 记录了这种权衡)。 - **密码 KDF 是 PBKDF2-SHA256**(31 万次迭代),而不是内存密集型 KDF。Argon2id 已在 路线图中。 - **UI 将过期时间固定为 24 小时且仅限一次查看。** 传输格式支持更多功能;只是 隐藏了控制选项。 - **仅支持英语。** - **速率限制器采用 fail-open 策略**——它是为了缓解滥用,而不是访问控制。 - **规范 URL 硬编码**到了原始部署环境中;自托管时请更新它们 (参见[部署](#deployment))。
## 常见问题
运营方能阅读我的笔记吗? 不能。内容在上传前已通过你浏览器中的 AES-256-GCM 进行了加密;服务器仅存储 密文和非机密元数据。解密 key 存放在 URL 的 fragment 中,而 浏览器永远不会将这部分发送给服务器。服务器*能*看到的(IP、时间、大小)在 [`SECURITY.md`](./SECURITY.md) §3 中有详细说明。
我丢失了链接——笔记还能恢复吗? 不能。系统没有账号,也没有服务器端索引;paste 的 id 和解密 key 仅存在于 URL 中。没有它,密文是无法恢复的——这是设计使然。
为什么我的链接提示“已过期或已被打开”? 每条笔记都是一次性查看的:第一个阅读者会以原子方式消耗它,之后的 所有人(包括你自己,如果你先打开了自己的链接)都会得到 `410 Gone`。即使 从未被打开,笔记也会在 24 小时后自动删除。
添加密码会让链接变得可以明文发送吗? 这会有所帮助——密码被混入到了 key 派生过程中,因此仅凭链接无法解密。 但是,持有链接的人可以在不消耗笔记的情况下离线测试密码,因此弱密码 只能减缓他们的速度。请使用强密码,并通过其他渠道发送它 ([`SPEC.md`](./SPEC.md) §8)。
接收方需要什么条件? 只需链接和任何现代浏览器——Web Crypto(`SubtleCrypto`)是唯一的条件。 无需账号、扩展程序或应用程序。
我可以通过脚本或 CLI 创建 paste 吗? 可以——官方 CLI 已发布到 npm:`npm install -g binthere`(或 `npx binthere`)。 它使用与 Web 客户端相同的固定协议,并针对相同的测试向量进行了测试; 请参阅 [CLI 部分](#cli)。第三方客户端也是可行的:HTTP API 仅接受 paste 格式 v1 的密文,并且 [`SPEC.md`](./SPEC.md) 中固定的测试向量使得 独立实现具有可验证性。
## 路线图 大致按优先级排列: - [x] **官方 CLI 客户端** —— 已发布;在 npm 上名为 [`binthere`](https://www.npmjs.com/package/binthere)(参见 [CLI](#cli)) - [ ] **Argon2id** 作为与 PBKDF2 并列的版本化密码 KDF 选项(spec 优先:先定测试向量再写代码) - [ ] **文件附件** —— 加密的二进制数据块,带有大小限制(大文件可能使用 R2) - [ ] CI 中的 **无头浏览器 CSP + 渲染测试**(Playwright),断言在 创建/查看/销毁的整个流程中没有出现 CSP 违规 ## 安全 binthere 是一款对安全性高度敏感的加密应用。威胁模型、明确的非目标 以及漏洞报告流程位于 [`SECURITY.md`](./SECURITY.md) 中;冻结的 协议和 paste 格式位于 [`SPEC.md`](./SPEC.md) 中。 ## 技术栈 - [Cloudflare Workers](https://workers.cloudflare.com/) —— 静态资源、KV、Durable Objects、原生速率限制 - 原生 JavaScript(原生 ES 模块)—— 前端无框架、无打包工具 - [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) —— AES-256-GCM、PBKDF2-SHA256 - [Vitest](https://vitest.dev/) —— 在真实的 `workerd` runtime 中的 Worker 套件,Node 中的 CLI 套件 - [ESLint 9](https://eslint.org/) —— 扁平化配置 ## 鸣谢 - [PrivateBin](https://privatebin.info) —— 本项目正是对其零知识 pastebin 模型进行的 一次彻底重构 - [qrcode-generator](https://github.com/kazuhikoarase/qrcode-generator),作者 Kazuhiko Arase (MIT 许可证)—— 已内置于 `public/js/qrcode.js` 中,用于离线且符合 CSP 安全规范的 QR 渲染 - [Newsreader](https://fonts.google.com/specimen/Newsreader),作者 Production Type, [Geist](https://vercel.com/font),作者 Vercel,以及 [JetBrains Mono](https://www.jetbrains.com/lp/mono/),作者 JetBrains(均为 SIL OFL 1.1 许可证)—— 自托管于 `public/fonts/`;许可证文本位于 [`public/fonts/THIRD-PARTY-NOTICES.md`](./public/fonts/THIRD-PARTY-NOTICES.md) ## 许可证 [MIT](./LICENSE) © 2026 nxfu

生而为一对一分享,而后归于遗忘。
如果 binthere 对你有帮助,请考虑给它点个 ⭐ ——这能帮助更多人发现它。

标签:MITM代理, 数据可视化, 程序员工具, 自定义脚本