现场故障:配置写 `honeypot_retention_days <= 30`,日志却报
「蜜罐自动清理未启用:honeypot_retention_days <= 0」。
根因(实测 ini v1.67.0 行为确认):
1. 键名多一个 t(retention vs retain)→ ini 对未知键**静默忽略**,保留期成 0;
2. `<= 30` 这种写法让 ini 把键名解析成 "honeypot_retention_days <"
(带尾随比较符),更加匹配不上;
3. 警告文案里写的也是错键名,等于在误导运维往错的键上配。
修复(三层):
- 键名归一化(internal/conf/keynorm.go):修剪首尾空白、剥掉尾随 `<` `>`
`=` 等比较符残留,并按别名表纠正常见误拼。归一化结果必须命中该段
合法键名白名单才会被采用 —— 不认识的键不猜,交给未知键告警。
- 未知键告警(internal/conf/unknown.go):启动时逐段比对键名,无法识别的
键 WARN 出来并给出最接近的合法键名建议。合法键名由反射从结构体 ini tag
得出(单一事实来源,新增配置项自动纳入,不会脱节)。
- 缺省值语义修正(internal/conf/honeypot_defaults.go):
* 保留期缺省 30 天(原先零值 0 = 永不清理);显式 <= 0 也回退 30。
要关闭自动清理请用 honeypot_auto_clean = false。
* honeypot_auto_clean 缺省 true。原先靠 bool 零值,ini 的 bool 解析是
「有键才赋值」,未配置与显式关闭无法区分 → 老配置升级上来自动清理
实际是关着的。现改用 HasKey 判定。
* honeypot_max_rows 缺省 200000;显式 0 = 不限(会告警提醒只增不减)。
* honeypot_threshold 缺省 1。
实测 ini 行为(探针程序,已删):
- 键缺失或值非法时 MapTo **不覆盖**预置默认值 → 可安全预置缺省值;
- 键名大小写敏感(Honeypot_Auto_Clean 不生效);
- HasKey 能区分「缺失」与「显式 0」;
- 段不存在时 MapTo 返回 nil 且不修改目标。
顺带清理 app.sample.ini 里的两条死配置(app.title / server.xsrf_expire):
代码中从未读取(站点标题走 i18n),留着会让新检测对官方模板误报。
验证:
- 用现场原始配置端到端复现 → 归一化后保留期 30 天、自动清理开启、上限 20 万,
日志明确说明「配置键名已自动纠正」;
- 误拼键(honeypot_autoclean / honeypot_max_row)自动纠正;未知键(totally_bogus)
WARN 告警;
- 显式 honeypot_auto_clean = false 被尊重为关闭;完全未配置时缺省开启;
- 新增 TestSampleIniHasNoUnknownKeys 回归检测:官方样例配置零误报;
- go build ./... + go test -timeout 600s ./... 全绿,go vet 无输出。
231 lines
17 KiB
INI
231 lines
17 KiB
INI
; ── 关于配置键名 ──────────────────────────────────────────────────────────
|
||
; ⚠️ 键名写错时,程序启动会 WARN 提示「存在无法识别的键」并给出最接近的合法键名。
|
||
; 对于少量已知的常见误拼(如 honeypot_retention_days → honeypot_retain_days),
|
||
; 程序会自动纠正并在日志里说明纠正了什么。
|
||
; 但请优先写对键名 —— 自动纠正只覆盖已知的误拼,不认识的键一律不猜。
|
||
|
||
[app]
|
||
production = true
|
||
external_url = "https://box.shiroko.one" ; 【必填】站点对外访问地址。用于生成 og:url 与邮件中的全部链接
|
||
; (找回密码、私信通知等),必须以 http(s):// 开头、结尾不带斜杠
|
||
; (程序会自动 TrimRight "/")。域名迁移时只改这一处即可全局生效。
|
||
icp = ""
|
||
default_lang = "" ; 站点默认语言:zh-CN / zh-TW / en / ja,留空默认简体中文(访客未设置语言时的兜底)
|
||
uptrace_dsn = ""
|
||
qiniu_access_key = ""
|
||
qiniu_access_secret = ""
|
||
aliyun_access_key = ""
|
||
aliyun_access_key_secret = ""
|
||
share_background = "https://your-default-background.jpg" ; 默认分享图背景图链接(1280x720)
|
||
font_path = "static/fonts/NotoSansSC-Regular.otf" ; 中文字体文件路径(相对项目根目录)
|
||
|
||
[security]
|
||
enable_text_censor = true
|
||
text_censor_source = auto ; 文本审核服务通道:qiniu(七牛)/ aliyun(阿里云)/ auto(七牛优先、阿里云兜底)。
|
||
; 留空等同于 auto,即引入该配置前的历史行为。
|
||
; 显式选 qiniu / aliyun 时必须在 [app] 配好对应凭据,否则启动即报错
|
||
; —— 避免运行期审核必然失败(审核失败是 fail-closed,拒绝提交,
|
||
; 会让发布/私信整站不可用,且现象隐蔽、只在日志可见)。
|
||
; 凭据位置:[app] 的 qiniu_access_key / qiniu_access_secret、
|
||
; aliyun_access_key / aliyun_access_key_secret。
|
||
; ⚠️ 配好凭据 ≠ 可用:阿里云通道走的是「内容安全 2.0 / 文本审核增强版」
|
||
; (接口 TextModeration,接入点 green-cip.cn-shanghai.aliyuncs.com),
|
||
; 开通前提只要求【实名认证,个人账号即可】,不需要企业认证。
|
||
; 开通入口:内容安全 2.0 控制台 → 开通「内容审核增强版」按量付费
|
||
; (开通本身不收费,按实际调用量出账;文本审核标准 7.5 元/万次)。
|
||
; 用 RAM 子账号调用需授予 AliyunYundunGreenWebFullAccess;欠费停服同理。
|
||
; 审核场景固定为 comment_detection(公聊评论检测,覆盖涉政/涉暴/涉黄等底线维度)。
|
||
; 单次 content 上限 600 字符,超长文本由代码分段审核(段间带重叠,避免跨段漏判)。
|
||
; ⚠️ 不要再去开通「内容安全 1.0」:它要求企业实名认证,个人账号无法开通,
|
||
; 调用会永久返回 code=596 "You have not opened Yundun Content Moderation Service"。
|
||
; 审核失败时日志会打印阿里云返回的 code / msg / requestId 便于定位;
|
||
; 首次出现账号级失败(未开通 / 未授权 / 密钥错误)还会额外打印一条一次性告警。
|
||
ip_blacklist = ; 逗号分隔的IP黑名单,支持精确IP(如1.2.3.4)和CIDR(如1.2.3.0/24),留空不启用
|
||
ip_blacklist_redirect_url = ; 黑名单IP访问时强制跳转到该URL
|
||
trust_proxy = false ; 是否信任反向代理设置的 CF-Connecting-IP / X-Real-IP / X-Forwarded-For 头。
|
||
; 仅当站点部署在可信反向代理(如 nginx/Cloudflare)之后且代理已覆盖这些头时设为 true;
|
||
; 否则保持 false,防止客户端伪造 header 绕过速率限制与 IP 黑名单。
|
||
; ⚠️ 若站点在 Cloudflare 后且需使用下面的自动封禁,必须设为 true,
|
||
; 否则解析到的将是 Cloudflare 边缘 IP,自动封禁会误伤所有访客。
|
||
; ⚠️ 开启 trust_proxy 时,必须配合「Cloudflare Tunnel / Authenticated Origin Pull /
|
||
; 防火墙仅放行 Cloudflare / EdgeOne 回源网段」锁定源站,否则攻击者可直连源站并伪造
|
||
; CF-Connecting-IP / EO-Client-IP 头。无需维护 CDN 庞大 CIDR 清单。
|
||
;
|
||
; —— 双 CDN(境内腾讯 EdgeOne + 境外 Cloudflare)场景 ——
|
||
; EdgeOne 回源会用 EO-Client-IP 携带客户端真实 IP(等效 CF-Connecting-IP),
|
||
; 本程序在 trust_proxy=true 时已同时信任这两个权威头。
|
||
; 两家 CDN 并存时必须:① 防火墙仅放行两家回源网段;② 在各家 CDN 的规则引擎中
|
||
; 删除回源请求里「对方 CDN」的权威头(经 EdgeOne 回源删 CF-Connecting-IP、
|
||
; 经 Cloudflare 回源删 EO-Client-IP),否则攻击者可借道另一家伪造客户端身份,
|
||
; 绕过限流 / 自动封禁 / 单 IP 投票限制。详细步骤见 outputs/edgeone-cloudflare-dual-cdn-guide.md。
|
||
|
||
trusted_proxies =
|
||
; 逗号分隔的「你自己的」反向代理/负载均衡 IP 或 CIDR(如 127.0.0.1、
|
||
; 10.0.0.0/8)。仅当 TCP 对端落在该清单内时才解析 X-Forwarded-For / X-Real-IP。
|
||
; 留空(默认)则这两个头永不被信任,仅采用 CDN 权威头 CF-Connecting-IP /
|
||
; EO-Client-IP(需源站已锁定)。
|
||
; 注意:此清单用于你自己的基础设施,不是 CDN 的全量 CIDR 清单。
|
||
enable_auto_ban = true ; 自动封禁(fail2ban 风格):检测到批量扫描/探测后临时封禁来源 IP
|
||
ban_track_notfound = false ; 是否把「未注册路径的 404」计入封禁统计(默认 false)。正常业务
|
||
; (域名/题目不存在等均走 302 业务跳转,不产生 404 计数)一律放行,
|
||
; 仅命中后台扫描特征路径(/admin、/.env 等)才参与统计封禁,避免
|
||
; 共享出口(CDN 边缘 / CGNAT)下正常用户被 404 探测连坐误伤。
|
||
; 如需恢复对纯 404 字典扫描的封禁再置 true。
|
||
ban_threshold_sensitive = 10 ; 统计窗口内命中敏感路径(/admin、/.env、/wp-admin 等)的次数阈值
|
||
ban_threshold_404 = 20 ; 统计窗口内返回 404 的次数阈值(仅 ban_track_notfound = true 时生效)
|
||
ban_window_seconds = 60 ; 统计窗口时长(秒)
|
||
ban_duration_minutes = 15 ; 临时封禁时长(分钟),到期自动解除,重启即清零
|
||
security_alert_email = ; 触发封禁时发送告警邮件的收件人(复用下方 SMTP 发信配置),留空不发送
|
||
|
||
; ── 蜜罐(虚假后台登录界面)────────────────────────────────────────────
|
||
enable_honeypot = false ; 开启后,扫描器探测后台路径(/wp-admin、/.ssh、/.env 等)
|
||
; 时不再返回 302/404,而是返回一个仿 WordPress 的假登录页,
|
||
; 并把其提交的用户名/密码记录入库供取证。
|
||
; 只在「没有任何路由匹配」的路径上生效,真实业务路由零交集;
|
||
; 假页面只含 canary 标记与假凭据,绝不含任何真实信息。
|
||
honeypot_paths = ; 追加自定义诱饵路径(逗号分隔),与内置清单取并集。
|
||
honeypot_retain_days = 30 ; 蜜罐命中记录的保留天数(取证用途),到期自动清理。
|
||
; ⚠️ 键名是 honeypot_retain_days(没有 tion)。写错会被静默忽略,
|
||
; 程序会尝试自动纠正并在日志里提示,但别指望它每次都猜对。
|
||
; 缺省 30 天;写 0 或负数会回退 30(不表示「永不清理」)。
|
||
; 要关闭自动清理请用 honeypot_auto_clean = false。
|
||
; 可用 TamaBox honeypot size 查看行数与磁盘占用。
|
||
honeypot_auto_clean = true ; 是否启用蜜罐记录的后台自动保留期清理(每小时一轮)。
|
||
; honeypot_hits 是只追加的审计表,写入完全由外部扫描流量
|
||
; 驱动,不清理的话表体积只增不减。关闭后仍可手动 prune。
|
||
; 缺省为 true(开启)。
|
||
honeypot_max_rows = 200000 ; 蜜罐命中记录的行数上限(第二道闸)。缺省 200000。
|
||
; 显式写 0 = 不限(会告警提醒表只增不减)。
|
||
; 保留期是时间维度上界,但攻击者可用海量源 IP 在保留期内
|
||
; 把表撑爆。实测单行约 281 字节(含索引,常规 UA),20 万行约 53 MB。
|
||
; 实测数据:连续删 12000 插 12000,行数恒定 5 万,文件稳定在 15 MB 不再增长
|
||
; (VACUUM 让空间可复用,不保证文件缩小)。
|
||
honeypot_threshold = 1 ; 窗口内蜜罐命中多少次即封禁。默认 1(一次即封):蜜罐路径
|
||
; 与真实业务路由零交集,正常访客不可能命中,一次即足以判定扫描器。
|
||
honeypot_ban_minutes = ; 蜜罐封禁时长(分钟)。留空则复用 ban_duration_minutes(15)。
|
||
; ⚠️ 蜜罐封禁与 enable_auto_ban 相互独立:即使关掉自动封禁,
|
||
; 只要 enable_honeypot = true,蜜罐命中仍会封禁并记录。
|
||
default_moderation_strictness = ; 站点默认「内容审核严格度」:lax(宽松)/ standard(标准)/ strict(严格)。
|
||
; 仅对未单独设置档位的账号生效(账号在设置页选择后以账号设置为准)。
|
||
; 留空回退 strict(最严档):保证升级不会放宽任何拦截。
|
||
; 严格档是三个档位中最严的(拦截面不小于升级前),因此部署后默认拦截面可能略有扩大;
|
||
; 若希望默认更少误伤,改为 standard 即可,无需改代码。
|
||
; 三档的拦截范围见 internal/security/censor/policy.go,
|
||
; 适用于提问、投票、私聊三类内容。
|
||
|
||
[server]
|
||
port = 80
|
||
; 绑定监听地址(含端口)。Cloudflare Tunnel 部署时强烈建议设为 127.0.0.1:<port>,
|
||
; 使源站端口仅对本地 cloudflared 可见,即使防火墙误配也无法被外部直连伪造 CF-Connecting-IP。
|
||
; 留空(默认)则监听所有网卡 ":port"。
|
||
listen =
|
||
salt = ""
|
||
# ⚠️ 必填:密码哈希盐,留空会导致启动失败(已在 conf.Init 强制校验)
|
||
xsrf_key = ""
|
||
|
||
[database]
|
||
type = "postgres"
|
||
user = "nekobox"
|
||
password = ""
|
||
host = "127.0.0.1"
|
||
port = 5432
|
||
name = "nekobox"
|
||
schema = "public"
|
||
# 仅在已确认需要自动清理重复 email/domain 时开启;默认关闭,避免启动时改写账号数据。
|
||
dedupe_unique_fields = false
|
||
|
||
[redis]
|
||
addr = "127.0.0.1:6379"
|
||
password = ""
|
||
|
||
[recaptcha]
|
||
captcha_type = recaptcha ; recaptcha(默认), turnstile, math(本地算数验证码,国内访问友好)
|
||
domain = "https://www.recaptcha.net"
|
||
site_key = ""
|
||
server_key = ""
|
||
turnstile_style = false
|
||
|
||
[upload]
|
||
default_avatar = ""
|
||
default_background = ""
|
||
|
||
# User avatar and background.
|
||
aliyun_endpoint = ""
|
||
aliyun_access_id = ""
|
||
aliyun_access_secret = ""
|
||
aliyun_bucket = ""
|
||
aliyun_bucket_cdn_host = ""
|
||
image_endpoint = ""
|
||
image_access_id = ""
|
||
image_access_secret =""
|
||
image_bucket = ""
|
||
image_bucket_cdn_host = ""
|
||
|
||
# User uploaded images.
|
||
image_endpoint = ""
|
||
image_access_id = ""
|
||
image_access_secret = ""
|
||
image_bucket = ""
|
||
image_bucket_cdn_host = ""
|
||
|
||
[mail]
|
||
account = ""
|
||
password = ""
|
||
port = 465
|
||
smtp = ""
|
||
|
||
[limits]
|
||
; ── 全站内容 / 数量上限(运行时配置,修改后重启生效)──────────────────────
|
||
; 缺失本段或个别键时按内置默认值运行。长度统一按字符(rune)计数。
|
||
; 键留空或填 0/负数时自动回退内置默认值,不会导致启动失败,但也不会按你的意图生效,
|
||
; 请按需填写正整数。本文件启用 IgnoreInlineComment,行内分号注释会被并入配置值,
|
||
; 因此下面注释一律独占整行、勿写行内注释。
|
||
|
||
; ── 标签与屏蔽词(账号设置页)──────────────────────────────────────────────
|
||
; 最多标签个数
|
||
max_tags = 5
|
||
; 单个标签最大字符数
|
||
max_tag_len = 5
|
||
; 最多屏蔽词个数
|
||
max_block_words = 10
|
||
; 单个屏蔽词最大字符数
|
||
max_block_word_len = 10
|
||
|
||
; ── 投票 ────────────────────────────────────────────────────────────────────
|
||
; 参与者填写的「自定义补充选项」(其他/自定义)最大字符数
|
||
custom_answer_len = 200
|
||
; 站长创建投票时单个「选项」文本最大字符数(预设选项)
|
||
vote_option_len = 300
|
||
|
||
; ── 提问 / 回答 / 追问 ─────────────────────────────────────────────────────
|
||
; 提问正文最大字符数(访客提交的问题内容)
|
||
question_content_len = 1000
|
||
; 回答正文最大字符数(箱主对提问的回答,及对追问的回复,共用)
|
||
answer_content_len = 2000
|
||
; 追问正文最大字符数(提问者被回答后继续追问的内容)
|
||
followup_content_len = 2000
|
||
|
||
; ── 私聊 ────────────────────────────────────────────────────────────────────
|
||
; 单条消息(发送/回复)最大字符数
|
||
chat_message_len = 2000
|
||
; 每个私聊会话的消息条数上限(系统硬顶)。用户只能在 1..该值 之间设置自己的
|
||
; 会话条数上限;新注册用户的默认值即该硬顶。
|
||
chat_max_messages = 200
|
||
; 单用户每日私信条数上限(用户可设范围 0..该值;0 表示不限制每日条数)
|
||
daily_message_limit_max = 999
|
||
|
||
[theme]
|
||
; 主题系统:只改配色 / 图案 / 整页背景,不涉及布局。
|
||
; 作用范围:提问箱页面(主页 / 单题 / 密码 / 分享卡)、箱主本人的投票页与 /user 后台;
|
||
; 首页、探索等站方页面保持站点默认外观。
|
||
; default :站点默认主题(箱主未选择时使用)。填 default 或留空 = 现有外观。
|
||
; available :启用哪些主题,逗号分隔,同时是箱主设置页的白名单;
|
||
; 列了但 static/css/themes/<slug>.css 不存在的会被忽略并告警。
|
||
; 新增主题:把 static/css/themes/_scaffold.css 复制成 <slug>.css,
|
||
; 改选择器与令牌值,再把 <slug> 加进 available —— 不需要改模板。
|
||
; 需要图片素材的,放 static/images/themes/<slug>/(static/fs.go 已嵌 images/themes 目录)。
|
||
default = default
|
||
available = default,sakura,shiroko
|
||
; ⚠️ 没登记在 available 里的主题,即使 css 已随二进制发布也不会出现在设置页;
|
||
; 启动日志会打出「[theme] available 未登记以下主题」提醒。,shiroko
|