设计具备客户端加密的零信任个人信息管理器
Source: Dev.to
我是一名就读于 Amrita Vishwa Vidyapeetham 的 B.Tech 计算机科学本科生,热衷于构建注重隐私的系统,并通过端到端部署真实软件来学习。
动机
InfoStuffs 的诞生并非出于想要打造另一个生产力应用的欲望。它始于我妹妹的一个非常具体的用户需求。她需要一个数字空间来组织个人文档和敏感笔记,但拒绝使用诸如 Google Keep 或 Notion 等标准云服务。
她的限制看似简单,却在技术上颇具挑战性:她想要云的便利,却不想把明文数据交给云提供商。
这个挑战成为了 InfoStuffs 的基石。我的目标从构建一个简单的网页应用转变为设计一个Zero‑Trust Information Management System,默认以隐私为先。
问题陈述
现代生产力工具通常分为两类:
| 类别 | 示例 | 缺点 |
|---|---|---|
| 便利的 SaaS | Notion、Google Keep | 将用户数据以明文存储或使用服务器管理的密钥,使数据易受内部泄漏或数据库泄露的影响。 |
| 自托管 | Obsidian、Nextcloud | 提供强大的隐私保护,但在多个设备之间访问和维护困难。 |
InfoStuffs 弥合了这一鸿沟。系统必须 足够安全,以至于即使服务器端完全被攻破也只能得到垃圾数据,同时能够通过任何设备的标准网页浏览器访问。
高层架构
为满足这些约束,InfoStuffs 采用了一个解耦的、云原生的架构,职责划分明确。
| 层级 | 技术 / 细节 |
|---|---|
| 前端 | React (Vite) + Material‑UI。负责 UI 渲染 以及客户端加密操作(加密/解密)。 |
| 后端 | Node.js + Express,遵循 混合架构。 |
| 本地开发 | 完全 Docker 化的单体容器,确保与生产环境依赖一致的统一环境。 |
| 生产部署 | 部署到 Vercel 作为 无状态 Serverless Functions,使 API 在空闲时可缩减至零(成本高效),同时保持单一的 Express 代码库。 |
| 数据库 | MongoDB Atlas – 存储加密的元数据和密文。 |
| 身份验证 | Clerk – 将身份管理外包,降低认证流程(MFA、会话管理)的攻击面。 |
| 存储 | Supabase Storage – 仅用于通过签名 URL 隔离二进制对象(图片、PDF)。 |
Source:
设计即安全:零信任金库
安全不是可选功能;它是主要的架构约束。
1. 静态密钥的问题
在最初的设计中,我使用了存放在服务器环境变量 (VITE_SECRET_KEY) 中的静态加密密钥。很快我意识到这是一个致命缺陷:如果攻击者或被入侵的托管环境泄露了环境变量,他们就可以解密所有用户的数据。该密钥是 可见的,违背了零信任的核心概念。
2. 解决方案:用户派生的密码学
为了解决这个问题,我完全移除了静态密钥,并在客户端实现了 PBKDF2(基于密码的密钥派生函数 2)。
- 当用户登录时,输入 金库密码。该密码既不被传输也不被持久化,只在客户端内存中短暂存在。
- 浏览器运行 PBKDF2,在内存中派生出临时的 256 位 AES 密钥。
- 该密钥在任何网络请求形成之前,对笔记、标题和文件路径进行加密。
服务器仅看到(并存储)密文。如果数据库管理员(我)查看数据,只会看到不可读的字符串。
PBKDF2 的参数在抵御暴力破解攻击与在低功耗客户端设备上可接受的延迟之间取得了平衡。
3. 对媒体的瞬时访问
文件存储方面,我完全避免使用公共存储桶。
- 加密路径: 数据库存储指向文件路径的 加密 字符串(例如,
"user/123/image.jpg"被加密后存储)。 - 按需访问: 当用户解锁金库时,客户端解密路径并向 Supabase 请求 签名 URL。
- 时限限制: 该 URL 的有效期恰好为 60 秒,随后即失效。这防止了“链接共享”泄漏,即使 URL 被拦截,也会在极短时间内失效。
基础设施演进:解决成本问题
一次最有价值的学习经验来自于将基础设施适配到真实的成本约束中。
Phase 1: The “Enterprise” Trap (GCP)
我最初的部署使用了 Google Cloud Platform,配合 Cloud Run 和 Cloud Build。虽然这是一套行业标准的 “Enterprise” 方案,但对个人项目来说却带来了显著的问题:
- 高成本: 负载均衡器、容器镜像仓库存储以及计算时间的费用迅速累积。
- 复杂性: 为一个简单的应用管理 IAM 角色和构建触发器显得大材小用。
Phase 2: The Hybrid “Serverless Monolith” (Vercel)
内容继续在 Phase 2 中展开——描述如何将应用重构为单一代码库,并在 Vercel 上以无服务器函数的形式部署,利用其成本高效的弹性伸缩模型,同时保持相同的开发工作流。
要点
- Zero‑Trust by Design:永不存储或传输明文密钥;在客户端派生密钥并仅保存在内存中。
- Cost‑Effective Architecture:单一的 Express 代码库即可同时服务本地 Docker 开发和 Vercel 无服务器生产环境,显著降低运维开销。
- Ephemeral Media Access:使用短 TTL 的签名 URL 在不牺牲可用性的前提下保护二进制资产。
InfoStuffs 演示了即使是个人开发者和小规模项目,也能实现以隐私为先、云原生的应用。
InfoStuffs – 零成本无服务器单体
概览
为消除部署费用,我将技术栈重新架构,使其 每月 $0 运行:
| 环境 | 方法 |
|---|---|
| 本地开发(Docker 化) | 单个 docker‑compose up 即可启动前端、后端和数据库服务。这保持了开发环境的隔离性,并能在任何机器上复现。 |
| 生产部署(无服务器) | 将 Express 应用重构为在 Vercel Serverless Functions 上运行,而不是持续运行的容器。 |
结果: 一个 Serverless Monolith —— 以传统单体方式开发(易于调试,基于 Docker 的本地运行),并以分布式函数方式部署。这样兼具两者优势:零基础设施管理 与 个人使用零成本。
我刻意避免使用微服务,因为业务领域尚未需要多个有界上下文;过早拆分只会增加复杂度和攻击面,却没有实际收益。
Source: …
技术挑战与解决方案
1. 环境变量可见性
依赖 .env 文件进行安全管理被证明存在问题。迁移到用户派生的密钥解决了该问题,但也带来了诸如“密码丢失”等边缘情况。
解决方案: 实现了 “核弹重置” 功能,让用户能够清除不可恢复的数据并重新开始,优先保证安全而非数据恢复。
2. Monorepo 构建上下文
Vercel 最初未能在 monorepo 中找到 vite.config.js。
解决方案: 在 Vercel 设置中显式指定 根目录,并重写构建命令,使依赖从正确的路径安装。
3. Docker 与 Serverless 路由不匹配
| 问题 | 细节 |
|---|---|
| 本地 Docker | Express 在内部处理所有路由。 |
| Vercel | 将 API 当作静态文件处理;对子路径的请求(例如 /api/info/nuke)在到达 Express 之前被 Vercel 的 404 处理程序拦截,导致浏览器报 CORS 错误。 |
解决方案: 创建了 混合路由策略:
- 添加了一个兼容 Vercel 的入口文件 (
api/info.js)。 - 配置
vercel.json重写规则,将所有子路由流量直接导入 Express 实例。
此桥接使相同代码能够在本地 Docker 中运行,也能作为 Vercel Function 在生产环境中运行,解决了 CORS 问题。
未来路线图
- Redis 缓存 – 减少对经常访问的(加密)元数据的数据库读取。
- React Native 移动应用 – 包装现有逻辑,以实现生物识别保险库解锁(Face ID),无需输入密码。
- 离线模式 – 利用 PWA 功能,只读访问已缓存的加密笔记。
结束语
InfoStuffs 不仅仅是一个记笔记的应用;它是对 Zero‑Trust Engineering 的一次实用探索。通过解决 数据可视化 和 云成本 的真实世界问题,我构建了一个隐私由数学而非政策强制执行的系统。它满足了真实用户的需求,同时也为全栈安全和 DevOps 提供了宝贵的学习经验。
- Repository:
- Live Deployment:
Note: 实时部署需要身份验证和保险库密码。核心安全属性在客户端强制执行,最好通过上述架构讨论来理解。

