安全防护设计:全方位保护你的数据
作为一款自托管应用,MarkStackAI 将安全性作为核心设计原则之一。你的书签、笔记和个人数据存储在自己的服务器上,这本身就是一种隐私保护。但仅靠自托管还不够,应用本身也必须从多个层面构建防御体系。本文详细介绍 MarkStackAI 经过全面安全审计后的安全架构设计。
密码安全:bcrypt 哈希与 DoS 防护
用户密码在存储前经过 bcrypt 算法哈希处理。bcrypt 是目前业界公认的密码哈希标准之一,它内置了自适应的计算成本因子(cost factor),随着硬件性能提升可以调高强度,确保暴力破解始终不可行。
然而 bcrypt 本身存在一个鲜为人知的风险:由于其计算开销较高,攻击者可以发送超长密码(如 1MB)来消耗服务器 CPU 资源,造成拒绝服务。MarkStackAI 在登录和注册的 Schema 层即限制密码最大长度为 128 字符,从输入验证层面阻断这一攻击向量。
时序攻击防护
当用户名不存在时,如果服务器直接返回"用户不存在",攻击者可以根据响应时间的差异来枚举有效用户名。MarkStackAI 在用户名不存在的情况下,仍然执行一次 dummy bcrypt 运算,使得登录成功与失败的响应时间基本一致,从而抵御时序攻击。同时,错误信息统一返回"用户名或密码错误",不区分具体原因。
JWT 认证机制
MarkStackAI 使用 JSON Web Token(JWT)进行用户认证,采用 PyJWT 库(迁移自已停止维护的 python-jose)。Token 的关键设计决策包括:
- 30 天有效期:平衡安全性与用户体验,避免频繁重新登录
- 仅包含 user_id:JWT payload 中不存储 role 等冗余信息,权限查询走数据库实时校验,避免角色变更后旧 token 仍持有过时权限
- 强密钥要求:生产环境中若检测到 SECRET_KEY 为默认弱密钥(如 "your-secret-key"),应用将拒绝启动并抛出
SystemExit
# 所有 401 错误统一返回同一消息,防止信息泄露
raise HTTPException(
status_code=401,
detail="认证失败"
)
RBAC 角色权限控制
系统定义了 admin 和 member 两种核心角色。API 层面的权限控制通过 FastAPI 的依赖注入实现:每个需要认证的端点都声明 current_user 依赖,管理端点额外校验角色。注册接口使用独立的 PublicSignupSchema(不包含 role 字段),从根本上防止角色注入攻击。
SSRF 防护:服务端请求伪造
MarkStackAI 的链接健康检测功能需要从后端发起 HTTP 请求检查书签 URL 是否可达。这一功能天然存在 SSRF 风险——攻击者可能提交内网地址(如 http://192.168.1.1)来探测内部网络。
防护策略采用多层校验:
- URL 解析验证:仅允许 http/https 协议,拒绝 file://、ftp:// 等
- DNS 解析验证:将主机名解析为 IP 后,检查是否属于私有地址段(10.x、172.16-31.x、192.168.x、127.x、169.254.x 等)
- 重定向链跟踪:不使用 HTTP 库的自动重定向,而是手动跟随每一跳(最多 5 跳),每次重定向目标都重新执行
_is_safe_url()校验,防止通过重定向绕过保护
XSS 防护:跨站脚本攻击
MarkStackAI 的笔记系统支持 Markdown 渲染,这意味着用户内容可能包含 HTML 标签。所有通过 v-html 渲染的内容,无论是笔记预览、文章详情还是代码高亮,都在渲染前经过 DOMPurify 消毒处理。DOMPurify 是业界标准的 HTML 消毒库,能有效移除所有潜在的恶意脚本标签和事件处理器。
此外,后端通过 HTTP 响应头设置了 Content Security Policy(CSP):
Content-Security-Policy: default-src 'self'
Strict-Transport-Security: max-age=31536000
CSP 限制页面只能加载同源资源,即使 XSS 成功注入了脚本标签,浏览器也会拒绝执行外部恶意脚本。
SQL 注入防护
MarkStackAI 使用 SQLAlchemy ORM 操作数据库,所有查询均通过参数化方式构建,天然免疫 SQL 注入。对于搜索功能中使用的 LIKE 查询,系统对用户输入中的 %、_、\ 通配符进行转义后再拼接 LIKE 模式,防止攻击者通过通配符绕过查询逻辑。
速率限制
通过 slowapi 中间件对关键端点实施速率限制:
- 登录:5 次/分钟——防止暴力破解
- 注册:3 次/小时——防止批量注册
- 分享页密码验证:5 次/分钟——防止密码穷举
- 笔记分享密码验证:5 次/分钟——同上
在 Nginx 层面还设置了额外的速率限制:auth 端点 5 请求/分钟,通用 API 30 请求/秒,形成双层防护。
文件上传校验
书签导入功能允许上传 HTML 文件。系统对上传文件实施 5MB 大小限制,并校验文件内容是否为合法的 HTML 格式。错误信息经过脱敏处理,不会向客户端暴露服务器文件路径或内部堆栈信息。备份恢复功能额外实施了 Zip Slip 路径校验,通过 resolve() 验证解压路径不会逃逸到目标目录之外。
安全头与统一错误处理
除了前面提到的 CSP 和 HSTS 之外,生产环境还关闭了 Swagger/ReDoc/OpenAPI 文档端点,避免暴露 API 结构。Nginx 配置了 server_tokens off 隐藏版本信息。
所有认证相关的错误消息统一返回"认证失败",不再区分"令牌未提供"、"令牌已过期"、"用户不存在"等具体原因,从根本上防止用户枚举。注册时,无论用户名是否已存在,都返回相同的错误提示文案。
安全不是一次性工程,而是持续的过程。MarkStackAI 已通过全面安全审计,但安全防护需要持续更新。自托管的优势在于,你可以根据自己的安全需求随时调整防护策略,而不是被动等待云服务商的安全修复。