一个自托管的临时共享空间服务。任意设备输入同一串空间密钥即可加入同一个临时空间,用来共享多段文本和文件;也可以额外设置空间密码提升端到端加密强度。所有使用均为端到端加密;服务端不记录任何信息,包括时间、动作和 IP。我们拒绝因为审查导致您的隐私信息泄漏。
- 空间密钥访问:同一空间密钥对应同一空间,大小写不敏感。
- 全量端到端加密:所有文本、文件内容和文件名元数据都在浏览器内加密后上传。
- 创建保护:创建新空间需要 Cloudflare Turnstile 人机验证。
- 加入保护:加入空间只允许进入已经存在且未过期的空间;不存在的空间密钥会返回错误,不会自动创建。
- 多段文本:每次保存会新增一个文本卡片,支持一键复制和删除。
- 文件共享:支持上传、单文件下载、删除。
- 一键导出:浏览器本地解密并打包为 zip,文本会保存为
clipboard-texts.txt。 - 立即销毁:手动删除当前空间的所有文本和文件。
- 手动续期:普通空间续期 10 分钟;扩容空间续期 24 小时。
- 匿名空间:所有空间都不写 app 审计日志,不记录任何信息,包括时间、动作和 IP。
- 隐私承诺:我们拒绝因为审查导致您的隐私信息泄漏。
空间密钥和扩容密钥都只允许数字和英文字母,服务端会统一转成大写后处理。空间密码是可选的端到端加密密钥,不发送给服务端,只保存在当前浏览器内存里;如果不填写空间密码,浏览器会使用空间码本身派生 E2EE 密钥。换设备加入同一空间时,必须使用同一个空间码和同一个空间密码设置。
| 状态 | 容量 | 默认过期/续期 |
|---|---|---|
| 普通空间 | 10MB | 10 分钟 |
| 使用有效扩容密钥 | 1GB | 24 小时 |
| 使用高级扩容密钥 | 4GB | 48 小时 |
扩容密钥只在创建空间时生效,用来提升容量和续期时长。每个扩容密钥最多同时维持 3 个未过期空间。空间过期后,同一个空间密钥不会在加入时自动创建,需要重新通过“创建空间”流程并完成人机验证。
生产环境应配置 Turnstile,创建新空间时会校验 token:
TURNSTILE_REQUIRED=true
TURNSTILE_SITE_KEY="0x..."
TURNSTILE_SECRET_KEY="0x..."同时配置 TURNSTILE_SITE_KEY 和 TURNSTILE_SECRET_KEY 时默认启用 Turnstile;只缺其中一个时不会在前端显示缺失 key 的验证框。本地开发可设置 TURNSTILE_REQUIRED=false。
cd clipboard
npm install
npm start打开:
http://127.0.0.1:8080
扩容密钥不写明文,只配置 SHA-256 哈希。服务端会先把用户输入转成大写,再计算 SHA-256。
生成哈希:
npm run hash-key -- ABC123配置示例:
EXPANSION_KEY_HASHES="e0bebd22819993425814866b62701e2919ea26f1370499c1037b53b9d49c2c8a:Admin"多个密钥:
EXPANSION_KEY_HASHES="hash1:Admin,hash2:Ops"本地开发可以把配置写进 .env:
cp .env.example .env然后编辑 .env。.env 已加入 .gitignore,不要提交;开源仓库只提交 .env.example。
npm start
npm run dev
npm test
npm run hash-key -- ABC123- 所有新写入的文本和文件必须带 E2EE 格式前缀;服务端会拒绝明文文本和未加密文件上传。
- 加密构造:WebCrypto
PBKDF2使用 HMAC-SHA-256、250000 次迭代和 saltclipboard:e2ee:v1:<spaceKey>,从“空间密码;若未填写则为空间码”派生不可导出的AES-GCM256-bit key;每段文本、文件内容和文件名元数据分别使用 96-bit 随机 IV 加密,GCM 认证标签为 128-bit。 - 空间密码不会发送给服务端,也不会写入 sessionStorage;没有空间密码时,知道空间码的人也拥有解密能力。
- 后量子边界:这个流程没有 RSA/ECDH 这类公钥交换,因此不暴露“录下今天的公钥握手、未来用量子计算解开会话密钥”的风险;实际抗离线猜测能力仍主要取决于空间码或空间密码的熵,以及 PBKDF2 参数带来的猜测成本。
- 服务端下载接口只返回密文字节;文本导出和全部导出都在浏览器本地解密后生成。
- 前端渲染保存文本时使用纯文本写入,不把用户文本作为 HTML 插入。
- 支持上传任意文件格式,包括
.exe、.js、.html等;服务端不按扩展名阻止文件,而是把上传内容当作不可解释的密文字节保存。 - 文件落盘统一使用服务端生成的 UUID
.bin文件名,下载统一以attachment和application/octet-stream返回,避免服务器或浏览器把上传内容当作可执行/可渲染内容处理。 - 开启 Helmet 安全响应头和严格 CSP。
- 空间密钥和扩容密钥都不以明文落盘;扩容密钥只在空间元数据里保存哈希,用于容量状态。
- 前端密钥输入框使用密码框显示。
Web App 型 E2EE 仍依赖前端代码分发可信:如果服务端在用户打开页面前已被控制,攻击者可能下发恶意 JavaScript 窃取加密密钥。生产环境建议继续放在 Nginx 后,只绑定本机端口,并保持部署链路和主机权限隔离。
推荐生产环境把 Node 服务绑定到 127.0.0.1,再用 Nginx、Caddy 或其它反向代理提供 HTTPS。
git clone https://github.com/Catherina0/clipboard.git
cd clipboard
npm ci --omit=dev
cp .env.example .env
nano .env示例 systemd 服务:
[Unit]
Description=Clipboard temporary sharing service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=clipboard
Group=clipboard
WorkingDirectory=/opt/clipboard
Environment=NODE_ENV=production
Environment=HOST=127.0.0.1
Environment=PORT=8080
Environment=CLIPBOARD_DATA_DIR=/opt/clipboard/data
ExecStart=/usr/bin/node /opt/clipboard/src/server.js
Restart=always
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ReadWritePaths=/opt/clipboard/data
[Install]
WantedBy=multi-user.target启动后验证:
curl http://127.0.0.1:8080/healthserver {
listen 443 ssl http2;
server_name clipboard.example.com;
client_max_body_size 4096m;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}也可以用 Docker 运行:
docker build -t clipboard:latest .
docker run -d \
--name clipboard \
-p 127.0.0.1:8080:8080 \
-v clipboard-data:/app/data \
--env-file .env \
--restart unless-stopped \
clipboard:latest应用日志取决于部署方式:
journalctl -u clipboard -f
docker logs -f clipboard