develop
main
develop → main,共 12 个提交,涉及 22 个文件(+803 / -86 行)。
本次合并以安全加固为核心,同时引入构建时版本注入和 CI/CD 自动化,并完成一轮死代码清理。
finalFormat
adapter/volcano/synthesis.go
opts.Format
if errCode != 0
/metrics
metrics/metrics.go
if status != "ok"
X-Forwarded-For
middleware/ratelimit.go
GetClientIP
TRUSTED_PROXY_HOPS=N
r.RequestURI
\n
\r
middleware/logger.go
version/version.go
Version
Commit
"dev"
Dockerfile
ARG VERSION
ARG COMMIT
-ldflags
controller/tts.go
/health
version.Version
version.Commit
"2.0.0 (v3 API)"
dto/health.go
HealthResponse
git describe
v0.2.0-5-g4abcd5
.github/workflows/docker.yml
middleware/auth.go
InitAPIKeys()
middleware/cors.go
InitCORSConfig()
dto/tts.go
ByteDanceTTSConfig
common/constants.go
MaxResponseTimes
MaxErrors
middleware/ratelimit_middleware.go
ratelimit_instrumented.go
resolveClientFormat
middleware/ratelimit_test.go
.gitignore
TODO.md
.env.example
.dockerignore
setting/config.go
TrustedProxyHops
commit
UpstreamErrors
TRUSTED_PROXY_HOPS
与 ratelimit_middleware.go 内容完全重复(SHA256 一致,1116 字节), 且无任何代码引用 .tmp 路径,属于误提交的开发期残留文件。
controller.tts.go:contentTypeFor 对 aac/flac 返回 audio/aac/flac, 但 adapter/volcano/synthesis.go 在上游降级时仅修改 opts.Format, finalFormat 仍保留 clientFormat,导致响应头与字节流不符。 修复:finalFormat 改为反映真实输出格式(非 wav 时取 opts.Format), 客户端按 AAC/FLAC 解码 MP3 流的失败场景消除。 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 "
- 9 项漏洞(高 1 / 中 2 / 低 4 / 信息 2) - 已核查无风险项 8 条 - 工程债务记录(零测试、死代码、云盘占用) - 安全加固建议(Docker 密钥传递、TLS、依赖固定) 供后续按优先级处理备查。 "
VUL-003 (中): X-Forwarded-For 信任链可被伪造 IP 绕过限流 本服务定位为公网入口,即使单人使用,公网暴露意味着攻击面 与公开服务等同,不能"够用就行"。 采用渐进式披露设计,平衡易用性与功能性: 1. 启发式模式(默认, 不设环境变量 或 TRUSTED_PROXY_HOPS=0) - 从 XFF 链尾扫描,跳过私有 IP,返回第一个公网 IP - 适合 90% 部署(单跳/多跳/直出),无需了解精确跳数 - 限制:多跳 CDN 场景下,限流粒度为"按 CDN 边缘 IP" - 直出部署:整个 XFF 分支不会执行 2. 精确模式(TRUSTED_PROXY_HOPS=N, N>0) - 从 XFF 链尾倒数第 N+1 个位置取值 - 精准到真实 client,需按实际反代跳数正确配置 - N=1:单跳反代;N=2:CDN+反代;以此类推 3. 两种模式都从链尾扫描 - XFF 首值是客户端可控的,信任首值等于信任攻击者 - 链尾由受控的反代添加,天然免疫伪造绕过 4. 默认值从 1 改为 0(行为变化) - 旧默认:精确模式 N=1,取 XFF 末值 - 新默认:启发式模式,跳过链尾私有 IP - 对单跳场景行为相同 - 对多跳/链尾含私有 IP 场景新版更准确(返回真实公网 IP) 5. 配套 - middleware/ratelimit_test.go:24 个表驱动测试用例, 覆盖直出/单跳/多跳/伪造/畸形/精确 N 边界,全部通过 - setting/config.go:LogStartupSummary 显示当前 XFF 模式 - .env.example:重写说明,标注默认行为 + 何时需配 - README.md:新增"反代拓扑与 X-Forwarded-For 解析"章节 (何时需要/两种模式/行为对比/为什么从链尾/启动日志验证) 6. 已知边界:TRUSTED_PROXY_HOPS=00 等被 Atoi 解析为 0 的 输入归入启发式模式,日志不会出现"精确模式 0 跳"矛盾输出。
TODO.md 是本地待办清单,通过任务看板追踪更合适,不入 git 仓库。
VUL-005 (低): 攻击者可在 HTTP 请求 URL 或上游错误响应中 注入 \n / \r 字符,伪造日志行干扰排障。无代码执行风险。 修复位置: - middleware/logger.go: 访问日志中的 r.RequestURI 是未经 解析的原始请求行,客户端可控。转义 \n / \r 为字面字符 - adapter/volcano/synthesis.go: 上游非 200 响应体 (rawBody) 可能是攻击者控制的恶意内容,转义后再嵌入错误消息
VUL-002 (低): transport 层错误不进入 UpstreamErrors 指标 原代码用 if errCode != 0 判断是否记录错误,但合成链路中 多种错误场景的 errCode 本身是 0(火山 v3 业务码非 0 时才 会传入),导致这些场景在 /metrics 上完全不可见: - transport_error: client.PostStream 失败(DNS / 连接 / TLS) - request_error : buildRequest 序列化失败 - wrap_error : WAV 头拼装失败 - stream_error (code=0): 读流错误 修复:把判断改为 if status != "ok",status 是上游调用全链 路的权威错误指示器,任何非 ok 状态都计为错误。 codeLabel(0) → "transport" 已有定义,修复后该分类真正生效, 火山接口不可达等网络故障首次在监控上可见。 向后兼容:已统计的 http_XXX 错误(status="http_xxx" != "ok") 行为不变,新增 transport / request / wrap / stream 错误进入指标。
判定:不引入新鉴权机制(会破坏 Prometheus 抓取),文档引导。 修复:「观测 / Metrics」section 末尾新增「公网部署:监控端点 无鉴权」段,含三端点风险表 + nginx 反代 basic auth 配置示例。 VUL-007 (低): 未设置 OPENAI_TTS_API_KEY 时鉴权完全关闭 判定:不改代码(无 API Key 即不鉴权是 README 明示的设计), 文档强化。 修复:三处加强: 1. 环境变量表 OPENAI_TTS_API_KEY 行加 🔴 公网必设 标记 2. 「API 使用说明」section 顶部 callout 警示 3. 「部署」section 末尾新增「公网部署安全清单」段,统一 覆盖鉴权关闭与监控端点保护,形成完整安全姿态
VUL-008 (信息): speed 超范围静默截断无提示 修复:「API 使用说明」speed 描述从"0.25 ~ 4.0"改为 "客户端接受范围 0.25 ~ 4.0;火山实际生效范围 0.5 ~ 2.0 (speech_rate [-50, 100]),超出范围会被静默截断,客户端无感反馈"。 选 TODO 方案 1(诚实修正文档),不改代码。 VUL-009 (信息): WAV 采样率依赖配置而非上游实际值 修复:BYTEDANCE_TTS_SAMPLE_RATE 描述加风险提示 "此值直接写入 WAV 头,需与上游实际 PCM 采样率一致, 否则音频变速变调"。 上游 v3 协议不返回 sample_rate 字段,代码侧无法自动取真实值, 仅文档强化。
VUL-003 修复期间意外发现项目遗留一批死代码,本次一并清掉: - middleware/ratelimit_middleware.go(37 行,物理删除) 文件内 RateLimit / ConcurrencyLimit 函数从 977e9cc 创建后 从未被引用,370a217 commit 用 ratelimit_instrumented.go (带 metrics 埋点 + 路径过滤)取代了它。占用包体,清。 - middleware/auth.go:InitAPIKeys(6 行) 注释说"已在 setting.InitAuthConfig 中完成",无 op。 - middleware/cors.go:InitCORSConfig(6 行) 同上,setting.InitCORSConfig 已做实际工作。 - dto/tts.go:ByteDanceTTSConfig 类型(7 行) 完整的配置走 setting.TTSOptions + adapter/volcano.Options, 此类型从未被任何代码实例化。 - setting/config.go: var _ = dto.ByteDanceTTSConfig{} 占位(3 行) 配合上方类型删除,移除 dto import。 - controller/tts.go:resolveClientFormat 合并 if reqFmt == "" 与 default 分支(都返回 setting.TTSOptions.Format),2 行简化。 - common/constants.go: MaxResponseTimes / MaxErrors 定义后从未被任何文件引用。 - middleware/ratelimit_instrumented.go 顶部注释 移除对"原 ratelimit_middleware.go"的悬空引用, 改为描述本文件相对路由使用实现的两个增强点。 影响: - 包体减少约 30 行 - 降低新人接手时的代码理解成本 - 零功能变更,24 个现有测试用例全过
977e9cc
现状:controller/tts.go:232 硬编码 Version: "2.0.0 (v3 API)", 不会随代码变化而更新,/health 无法反映实际跑的代码。 修复:四文件改动,实现构建时 ldflags 注入。 1. 新建 version/version.go,声明两个包级变量: Version (默认 "dev") Commit (默认 "dev") 2. dto/health.go:HealthResponse 加 Commit 字段(JSON 输出多一字段) 3. controller/tts.go:HealthResponse 用 version.Version / version.Commit 替代硬编码字面量 4. Dockerfile:加 ARG VERSION=dev ARG COMMIT=dev, go build 时通过 -ldflags 注入到 version 包的两个变量 效果: - 本地 go build (不传 ldflags) → version=dev commit=dev - 开发 build (git describe) → version=v0.2.0-5-g4abcd5 commit=g4abcd5 - release build (打 tag 后) → version=v0.2.1 commit=<对应 hash> 测试环境(develop 分支,无 tag)显示距离上次 release 几个 commit + 具体 hash;生产环境(main + tag)显示干净 semver。
为 Gitea→GitHub 镜像 + GitHub 镜像仓库场景准备 Docker 镜像 自动构建和发布流程。 触发条件: - push tag v* (release 时打 tag 自动触发) - workflow_dispatch (手动触发,可在 GitHub UI 测试) 构建: - 多平台:linux/amd64 + linux/arm64 - 通过 QEMU + Buildx 跨架构构建 - 推送到 ghcr.io/<github-user>/volcano-engine-tts-ui - 双标签:语义版本 + 短 commit hash - OCI labels 包含 version 和 revision 与之前 version 包配合: workflow 用 git describe --tags --always --dirty 算 VERSION (commit hash 类似),通过 --build-arg 传给 Dockerfile, Dockerfile 用 -ldflags 注入到 version.Version / version.Commit 两个变量。最终 /health 端点返回构建时注入的真实版本信息。 前置条件(用户需自行完成): 1. GitHub 建镜像仓库 2. Gitea 配置 Push Mirror 到 GitHub(开启 Sync Tags) 3. 仓库设为 Public(否则 GHCR 镜像默认 private)
No dependencies set.
The note is not visible to the blocked user.
变更概览
develop→main,共 12 个提交,涉及 22 个文件(+803 / -86 行)。本次合并以安全加固为核心,同时引入构建时版本注入和 CI/CD 自动化,并完成一轮死代码清理。
一、安全修复(6 项)
VUL-001(中):AAC/FLAC 响应 Content-Type 与真实数据不一致
finalFormat仍保留客户端请求格式,导致响应头与字节流不符,客户端解码失败。adapter/volcano/synthesis.go中finalFormat改为反映真实输出格式(非 wav 时取opts.Format)。VUL-002(低):Transport 层错误不进入 UpstreamErrors 指标
if errCode != 0判断是否计错误,但合成链路中多种错误场景(transport_error / request_error / wrap_error / stream_error)的 errCode 为 0,导致这些场景在/metrics上完全不可见。metrics/metrics.go改为if status != "ok",任何非 ok 状态均计为错误。火山接口不可达等网络故障首次在监控上可见。VUL-003(低):XFF 解析可被伪造绕过 IP 限流
X-Forwarded-For第一个值,该值由客户端控制,攻击者可伪造任意 IP 绕过限流。middleware/ratelimit.go重写GetClientIP,从 XFF 链尾部解析,支持两种模式:TRUSTED_PROXY_HOPS=N指定反代跳数,适用于 Cloudflare + nginx 等多跳场景。VUL-005(低):日志注入(RequestURI 与上游错误体)
r.RequestURI和上游响应体未经转义直接写入日志,攻击者可在 URL 或错误体中注入\n/\r伪造日志行。middleware/logger.go和adapter/volcano/synthesis.go对换行符做转义处理。VUL-006(低):监控端点(/metrics /health /dashboard)无鉴权
VUL-008 / VUL-009(信息):speed 与 sample_rate 文档描述修正
二、新功能
构建时版本信息注入
version/version.go,声明Version和Commit两个包级变量(默认"dev")。Dockerfile新增ARG VERSION/ARG COMMIT,通过-ldflags在构建时注入。controller/tts.go的/health端点改用version.Version/version.Commit,替代硬编码的"2.0.0 (v3 API)"。dto/health.go的HealthResponse新增Commit字段。git describe输出(如v0.2.0-5-g4abcd5),生产环境(tag 构建)显示干净 semver,运维可通过/health确认"跑的到底是哪个 commit"。GitHub Actions Docker 发布 Workflow
.github/workflows/docker.yml,自动化 Docker 镜像构建与发布。三、工程改进
死代码清理(DEBT-2,7 文件,约 30 行)
middleware/auth.go中 no-op 的InitAPIKeys()middleware/cors.go中 no-op 的InitCORSConfig()dto/tts.go中未使用的ByteDanceTTSConfig结构体common/constants.go中未使用的MaxResponseTimes/MaxErrors常量middleware/ratelimit_middleware.go(功能已由ratelimit_instrumented.go替代)controller/tts.go中resolveClientFormat的冗余空字符串分支限流中间件增强
middleware/ratelimit.go:新增 XFF 双模式解析(见 VUL-003),初始化时同步配置到 setting 包。middleware/ratelimit_test.go:229 行单元测试,覆盖限流窗口、并发控制、XFF 解析等场景。配置与忽略文件
.gitignore:新增忽略TODO.md(本地待办清单不入仓库).env.example:新增环境变量示例.dockerignore:更新构建上下文排除规则setting/config.go:新增TrustedProxyHops等配置项的启动日志展示四、文档
兼容性说明
/health新增commit字段外)均保持不变。/metrics中UpstreamErrors现在会统计 transport/request/wrap/stream 类错误(此前遗漏),监控告警阈值可能需要微调。TRUSTED_PROXY_HOPS。TRUSTED_PROXY_HOPS环境变量(可选,默认启发式模式)。controller.tts.go:contentTypeFor 对 aac/flac 返回 audio/aac/flac, 但 adapter/volcano/synthesis.go 在上游降级时仅修改 opts.Format, finalFormat 仍保留 clientFormat,导致响应头与字节流不符。 修复:finalFormat 改为反映真实输出格式(非 wav 时取 opts.Format), 客户端按 AAC/FLAC 解码 MP3 流的失败场景消除。 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 "VUL-003 (中): X-Forwarded-For 信任链可被伪造 IP 绕过限流 本服务定位为公网入口,即使单人使用,公网暴露意味着攻击面 与公开服务等同,不能"够用就行"。 采用渐进式披露设计,平衡易用性与功能性: 1. 启发式模式(默认, 不设环境变量 或 TRUSTED_PROXY_HOPS=0) - 从 XFF 链尾扫描,跳过私有 IP,返回第一个公网 IP - 适合 90% 部署(单跳/多跳/直出),无需了解精确跳数 - 限制:多跳 CDN 场景下,限流粒度为"按 CDN 边缘 IP" - 直出部署:整个 XFF 分支不会执行 2. 精确模式(TRUSTED_PROXY_HOPS=N, N>0) - 从 XFF 链尾倒数第 N+1 个位置取值 - 精准到真实 client,需按实际反代跳数正确配置 - N=1:单跳反代;N=2:CDN+反代;以此类推 3. 两种模式都从链尾扫描 - XFF 首值是客户端可控的,信任首值等于信任攻击者 - 链尾由受控的反代添加,天然免疫伪造绕过 4. 默认值从 1 改为 0(行为变化) - 旧默认:精确模式 N=1,取 XFF 末值 - 新默认:启发式模式,跳过链尾私有 IP - 对单跳场景行为相同 - 对多跳/链尾含私有 IP 场景新版更准确(返回真实公网 IP) 5. 配套 - middleware/ratelimit_test.go:24 个表驱动测试用例, 覆盖直出/单跳/多跳/伪造/畸形/精确 N 边界,全部通过 - setting/config.go:LogStartupSummary 显示当前 XFF 模式 - .env.example:重写说明,标注默认行为 + 何时需配 - README.md:新增"反代拓扑与 X-Forwarded-For 解析"章节 (何时需要/两种模式/行为对比/为什么从链尾/启动日志验证) 6. 已知边界:TRUSTED_PROXY_HOPS=00 等被 Atoi 解析为 0 的 输入归入启发式模式,日志不会出现"精确模式 0 跳"矛盾输出。VUL-005 (低): 攻击者可在 HTTP 请求 URL 或上游错误响应中 注入 \n / \r 字符,伪造日志行干扰排障。无代码执行风险。 修复位置: - middleware/logger.go: 访问日志中的 r.RequestURI 是未经 解析的原始请求行,客户端可控。转义 \n / \r 为字面字符 - adapter/volcano/synthesis.go: 上游非 200 响应体 (rawBody) 可能是攻击者控制的恶意内容,转义后再嵌入错误消息判定:不引入新鉴权机制(会破坏 Prometheus 抓取),文档引导。 修复:「观测 / Metrics」section 末尾新增「公网部署:监控端点 无鉴权」段,含三端点风险表 + nginx 反代 basic auth 配置示例。 VUL-007 (低): 未设置 OPENAI_TTS_API_KEY 时鉴权完全关闭 判定:不改代码(无 API Key 即不鉴权是 README 明示的设计), 文档强化。 修复:三处加强: 1. 环境变量表 OPENAI_TTS_API_KEY 行加 🔴 公网必设 标记 2. 「API 使用说明」section 顶部 callout 警示 3. 「部署」section 末尾新增「公网部署安全清单」段,统一 覆盖鉴权关闭与监控端点保护,形成完整安全姿态VUL-003 修复期间意外发现项目遗留一批死代码,本次一并清掉: - middleware/ratelimit_middleware.go(37 行,物理删除) 文件内 RateLimit / ConcurrencyLimit 函数从977e9cc创建后 从未被引用,370a217 commit 用 ratelimit_instrumented.go (带 metrics 埋点 + 路径过滤)取代了它。占用包体,清。 - middleware/auth.go:InitAPIKeys(6 行) 注释说"已在 setting.InitAuthConfig 中完成",无 op。 - middleware/cors.go:InitCORSConfig(6 行) 同上,setting.InitCORSConfig 已做实际工作。 - dto/tts.go:ByteDanceTTSConfig 类型(7 行) 完整的配置走 setting.TTSOptions + adapter/volcano.Options, 此类型从未被任何代码实例化。 - setting/config.go: var _ = dto.ByteDanceTTSConfig{} 占位(3 行) 配合上方类型删除,移除 dto import。 - controller/tts.go:resolveClientFormat 合并 if reqFmt == "" 与 default 分支(都返回 setting.TTSOptions.Format),2 行简化。 - common/constants.go: MaxResponseTimes / MaxErrors 定义后从未被任何文件引用。 - middleware/ratelimit_instrumented.go 顶部注释 移除对"原 ratelimit_middleware.go"的悬空引用, 改为描述本文件相对路由使用实现的两个增强点。 影响: - 包体减少约 30 行 - 降低新人接手时的代码理解成本 - 零功能变更,24 个现有测试用例全过现状:controller/tts.go:232 硬编码 Version: "2.0.0 (v3 API)", 不会随代码变化而更新,/health 无法反映实际跑的代码。 修复:四文件改动,实现构建时 ldflags 注入。 1. 新建 version/version.go,声明两个包级变量: Version (默认 "dev") Commit (默认 "dev") 2. dto/health.go:HealthResponse 加 Commit 字段(JSON 输出多一字段) 3. controller/tts.go:HealthResponse 用 version.Version / version.Commit 替代硬编码字面量 4. Dockerfile:加 ARG VERSION=dev ARG COMMIT=dev, go build 时通过 -ldflags 注入到 version 包的两个变量 效果: - 本地 go build (不传 ldflags) → version=dev commit=dev - 开发 build (git describe) → version=v0.2.0-5-g4abcd5 commit=g4abcd5 - release build (打 tag 后) → version=v0.2.1 commit=<对应 hash> 测试环境(develop 分支,无 tag)显示距离上次 release 几个 commit + 具体 hash;生产环境(main + tag)显示干净 semver。