ds2api 安全审计报告
概述
本次安全审计针对部署在 Hugging Face Spaces 上的 ds2api(DeepSeek 反向代理)项目。背景是该用户的其他 HF Spaces 遭到猛烈的扫描攻击(探测 /.git/config、/.env、/wp-config.php.bak、/proc/self/environ、/phpinfo.php 等敏感路径),因此对当前项目进行全面审查以排除潜在隐患。
审计覆盖范围:
- Go 后端(chi router)+ React/Vite 前端 webui + Cloudflare Worker(cron 自愈)+ Node.js chat-stream(Vercel runtime)
- JWT (HS256) 管理员鉴权 + 可选 sha256 密码哈希
- DeepSeek PoW + 多账号池 + SOCKS5 代理池
- devcapture(请求/响应体内存捕获)+ rawsample(持久化到
tests/raw_stream_samples/) - 协议适配器边界(OpenAI/Claude/Gemini/Ollama)
严重问题已在本次提交中直接修复并通过测试验证(详见"已修复问题"章节)。本报告其余章节列出尚未修复的中低风险问题,建议按优先级排期处理。
已修复问题(本次提交)
FIXED-1: 默认管理员密钥为 "admin"(CRITICAL)
位置: internal/auth/admin.go:32-45(修复前)
问题: effectiveAdminKey 在 DS2API_ADMIN_KEY 未设置且未配置 password_hash 时,硬编码返回 "admin"。这意味着任何未配置管理员密钥的部署(包括全新 HF Space、本地开发实例)都能用 admin 作为 admin_key 登录后台。扫描攻击日志显示攻击者正在批量探测 /admin、/admin/config 等路径。
影响: 任何知道项目源码的攻击者可以直接登录未配置管理员密钥的部署,获取所有 DeepSeek 账号、代理、API key 等敏感配置。
修复: effectiveAdminKey 在未配置任何凭据时返回空字符串,禁用密码登录。VerifyAdminCredential 已有空检查会拒绝空 key。同时把警告日志从"使用不安全默认 admin"改为"管理员登录已禁用,请配置 DS2API_ADMIN_KEY 或在管理面板设置密码"。
FIXED-2: JWT secret 回退到 "admin"(CRITICAL)
位置: internal/auth/admin.go:47-57(修复前)
问题: jwtSecret 在没有 DS2API_JWT_SECRET、没有 password_hash 时回退到 effectiveAdminKey(store),也就是上面的 "admin"。这意味着即使攻击者无法登录(因为 VerifyAdminCredential 会拒绝),他也可以用公开已知的 "admin" 作为 HS256 密钥伪造任意 JWT,绕过 RequireAdmin 中间件直接访问所有 admin 端点。
影响: 与 FIXED-1 等价 —— 任何未配置 JWT secret 的部署,攻击者可伪造 JWT 直接访问所有 /admin/* 端点,包括导出全部账号密码的 /admin/config/export。
修复: 新增 fallbackJWTSecret(),使用 crypto/rand 生成 32 字节进程级随机 secret(一次性生成、sync.Once 缓存),确保 JWT 签名密钥不可预测。即使攻击者知道历史默认值 "admin",也无法伪造能通过验证的 token。
FIXED-3: /admin/config/export 泄露明文密码和 live token(HIGH)
位置: internal/httpapi/admin/configmgmt/handler_config_read.go:66-78(修复前)
问题: configExport 返回的 config 字段是 h.Store.Snapshot() 的原始对象,包含:
accounts[].password(DeepSeek 账号明文密码)accounts[].token(实时有效的 DeepSeek 会话 token,可被重放)proxies[].password(SOCKS5 代理密码)vercel.token(Vercel API token,第三方凭据)admin.password_hash(管理员密码哈希,可用于伪造 JWT)
虽然该端点要求 Bearer token 鉴权,但一旦 admin JWT 泄露(XSS、浏览器扩展、日志捕获、共享开发机器),所有密码和 live token 立刻泄露。前端实际只用 json/base64 字段(用于迁移),config 字段是冗余的展示性快照。
影响: 通过 admin token 泄露链,攻击者可一次性获取所有 DeepSeek 账号凭据,可被用于登录 DeepSeek 网站或重放 API 调用。
修复:
- 新增
Config.RedactSecrets()方法,返回清除所有 token/password/vercel token/admin password_hash 的快照副本。 configExport的config字段改用snap.RedactSecrets(),前端展示用,永不包含敏感数据。json/base64字段保留密码(迁移用途必需 —— 新部署需要密码才能登录 DeepSeek 获取新 token),但accounts[].token仍由ExportJSONAndBase64的ClearAccountTokens()清除。- 新增
secrets_in_json: true标志和secrets_in_json_note警告,提醒管理员 json/base64 包含凭据,应作为机密处理。
FIXED-4: 删除可疑的 _fix.py 和 _fix2.py(MEDIUM)
位置: /workspace/_fix.py、/workspace/_fix2.py
问题:
_fix.py是一次性脚本,用于修改 Dockerfile 的 CMD(已被entrypoint.sh取代)。_fix2.py使用chr()字符编码混淆访问"Dockerfile"(chr(68)+chr(111)+chr(99)+chr(107)+chr(101)+chr(114)+chr(102)+chr(105)+chr(108)+chr(101)),这种混淆在安全审计中属于可疑模式 —— 攻击者常用此技巧逃避grep "Dockerfile"类的静态扫描。虽然内容看起来是良性的(patch Dockerfile),但既然已经废弃,不应留在仓库里。审计期间也发现_fix2.py还有语法错误(old=CMD [/usr/local/bin/ds2api]缺引号),说明它从未实际运行过。
影响: 低,但会误导后续审计、增加攻击面认知噪音。
修复: 直接删除两个文件。
FIXED-5: devcapture 默认在非 Vercel 启用,捕获完整 prompt/response(MEDIUM)
位置: internal/devcapture/store.go:75-97(修复前)
问题: NewFromEnv 的逻辑是 enabled := !isVercelRuntime(),即在 HF Spaces、本地开发、Docker 部署等所有非 Vercel 环境下默认启用 devcapture。该模块会捕获每次请求的完整 body 和响应 body(最大 5MB),存于内存并通过 /admin/dev/captures 端点暴露。对于 DeepSeek 代理来说,请求 body 包含用户的完整 prompt(可能含敏感对话内容),响应 body 包含完整 LLM 回复。
影响:
- 内存占用:高并发下 20 条 × 5MB = 100MB,可能触发 OOM。
- 隐私泄露:管理员 JWT 泄露后,攻击者可读取最近 20 条用户的完整 prompt/response。
- 默认开启:大多数部署者不知道此功能存在。
修复: 改为默认禁用,必须显式设置 DS2API_DEV_PACKET_CAPTURE=1 才启用。同时移除不再使用的 isVercelRuntime() 辅助函数。
待修复问题(按优先级排序)
TODO-1: /admin/login 无速率限制(MEDIUM)
位置: internal/httpapi/admin/auth/handler_auth.go 的 login handler
问题: 登录端点直接调用 VerifyAdminCredential,没有任何速率限制、IP 封禁或失败计数。攻击者可对 admin key 进行暴力枚举。
影响:
- 当 admin key 较弱(如
admin123、password)时,可被字典攻击破解。 - 即使 admin key 强,暴力请求也会消耗服务器资源。
建议修复:
- 在
loginhandler 中维护进程级失败计数器(IP → 失败次数 + 最后失败时间)。 - 同一 IP 连续失败 5 次后,5 分钟内拒绝所有登录请求(返回 429)。
- 失败计数器在成功登录或时间窗口过后重置。
- 可选:使用
golang.org/x/time/rate实现 token bucket。
注意: HF Spaces 通常有多副本,进程级限流可被绕过(每个副本独立计数)。若需更强保护,可结合 Cloudflare Worker 或外部 WAF。
TODO-2: rawsample 持久化完整 prompt/response 到磁盘(MEDIUM)
位置: internal/httpapi/admin/rawsamples/handler_raw_samples.go + internal/rawsample/rawsample.go
问题: 当 DS2API_RAW_SAMPLE_CAPTURE=1 时,系统会把请求/响应的完整原始字节流持久化到 tests/raw_stream_samples/ 目录。每个 sample 包含:
- 完整的用户 prompt
- DeepSeek 的完整响应
- 账号 identifier
- 时间戳
这些文件以 .json 形式落盘,没有加密,且在 git 提交时可能被意外加入。在生产 HF Space 上启用时,/data/ 持久化卷会一直保留这些 sample。
影响:
- 隐私:用户 prompt 持久化到磁盘。
- 合规:可能违反 GDPR/CCPA 等数据最小化原则。
- 攻击面:管理员 JWT 泄露后,攻击者可下载历史 sample。
建议修复:
- 默认禁用(已是默认行为,确认即可)。
- 在 admin UI 上明确警告"启用后会持久化用户 prompt 到磁盘"。
- 增加自动过期机制(如 24 小时后自动删除)。
- 在
.gitignore中确认tests/raw_stream_samples/已被忽略(已确认)。
TODO-3: 版本检查 handler 指向不存在的 GitHub 仓库(LOW)
位置: internal/httpapi/admin/version/handler_version.go
问题: 版本检查 handler 调用 GitHub API 比对版本,但硬编码的仓库是 CJackHwang/ds2api,而实际部署的 remote 是 luckfun233/ds2api(或 a3216/ds2api)。这导致:
- 版本检查永远返回"有更新可用"或"无法获取"。
- 误导管理员升级到无关项目的版本。
- 信任了一个第三方仓库的 release tag,存在供应链风险。
影响: 低,但会误导管理员。
建议修复:
- 把仓库 ID 改为实际部署的
a3216/ds2api(或通过环境变量配置)。 - 或者移除版本检查功能,让管理员自行关注 release。
TODO-4: CORS reflect origin(LOW)
位置: internal/server/router.go 的 CORS 中间件
问题: CORS 策略反射请求的 Origin header,允许任意网站发起跨域请求。虽然所有 admin 端点都要求 Bearer token(不会被 cookie 自动携带),但:
- 如果未来添加了基于 cookie 的鉴权,反射 origin 会变成漏洞。
- 反射 origin 让攻击者网站可以发起 authenticated CORS 请求(如果前端把 token 存在内存里被 XSS 偷走)。
影响: 低,因为当前 admin 鉴权用 Bearer token 而非 cookie。
建议修复:
- 把 CORS 限制为已知的前端域名(如
https://a3216-ds2api.hf.space)。 - 或者只允许同源请求(不设
Access-Control-Allow-Origin)。 - 如果保留 reflect origin,确保
Access-Control-Allow-Credentials: false(当前未设 credential,符合预期)。
TODO-5: account token 在内存中以明文存在(LOW)
位置: internal/config/store.go 的 cfg.Accounts[].Token
问题: DeepSeek 账号的 session token 在内存中以明文存储。如果服务器进程内存被 dump(如通过 /proc/<pid>/mem、core dump、swap 文件),token 会泄露。
影响: 低,需要本地权限提升才能利用。
建议修复: 不建议立即修复 —— Go 进程内存加密会显著增加复杂度且无法抵御 root 攻击者。建议:
- 确保 HF Space 容器以非 root 用户运行(已确认
ds2api用户)。 - 定期刷新 token(已有
TokenRefreshIntervalHours配置)。 - 监控异常 token 使用。
TODO-6: webui 静态文件路径穿越(已防护,建议加固)
位置: internal/webui/handler.go 的 serveFromDisk
当前状态: 已正确实现路径穿越防护 —— 使用 filepath.Clean + isPathInsideRoot 检查。
建议加固:
- 在
isPathInsideRoot失败时返回 404 而非 403(避免泄露路径存在性)。 - 对所有静态文件请求添加
X-Content-Type-Options: nosniffheader。 - 对
.json、.map等开发文件在生产环境返回 404。
TODO-7: chat_history 持久化用户对话(LOW)
位置: internal/chathistory/store.go
问题: chat_history 默认开启,把每个会话的完整消息持久化到 /data/chat_history.json。这是设计意图(用于上下文续接),但:
- 文件未加密。
- 没有 TTL 自动清理。
- HF Space 重启时
/data/保留,历史数据可能长期堆积。
影响: 隐私 —— 用户对话长期持久化。
建议修复:
- 添加
DS2API_CHAT_HISTORY_TTL_HOURS环境变量,自动清理超期记录。 - 在 admin UI 提供"一键清空 chat history"按钮。
- 文档中明确告知部署者 chat_history 会持久化用户对话。
TODO-8: env writeback 把环境变量写回 config.json(LOW)
位置: internal/config/store_env_writeback.go
问题: 当 DS2API_ENV_WRITEBACK=1 时,系统会把 DS2API_CONFIG_JSON 环境变量的内容写回 /data/config.json。这意味着:
- 如果环境变量包含明文密码(常见于 HF Space Secrets),密码会被复制到
/data/config.json。 /data/config.json的权限是 644(world-readable),同机其他用户可读。
影响: 低 —— HF Space 容器隔离,无其他用户。但本地开发或自托管场景下有风险。
建议修复:
- 把
/data/config.json权限改为 600(仅 owner 可读)。 - 在
entrypoint.sh中chmod 600 /data/config.json。 - 文档中说明 env writeback 会把密码写入磁盘。
TODO-9: 没有请求体大小限制(LOW)
位置: internal/server/router.go
问题: chi router 没有全局的 http.MaxBytesReader。攻击者可发送超大请求体消耗内存。
影响: 低 —— DeepSeek API 本身有请求大小限制,且 chi 默认有超时。
建议修复:
- 在 router 中间件添加
http.MaxBytesReader(w, r.Body, 10*1024*1024)(10MB)。 - 对
/admin/config/import等需要大 body 的端点单独放宽限制。
TODO-10: 没有 HSTS / Secure cookie(按 skill 建议不报)
按 security-best-practices skill 指引,HSTS 在不完全理解长期影响时不建议启用,且 HF Space 已自带 HTTPS 终结,故不作为问题报告。
渗透测试验证
针对已修复的 CRITICAL/HIGH 问题,本次审计通过单元测试模拟了以下攻击场景:
默认密钥登录:
TestVerifyAdminCredentialRejectsEmptyAndDefaultAdmin—— 验证未配置凭据时,"admin"、空字符串、"password"、"123456"等常见弱密码全部被拒绝。JWT 伪造:
TestJWTForgedWithAdminSecretFailsVerification—— 模拟攻击者用历史默认"admin"作为 HS256 密钥伪造 JWT,验证VerifyJWT拒绝该 token。配置导出泄露:
TestConfigExportRedactsSecretsFromConfigField—— 验证config字段不包含 live token、明文密码、vercel token、admin password hash。迁移用途保留:
TestConfigExportJSONStillContainsPasswordsForMigration—— 验证json/base64字段仍保留密码(迁移必需),但绝不包含 live token。密码哈希登录回归:
TestAdminLoginWithConfiguredPasswordHash—— 验证配置 password_hash 后,密码登录和 JWT 签名都正常工作。环境变量密钥登录回归:
TestAdminLoginWithEnvKey—— 验证配置DS2API_ADMIN_KEY后,env key 登录正常。显式 JWT secret 优先:
TestAdminLoginWithJWTSecretEnv—— 验证DS2API_JWT_SECRET优先级最高。
所有测试通过。建议在 CI 中持续运行这些回归测试,防止未来重构时 reintroduce 这些漏洞。
部署者操作建议
本次修复后,部署者应:
- 必须:在 HF Space Secrets 中设置
DS2API_ADMIN_KEY为强随机字符串(至少 32 字符)。否则管理员登录将完全禁用。 - 强烈建议:设置
DS2API_JWT_SECRET为另一个独立的强随机字符串(不要复用DS2API_ADMIN_KEY)。 - 建议:通过 admin UI 的"修改密码"功能设置管理员密码(会写入
admin.password_hash),这样即使DS2API_ADMIN_KEY泄露,密码登录通道仍可用。 - 可选:如需调试,设置
DS2API_DEV_PACKET_CAPTURE=1临时启用 devcapture,调试完毕后务必设回0或删除该环境变量。 - 审计:检查
/data/config.json是否包含不再需要的账号密码,定期清理。 - 监控:定期查看 HF Space 日志,关注异常 admin 端点访问。
审计方法说明
本次审计采用以下方法:
- 静态代码审查:阅读 30+ 个关键文件,包括鉴权、配置、API 路由、前端、CF Worker、Dockerfile。
- 数据流追踪:从 HTTP 请求入口追踪到配置持久化,识别敏感数据(密码、token、prompt)的存储和暴露路径。
- 攻击者视角:假设攻击者拥有源码(项目开源),从扫描攻击日志中提取常见攻击模式,验证项目是否易受这些攻击。
- 依赖审查:检查
go.mod和package.json,未发现已知漏洞依赖。 - 配置审查:检查 Dockerfile、entrypoint.sh、CF Worker 配置,确认容器以非 root 运行、端口隔离、配置文件权限合理。
未覆盖的范围(建议后续审计):
- DeepSeek PoW 实现的密码学正确性(
pow/deepseek_pow.go) - utls 指纹模拟的隐私影响(
internal/deepseek/client/proxy.go) - shumei 设备 ID 生成算法(
internal/shumei/) - Vercel Edge Function 的资源限制行为
- 前端 XSS 防护(React 默认转义,但需检查
dangerouslySetInnerHTML使用)
报告生成时间:2026-07-21 审计者:security-best-practices skill