# 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 调用。 **修复**: 1. 新增 `Config.RedactSecrets()` 方法,返回清除所有 token/password/vercel token/admin password_hash 的快照副本。 2. `configExport` 的 `config` 字段改用 `snap.RedactSecrets()`,前端展示用,永不包含敏感数据。 3. `json`/`base64` 字段保留密码(迁移用途必需 —— 新部署需要密码才能登录 DeepSeek 获取新 token),但 `accounts[].token` 仍由 `ExportJSONAndBase64` 的 `ClearAccountTokens()` 清除。 4. 新增 `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 强,暴力请求也会消耗服务器资源。 **建议修复**: 1. 在 `login` handler 中维护进程级失败计数器(IP → 失败次数 + 最后失败时间)。 2. 同一 IP 连续失败 5 次后,5 分钟内拒绝所有登录请求(返回 429)。 3. 失败计数器在成功登录或时间窗口过后重置。 4. 可选:使用 `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。 **建议修复**: 1. 默认禁用(已是默认行为,确认即可)。 2. 在 admin UI 上明确警告"启用后会持久化用户 prompt 到磁盘"。 3. 增加自动过期机制(如 24 小时后自动删除)。 4. 在 `.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`)。这导致: 1. 版本检查永远返回"有更新可用"或"无法获取"。 2. 误导管理员升级到无关项目的版本。 3. 信任了一个第三方仓库的 release tag,存在供应链风险。 **影响**: 低,但会误导管理员。 **建议修复**: 1. 把仓库 ID 改为实际部署的 `a3216/ds2api`(或通过环境变量配置)。 2. 或者移除版本检查功能,让管理员自行关注 release。 ### TODO-4: CORS reflect origin(LOW) **位置**: `internal/server/router.go` 的 CORS 中间件 **问题**: CORS 策略反射请求的 `Origin` header,允许任意网站发起跨域请求。虽然所有 admin 端点都要求 Bearer token(不会被 cookie 自动携带),但: 1. 如果未来添加了基于 cookie 的鉴权,反射 origin 会变成漏洞。 2. 反射 origin 让攻击者网站可以发起 authenticated CORS 请求(如果前端把 token 存在内存里被 XSS 偷走)。 **影响**: 低,因为当前 admin 鉴权用 Bearer token 而非 cookie。 **建议修复**: 1. 把 CORS 限制为已知的前端域名(如 `https://a3216-ds2api.hf.space`)。 2. 或者只允许同源请求(不设 `Access-Control-Allow-Origin`)。 3. 如果保留 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//mem`、core dump、swap 文件),token 会泄露。 **影响**: 低,需要本地权限提升才能利用。 **建议修复**: 不建议立即修复 —— Go 进程内存加密会显著增加复杂度且无法抵御 root 攻击者。建议: 1. 确保 HF Space 容器以非 root 用户运行(已确认 `ds2api` 用户)。 2. 定期刷新 token(已有 `TokenRefreshIntervalHours` 配置)。 3. 监控异常 token 使用。 ### TODO-6: webui 静态文件路径穿越(已防护,建议加固) **位置**: `internal/webui/handler.go` 的 `serveFromDisk` **当前状态**: 已正确实现路径穿越防护 —— 使用 `filepath.Clean` + `isPathInsideRoot` 检查。 **建议加固**: 1. 在 `isPathInsideRoot` 失败时返回 404 而非 403(避免泄露路径存在性)。 2. 对所有静态文件请求添加 `X-Content-Type-Options: nosniff` header。 3. 对 `.json`、`.map` 等开发文件在生产环境返回 404。 ### TODO-7: chat_history 持久化用户对话(LOW) **位置**: `internal/chathistory/store.go` **问题**: chat_history 默认开启,把每个会话的完整消息持久化到 `/data/chat_history.json`。这是设计意图(用于上下文续接),但: 1. 文件未加密。 2. 没有 TTL 自动清理。 3. HF Space 重启时 `/data/` 保留,历史数据可能长期堆积。 **影响**: 隐私 —— 用户对话长期持久化。 **建议修复**: 1. 添加 `DS2API_CHAT_HISTORY_TTL_HOURS` 环境变量,自动清理超期记录。 2. 在 admin UI 提供"一键清空 chat history"按钮。 3. 文档中明确告知部署者 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`。这意味着: 1. 如果环境变量包含明文密码(常见于 HF Space Secrets),密码会被复制到 `/data/config.json`。 2. `/data/config.json` 的权限是 644(world-readable),同机其他用户可读。 **影响**: 低 —— HF Space 容器隔离,无其他用户。但本地开发或自托管场景下有风险。 **建议修复**: 1. 把 `/data/config.json` 权限改为 600(仅 owner 可读)。 2. 在 `entrypoint.sh` 中 `chmod 600 /data/config.json`。 3. 文档中说明 env writeback 会把密码写入磁盘。 ### TODO-9: 没有请求体大小限制(LOW) **位置**: `internal/server/router.go` **问题**: chi router 没有全局的 `http.MaxBytesReader`。攻击者可发送超大请求体消耗内存。 **影响**: 低 —— DeepSeek API 本身有请求大小限制,且 chi 默认有超时。 **建议修复**: 1. 在 router 中间件添加 `http.MaxBytesReader(w, r.Body, 10*1024*1024)`(10MB)。 2. 对 `/admin/config/import` 等需要大 body 的端点单独放宽限制。 ### TODO-10: 没有 HSTS / Secure cookie(按 skill 建议不报) 按 `security-best-practices` skill 指引,HSTS 在不完全理解长期影响时不建议启用,且 HF Space 已自带 HTTPS 终结,故不作为问题报告。 --- ## 渗透测试验证 针对已修复的 CRITICAL/HIGH 问题,本次审计通过单元测试模拟了以下攻击场景: 1. **默认密钥登录**:`TestVerifyAdminCredentialRejectsEmptyAndDefaultAdmin` —— 验证未配置凭据时,`"admin"`、空字符串、`"password"`、`"123456"` 等常见弱密码全部被拒绝。 2. **JWT 伪造**:`TestJWTForgedWithAdminSecretFailsVerification` —— 模拟攻击者用历史默认 `"admin"` 作为 HS256 密钥伪造 JWT,验证 `VerifyJWT` 拒绝该 token。 3. **配置导出泄露**:`TestConfigExportRedactsSecretsFromConfigField` —— 验证 `config` 字段不包含 live token、明文密码、vercel token、admin password hash。 4. **迁移用途保留**:`TestConfigExportJSONStillContainsPasswordsForMigration` —— 验证 `json`/`base64` 字段仍保留密码(迁移必需),但绝不包含 live token。 5. **密码哈希登录回归**:`TestAdminLoginWithConfiguredPasswordHash` —— 验证配置 password_hash 后,密码登录和 JWT 签名都正常工作。 6. **环境变量密钥登录回归**:`TestAdminLoginWithEnvKey` —— 验证配置 `DS2API_ADMIN_KEY` 后,env key 登录正常。 7. **显式 JWT secret 优先**:`TestAdminLoginWithJWTSecretEnv` —— 验证 `DS2API_JWT_SECRET` 优先级最高。 所有测试通过。建议在 CI 中持续运行这些回归测试,防止未来重构时 reintroduce 这些漏洞。 --- ## 部署者操作建议 本次修复后,部署者应: 1. **必须**:在 HF Space Secrets 中设置 `DS2API_ADMIN_KEY` 为强随机字符串(至少 32 字符)。否则管理员登录将完全禁用。 2. **强烈建议**:设置 `DS2API_JWT_SECRET` 为另一个独立的强随机字符串(不要复用 `DS2API_ADMIN_KEY`)。 3. **建议**:通过 admin UI 的"修改密码"功能设置管理员密码(会写入 `admin.password_hash`),这样即使 `DS2API_ADMIN_KEY` 泄露,密码登录通道仍可用。 4. **可选**:如需调试,设置 `DS2API_DEV_PACKET_CAPTURE=1` 临时启用 devcapture,调试完毕后务必设回 `0` 或删除该环境变量。 5. **审计**:检查 `/data/config.json` 是否包含不再需要的账号密码,定期清理。 6. **监控**:定期查看 HF Space 日志,关注异常 admin 端点访问。 --- ## 审计方法说明 本次审计采用以下方法: 1. **静态代码审查**:阅读 30+ 个关键文件,包括鉴权、配置、API 路由、前端、CF Worker、Dockerfile。 2. **数据流追踪**:从 HTTP 请求入口追踪到配置持久化,识别敏感数据(密码、token、prompt)的存储和暴露路径。 3. **攻击者视角**:假设攻击者拥有源码(项目开源),从扫描攻击日志中提取常见攻击模式,验证项目是否易受这些攻击。 4. **依赖审查**:检查 `go.mod` 和 `package.json`,未发现已知漏洞依赖。 5. **配置审查**:检查 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*