feat(autoban): 封禁响应图改为 ban_image_path 配置,默认仍是纯文字
上一版把 403 图 //go:embed 进了二进制。实际部署里图会集中放在 R2 统一管理、 由运维分发到各机器,进仓库反而成了负担:换一张图就要重新编译。 改为配置驱动: - 新增 [security] ban_image_path(相对路径以进程工作目录为基准); - 留空 / 文件读不到 / 是目录 / 不是图片 / 超过 2 MB → 一律回退为纯文字 "403 Forbidden",即改造前的老行为。封禁响应是最后一道兜底,不能因为 一张图缺失就变成 500,更不能把站点拦在启动阶段(失败只 Warn); - 启动时读一次装进 atomic.Pointer,热路径零磁盘 IO;换图 = 换文件 + 重启。 安全细节:按内容嗅探(http.DetectContentType)而不是扩展名,必须是 image/*。 把 ban_image_path 配成 app.ini / .env 会被拒绝并告警 —— 否则等于把配置文件 原样吐给每一个被封的攻击者。这条有专门的用例与变异验证。 顺带删掉 internal/route/assets(webp + embed),已无引用。 测试: - TestWriteBannedTextFallback:未配图 → 403 + text/plain + 13 字节明文; - TestWriteBannedServesConfiguredImage:配了 1×1 PNG → image/png(按内容嗅探)、 Content-Length 与文件一致、body 与文件逐字节相同; - TestLoadBanImageRejectsBadInput:不存在 / 是目录 / 非图片 / 空文件四种配置错误 都不得装载,且**不得把文件内容当响应体吐出去**; - TestLoadBanImageTooLarge:超 2 MB 拒绝; - TestWriteBannedHead:两条分支的 HEAD 都只回头不给 body。 变异验证:去掉 image/* 校验 → 用例报「把文件内容当响应体吐了出去: "[server]\nsalt = secret"」;强制走纯文字分支 → 配图用例按预期变红。 实测:真 webp 嗅探为 image/webp;app.sample.ini 嗅探为 text/plain 被拒。
This commit is contained in:
@@ -297,12 +297,13 @@ func TestHoneypotBansEvenWhenAutoBanDisabled(t *testing.T) {
|
||||
if resp2.StatusCode != http.StatusForbidden {
|
||||
t.Errorf("status = %d, want 403(蜜罐封禁不得被 enable_auto_ban 连坐)", resp2.StatusCode)
|
||||
}
|
||||
// body 是一张 403 图(见 writeBanned),不再是那行明文。
|
||||
if ct := resp2.Header.Get("Content-Type"); ct != "image/webp" {
|
||||
t.Errorf("Content-Type = %q, want image/webp", ct)
|
||||
// 没配 ban_image_path 时,封禁响应就是纯文字(见 writeBanned)。
|
||||
// 配了图的分支由 banned_test.go 覆盖。
|
||||
if ct := resp2.Header.Get("Content-Type"); !strings.HasPrefix(ct, "text/plain") {
|
||||
t.Errorf("Content-Type = %q, want text/plain", ct)
|
||||
}
|
||||
if len(body) < 1024 {
|
||||
t.Errorf("body 只有 %d 字节,want 一张图片", len(body))
|
||||
if !strings.Contains(string(body), banText) {
|
||||
t.Errorf("body = %q, want 含 %q", truncate(string(body), 80), banText)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user