From 78c72004bf198f6b7e118d01b04cf98e920c8dc2 Mon Sep 17 00:00:00 2001 From: "3371392206@qq.com" <3371392206@qq.com> Date: Sun, 23 Aug 2026 13:15:15 +0800 Subject: [PATCH 01/12] =?UTF-8?q?chore:=20=E5=88=A0=E9=99=A4=20ratelimit?= =?UTF-8?q?=5Fmiddleware.go.tmp=20=E4=B8=B4=E6=97=B6=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 与 ratelimit_middleware.go 内容完全重复(SHA256 一致,1116 字节), 且无任何代码引用 .tmp 路径,属于误提交的开发期残留文件。 --- middleware/ratelimit_middleware.go.tmp | 32 -------------------------- 1 file changed, 32 deletions(-) delete mode 100644 middleware/ratelimit_middleware.go.tmp diff --git a/middleware/ratelimit_middleware.go.tmp b/middleware/ratelimit_middleware.go.tmp deleted file mode 100644 index bbbb9f0..0000000 --- a/middleware/ratelimit_middleware.go.tmp +++ /dev/null @@ -1,32 +0,0 @@ -package middleware - -import ( - "log" - "net/http" -) - -func RateLimit(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - clientIP := GetClientIP(r) - if !GlobalRateLimiter.Allow(clientIP) { - log.Printf("警告: 已超过IP速率限制,拒绝请求 - 客户端IP: %s", clientIP) - SendJSONError(w, http.StatusTooManyRequests, "Rate limit exceeded. Please try again later.", "rate_limit_error", "rate_limit_exceeded") - return - } - next.ServeHTTP(w, r) - }) -} - -func ConcurrencyLimit(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - select { - case ConcurrencySem <- struct{}{}: - defer func() { <-ConcurrencySem }() - next.ServeHTTP(w, r) - default: - log.Printf("警告: 已达到最大并发请求数限制,拒绝请求 - 客户端IP: %s", GetClientIP(r)) - SendJSONError(w, http.StatusServiceUnavailable, "Server is busy, maximum concurrent requests reached. Please try again later.", "concurrency_limit_error", "max_concurrent_requests") - return - } - }) -} -- 2.39.5 From 72d0d6a3a931a46d6333aec7309624c9fceb7edd Mon Sep 17 00:00:00 2001 From: "3371392206@qq.com" <3371392206@qq.com> Date: Tue, 25 Aug 2026 23:09:47 +0800 Subject: [PATCH 02/12] =?UTF-8?q?VUL-001=20(=E4=B8=AD):=20aac/flac=20?= =?UTF-8?q?=E5=93=8D=E5=BA=94=20Content-Type=20=E4=B8=8E=E7=9C=9F=E5=AE=9E?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E4=B8=8D=E4=B8=80=E8=87=B4=20=20=20controlle?= =?UTF-8?q?r.tts.go:contentTypeFor=20=E5=AF=B9=20aac/flac=20=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=20audio/aac/flac,=20=20=20=E4=BD=86=20adapter/volcano?= =?UTF-8?q?/synthesis.go=20=E5=9C=A8=E4=B8=8A=E6=B8=B8=E9=99=8D=E7=BA=A7?= =?UTF-8?q?=E6=97=B6=E4=BB=85=E4=BF=AE=E6=94=B9=20opts.Format,=20=20=20fin?= =?UTF-8?q?alFormat=20=E4=BB=8D=E4=BF=9D=E7=95=99=20clientFormat,=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E5=93=8D=E5=BA=94=E5=A4=B4=E4=B8=8E=E5=AD=97=E8=8A=82?= =?UTF-8?q?=E6=B5=81=E4=B8=8D=E7=AC=A6=E3=80=82=20=20=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?:finalFormat=20=E6=94=B9=E4=B8=BA=E5=8F=8D=E6=98=A0=E7=9C=9F?= =?UTF-8?q?=E5=AE=9E=E8=BE=93=E5=87=BA=E6=A0=BC=E5=BC=8F(=E9=9D=9E=20wav?= =?UTF-8?q?=20=E6=97=B6=E5=8F=96=20opts.Format),=20=20=20=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AB=AF=E6=8C=89=20AAC/FLAC=20=E8=A7=A3=E7=A0=81=20M?= =?UTF-8?q?P3=20=E6=B5=81=E7=9A=84=E5=A4=B1=E8=B4=A5=E5=9C=BA=E6=99=AF?= =?UTF-8?q?=E6=B6=88=E9=99=A4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VUL-004 (高): .env 凭据泄露 README 引导用户 cp .env.example .env 填密钥,但 .gitignore 未忽略 .env,任何 git add . 都会把含 BYTEDANCE_TTS_API_KEY 的文件提交进 git 历史,不可逆。 修复: - .gitignore 新增 Secrets section,拦截 .env 与 .env.* 变体, 保留 .env.example 作为模板追踪 - .dockerignore 升级为同名规则模式,覆盖未来 .env.local / .env.production 等变体,保证 git 与 docker 两通道一致 详见 VULNERABILITY_REPORT.md " --- .dockerignore | 3 ++- .gitignore | 9 +++++++-- adapter/volcano/synthesis.go | 7 +++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.dockerignore b/.dockerignore index 45b0f8d..9a5c2ee 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,8 @@ *.exe *.md .env -.env.example +.env.* +!.env.example .git .gitignore tts_api_architecture.html diff --git a/.gitignore b/.gitignore index e7ef1d8..8735742 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -# Go build cache +# Go build cache .gocache/ *.exe *.test @@ -11,4 +11,9 @@ Thumbs.db # Logs -*.log \ No newline at end of file +*.log + +# Secrets (do NOT commit local .env files; keep .env.example tracked as template) +.env +.env.* +!.env.example \ No newline at end of file diff --git a/adapter/volcano/synthesis.go b/adapter/volcano/synthesis.go index 07ccbaa..5a461da 100644 --- a/adapter/volcano/synthesis.go +++ b/adapter/volcano/synthesis.go @@ -116,7 +116,14 @@ func Synthesis( duration := time.Since(started) finalData := parsed.AudioData + // finalFormat 反映真实输出格式(用于 controller 写 Content-Type): + // - wav 走 pcm 上游 + 本地拼头,对外仍是 wav + // - aac/flac 在上方已被上游降级为 mp3,真实输出也是 mp3 + // - 其余与 clientFormat 一致 finalFormat := clientFormat + if clientFormat != "wav" { + finalFormat = opts.Format + } sampleRate := opts.SampleRate if clientFormat == "wav" { wav, wrapErr := WrapWAVHeader(parsed.AudioData, opts.SampleRate) -- 2.39.5 From ed3d7c6b611a39c1d24d8b728ed967221f64a2c3 Mon Sep 17 00:00:00 2001 From: "3371392206@qq.com" <3371392206@qq.com> Date: Tue, 25 Aug 2026 23:10:34 +0800 Subject: [PATCH 03/12] =?UTF-8?q?=E6=9C=AC=E6=AC=A1=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=AE=A1=E6=9F=A5(=E5=85=A8=2012=20=E4=B8=AA=E5=8C=85,?= =?UTF-8?q?=E7=BA=A6=202400=20=E8=A1=8C)=E7=9A=84=E4=BA=A4=E4=BB=98?= =?UTF-8?q?=E7=89=A9:=20=20=20-=209=20=E9=A1=B9=E6=BC=8F=E6=B4=9E(?= =?UTF-8?q?=E9=AB=98=201=20/=20=E4=B8=AD=202=20/=20=E4=BD=8E=204=20/=20?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=202)=20=20=20-=20=E5=B7=B2=E6=A0=B8=E6=9F=A5?= =?UTF-8?q?=E6=97=A0=E9=A3=8E=E9=99=A9=E9=A1=B9=208=20=E6=9D=A1=20=20=20-?= =?UTF-8?q?=20=E5=B7=A5=E7=A8=8B=E5=80=BA=E5=8A=A1=E8=AE=B0=E5=BD=95(?= =?UTF-8?q?=E9=9B=B6=E6=B5=8B=E8=AF=95=E3=80=81=E6=AD=BB=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E3=80=81=E4=BA=91=E7=9B=98=E5=8D=A0=E7=94=A8)=20=20=20-=20?= =?UTF-8?q?=E5=AE=89=E5=85=A8=E5=8A=A0=E5=9B=BA=E5=BB=BA=E8=AE=AE(Docker?= =?UTF-8?q?=20=E5=AF=86=E9=92=A5=E4=BC=A0=E9=80=92=E3=80=81TLS=E3=80=81?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E5=9B=BA=E5=AE=9A)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 供后续按优先级处理备查。 " --- VULNERABILITY_REPORT.md | 197 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 VULNERABILITY_REPORT.md diff --git a/VULNERABILITY_REPORT.md b/VULNERABILITY_REPORT.md new file mode 100644 index 0000000..ff232fb --- /dev/null +++ b/VULNERABILITY_REPORT.md @@ -0,0 +1,197 @@ +# 漏洞报告 — Volcano-Engine-TTS-UI + +## 元信息 + +| 项目 | 内容 | +|---|---| +| 目标 | ByteDance TTS v3 → OpenAI 兼容接口适配器(Go) | +| 审查范围 | 全部 12 个包、约 2400 行源码(不含 health.html 前端静态页) | +| 审查方式 | 人工代码审查 + `go build` / `go vet`(均通过) | +| 分支/提交 | develop @ 78c7200 | +| 报告日期 | 2026 年 8 月 25 日 | +| 严重度分级 | 🔴 高(必须修复)/ 🟠 中(建议修复)/ 🟡 低(视部署环境)/ ⚪ 信息(记录备查) | + +--- + +## 漏洞清单(按严重度) + +| 编号 | 严重度 | 标题 | 位置 | 一句话影响 | +|---|---|---|---|---| +| VUL-004 | 🔴 高 | `.env` 未被 `.gitignore` 忽略,凭据可能入库/入镜像 | `.gitignore` | API Key 随 git 提交或 Docker 镜像层泄露 | +| VUL-001 | 🟠 中 | aac/flac 响应 Content-Type 与数据不一致 | controller/tts.go、adapter/volcano/synthesis.go | 客户端按 AAC 解码 MP3 数据,播放失败 | +| VUL-003 | 🟠 中 | `X-Forwarded-For` 信任链可伪造 IP 绕过限流 | middleware/ratelimit.go | 反代追加模式下限流失效 | +| VUL-002 | 🟡 低 | transport 层错误不进入 `UpstreamErrors` 指标 | metrics/metrics.go、adapter/volcano/synthesis.go | 网络故障在监控上不可见 | +| VUL-005 | 🟡 低 | 日志注入:客户端可控内容原样写入日志 | middleware/logger.go、controller/tts.go | 可伪造日志行 | +| VUL-006 | 🟡 低 | `/metrics`、`/health`、`/dashboard` 无鉴权 | router/router.go | 公网暴露时泄漏运行细节(设计权衡) | +| VUL-007 | 🟡 低 | `OPENAI_TTS_API_KEY` 未设置时鉴权完全关闭 | middleware/auth.go | 公网直连即无访问控制(设计权衡) | +| VUL-008 | ⚪ 信息 | speed 超范围静默截断 | adapter/volcano/request.go | 0.25~0.5x、2.0~4.0x 实际被 clamp,无提示 | +| VUL-009 | ⚪ 信息 | WAV 输出采样率依赖配置而非上游实际值 | adapter/volcano/audio.go | 配置错误导致音频变速 | + +--- + +## VUL-004 🔴 高 — `.env` 未被忽略,凭据可能入库/入镜像 + +**位置**: `.gitignore`(全文件仅忽略构建产物与编辑器文件) + +**描述**: README 与 `.env.example` 均指导用户执行 `cp .env.example .env` 后填入火山 API Key。但 `.gitignore` **没有包含 `.env`**。任何按此流程操作并执行 `git add .` / `git commit` 的用户,都会把含 `BYTEDANCE_TTS_API_KEY`、`OPENAI_TTS_API_KEY` 的文件提交进仓库历史(即使之后删除,历史中仍可找回)。Dockerfile 第 8 行 `COPY . .` 同样会把 `.env` 拷入镜像层。 + +**影响**: 火山账号 API Key 泄露 → 冒用额度、产生费用、音色资源被盗用。密钥一旦进入 git 历史或镜像层即视为已泄露,只能吊销重建。 + +**修复建议**: +```gitignore +# Secrets +.env +.env.* +!.env.example +``` + +**验证**: 当前工作区无 `.env` 文件,仓库历史也未发现已提交的 `.env`(已核查 `git log` 提交列表无该文件),属"配置隐患"而非"已泄露"。 + +--- + +## VUL-001 🟠 中 — aac/flac 响应 Content-Type 与真实数据不一致 + +**位置**: controller/tts.go:196-212(`contentTypeFor`)、adapter/volcano/synthesis.go:59-63、118-119 + +**描述**: 客户端请求 `response_format: "aac"`(或 `flac`)时,调用链为: + +``` +resolveClientFormat("aac") → "aac"(白名单放行) +synthesis: opts.Format = "mp3"(上游降级,正确) +synthesis: finalFormat = clientFormat = "aac"(错误,保留客户端格式) +controller: Content-Type = contentTypeFor("aac") = "audio/aac"(错误) +``` + +实际响应字节是 **MP3**,但 `Content-Type` 是 `audio/aac`。 + +**影响**: 客户端(浏览器 `