// Copyright 2022 E99p1ant. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package conf // ⚠️ 仅应由 "-ldflags" 注入。 var ( BuildTime string BuildCommit = "dev" // StaticVersion 静态资源缓存版本号:base.css 与主题 css 共用,改样式只改这一处。 StaticVersion = "20261022c" ) // 文本审核通道取值([security] text_censor_source)。定义在 conf 侧, // 让配置校验(conf.Init)与通道装配(internal/security/censor)共用同一份字面量。 const ( CensorSourceAuto = "auto" // 七牛优先、阿里云兜底(历史行为,留空同义) CensorSourceQiniu = "qiniu" CensorSourceAliyun = "aliyun" ) var ( App struct { Production bool `ini:"production"` ExternalURL string `ini:"external_url"` ICP string `ini:"icp"` DefaultLang string `ini:"default_lang"` // 站点默认语言(zh-CN/zh-TW/en/ja),留空回退简体中文 UptraceDSN string `ini:"uptrace_dsn"` QiniuAccessKey string `ini:"qiniu_access_key"` QiniuAccessSecret string `ini:"qiniu_access_secret"` AliyunAccessKey string `ini:"aliyun_access_key"` AliyunAccessKeySecret string `ini:"aliyun_access_key_secret"` SentryDSN string `ini:"sentry_dsn"` MaintenanceMode bool `ini:"maintenance_mode"` ShareBackground string `ini:"share_background"` FontPath string `ini:"font_path"` } // Theme 主题系统([theme] 段)。 // default :站点默认主题 slug,留空/非法回退内置 default(= 现在的外观)。 // available :启用哪些主题(逗号分隔),同时是箱主设置页的白名单; // 列了但缺少 css 文件的会被忽略并告警(见 internal/theme)。 Theme struct { Default string `ini:"default"` Available string `ini:"available"` } // Announcement 站内公告([announcement] 段)。解析与展示判定见 announcement.go。 Announcement announcementConfig Security struct { EnableTextCensor bool `ini:"enable_text_censor"` IPBlacklist string `ini:"ip_blacklist"` IPBlacklistRedirectURL string `ini:"ip_blacklist_redirect_url"` // TrustProxy 为 true 时信任 CDN 权威客户端头 CF-Connecting-IP / EO-Client-IP // (以及受信代理设置的 X-Real-IP / X-Forwarded-For);否则一律用 RemoteAddr, // 防止伪造 header 绕过速率限制与 IP 黑名单。 // ⚠️ 权威头只在 TCP 对端可信(回环,或命中 TrustedProxies)时才会被采信 —— // 见 shared.ResolveClientIP:公网对端带来的权威头一律忽略(P1-3 修复)。 TrustProxy bool `ini:"trust_proxy"` // TrustedProxies 逗号分隔的 IP / CIDR,仅限自己的反向代理 / LB。仅当 TCP 对端 // 落在该清单内才解析 X-Forwarded-For / X-Real-IP;留空则这两个头永不被信任。 // 回环地址(127.0.0.1 / ::1)无需登记即视为可信对端:cloudflared / 本机 nginx // 回源的连接必然由本机进程发起,外部无法伪造。 // CDN 直连回源(非 Tunnel)的部署形态下,必须把该 CDN 的回源网段登记到这里, // 否则它的权威头会被忽略、客户端 IP 退化为 CDN 边缘 IP。 TrustedProxies string `ini:"trusted_proxies"` EnableAutoBan bool `ini:"enable_auto_ban"` // fail2ban 风格:检测到扫描特征后临时封禁来源 IP // BanTrackNotFound 是否把未注册路径的 404 计入封禁统计。默认 false:仅命中后台 // 扫描特征路径(KindSensitive)才统计,避免共享出口(CDN 边缘 / CGNAT)下 // 正常用户被 404 探测流量连坐误伤。 BanTrackNotFound bool `ini:"ban_track_notfound"` BanThresholdSensitive int `ini:"ban_threshold_sensitive"` // 窗口内敏感路径命中次数 BanThreshold404 int `ini:"ban_threshold_404"` // 窗口内 404 次数 BanWindowSeconds int `ini:"ban_window_seconds"` // 统计窗口(秒) BanDurationMinutes int `ini:"ban_duration_minutes"` // 封禁时长(分钟) // DefaultModerationStrictness 站点默认审核严格度(lax / standard / strict), // 仅对未单独设置档位的账号生效(见 moderation.StrictnessFor)。留空或非法值回退 // strict(最严档),保证升级不会放宽拦截;默认拦截面可能较升级前略有扩大, // 希望更少误伤则显式写 standard。 DefaultModerationStrictness string `ini:"default_moderation_strictness"` // TextCensorSource 文本审核通道:qiniu / aliyun(内容安全 2.0 TextModeration, // 个人实名账号即可开通)/ auto(默认,留空同义:七牛优先、阿里云复核)。 // 仅显式选定的通道会被调用,缺凭据时启动即报错(见 conf.Init)—— // 审核失败是 fail-closed(拒绝提交),会让发布与私信整站不可用且现象隐蔽。 TextCensorSource string `ini:"text_censor_source"` // EnableHoneypot 开启「虚假后台登录界面」蜜罐:扫描器探测后台路径时 // 返回一个仿 WordPress 的假登录页,并记录其提交的用户名/密码等破解信息。 // 仅在「无任何路由匹配」的路径上生效,与真实业务路由零交集。 EnableHoneypot bool `ini:"enable_honeypot"` // HoneypotPaths 追加自定义诱饵路径(逗号分隔),与内置清单并集。 HoneypotPaths string `ini:"honeypot_paths"` // HoneypotRetainDays 蜜罐命中记录的保留天数,到期自动清理。 // ⚠️ 键名是 honeypot_retain_days(没有 tion)。写成 honeypot_retention_days // 会被 ini 静默忽略 —— 现场踩过这个坑,表现为「自动清理未启用」。 // 缺省 30 天(见 sanitizeHoneypot);显式 <= 0 也回退 30。 // 要关闭自动清理请用 honeypot_auto_clean = false。 HoneypotRetainDays int `ini:"honeypot_retain_days"` // HoneypotAutoClean 是否启用蜜罐记录的后台自动保留期清理。 // 缺省 true:honeypot_hits 是只追加的审计表,写入完全由外部扫描流量驱动, // 没有自动清理的话表体积只增不减(这是「数据库被扫描撑爆」的真实路径)。 // 关闭后仍可手动执行 TamaBox honeypot prune。 // ⚠️ 缺省值靠 HasKey 判定(见 sanitizeHoneypot):ini 的 bool 解析是 // 「有键才赋值」,若只看零值就无法区分「未配置」与「显式关闭」。 HoneypotAutoClean bool `ini:"honeypot_auto_clean"` // HoneypotMaxRows 蜜罐命中记录的行数上限。缺省 200000(约 53 MB)。 // 显式写 0 表示不设上限(逃生口,会告警提醒表只增不减)。 // 保留期是时间维度的上界,但攻击者可以用海量源 IP 在保留期内把表撑爆, // 这是第二道闸。实测单行约 281 字节(含索引,常规 UA),20 万行约 53 MB。 HoneypotMaxRows int `ini:"honeypot_max_rows"` // HoneypotThreshold 蜜罐「提交凭据」多少次即封禁。默认 1(一次即封): // 往假后台里填账号密码是证据最强的行为,抓到一次就足够。 // ⚠️ 它不作用于纯路径探测(GET 看一眼假登录页)—— 那类请求走常规 // ban_threshold_sensitive,否则扫描器 GET 一次就被封,永远走不到 // POST 提交凭据那一步,蜜罐也就拿不到字典。 // 缺省回退 1(见 sanitizeHoneypot)。 HoneypotThreshold int `ini:"honeypot_threshold"` // HoneypotBanMinutes 蜜罐封禁时长(分钟)。缺省 1440(24 小时), // 刻意远长于 ban_duration_minutes:命中蜜罐意味着对方已在提交账号密码, // 证据强度高于单纯路径探测(见 DefaultHoneypotBanMinutes)。 // 显式 <= 0 表示回退 ban_duration_minutes。 HoneypotBanMinutes int `ini:"honeypot_ban_minutes"` // HoneypotCredDir 凭据取证文件的存放目录。 // // ⚠️ 捕获到的用户名/密码**不入库**,只追加到这个目录下的按天文件 // (creds-YYYY-MM-DD.jsonl,一行一条 JSON)。原因:明文账号密码一旦入库, // 就会跟着进备份、主从复制和任何能连库的报表工具,事后无法彻底清除。 // 目录会被强制为 0700、文件 0600;留空使用默认 data/honeypot-creds。 // 目录不可写时只降级为「不保存凭据」,不会影响站点启动。 HoneypotCredDir string `ini:"honeypot_cred_dir"` // BanImagePath 被封禁时返回的图片路径(本地文件,相对路径以进程工作目录为基准)。 // // 留空、文件读不到、或内容不是图片时,一律回退为纯文字 "403 Forbidden" —— // 这条路径绝不能因为一张图把「拒绝」响应弄坏,更不能让缺失的资源变成 500。 // // 只接受 image/* 的内容:如果配成 app.ini 之类的文件会被拒绝并告警, // 否则等于把配置文件当响应体吐给每一个被封的攻击者。 BanImagePath string `ini:"ban_image_path"` SecurityAlertEmail string `ini:"security_alert_email"` // 触发封禁时告警收件人(留空不发送) // CSP 内容安全策略的生效模式:enforce / report / off,缺省 enforce。 // // enforce 下发 Content-Security-Policy(真正拦截);report 只下发 Report-Only // (照常加载、只记违规),用于临时观察。默认拦截而不是观察,原因是: // 没人会盯着 console,且不给 csp_report_uri 时 Report-Only 等于什么都没做, // 留个不生效的开关反而制造「已防护」的错觉。默认值的安全前提是白名单已经 // 用真浏览器逐页巡检过(csp_sweep.py:20 个页面零违规)。 // 上线后发现误伤,把 csp 改成 report 或 off 再重启即可恢复,不用改代码。 CSP string `ini:"csp"` // CSPReportURI 违规上报地址(可选)。留空时浏览器只把违规打到 console 不上报; // 用 report 模式观察时应当配上,否则「观察一周」就只是空谈。 CSPReportURI string `ini:"csp_report_uri"` } Server struct { Port int `ini:"port"` Listen string `ini:"listen"` // 绑定监听地址(含端口)。留空则默认 ":port"(监听所有网卡)。 Salt string `ini:"salt"` XSRFKey string `ini:"xsrf_key"` } Database struct { DSN string Type string `ini:"type"` User string `ini:"user"` Password string `ini:"password"` Host string `ini:"host"` Port uint `ini:"port"` Name string `ini:"name"` Schema string `ini:"schema"` DedupeUniqueFields bool `ini:"dedupe_unique_fields"` } Redis struct { Addr string `ini:"addr"` Password string `ini:"password"` } Recaptcha struct { CaptchaType string `ini:"captcha_type"` Domain string `ini:"domain"` SiteKey string `ini:"site_key"` ServerKey string `ini:"server_key"` TurnstileStyle bool `ini:"turnstile_style"` } Pixel struct { Host string `ini:"host"` } Upload struct { DefaultAvatarURL string `ini:"default_avatar"` DefaultBackground string `ini:"default_background"` DefaultBackgroundImageURL string `ini:"default_qrbackgroundimage"` AliyunEndpoint string `ini:"aliyun_endpoint"` AliyunAccessID string `ini:"aliyun_access_id"` AliyunAccessSecret string `ini:"aliyun_access_secret"` AliyunBucket string `ini:"aliyun_bucket"` AliyunBucketCDNHost string `ini:"aliyun_bucket_cdn_host"` ImageEndpoint string `ini:"image_endpoint"` ImageAccessID string `ini:"image_access_id"` ImageAccessSecret string `ini:"image_access_secret"` ImageBucket string `ini:"image_bucket"` ImageBucketCDNHost string `ini:"image_bucket_cdn_host"` } Mail struct { Account string `ini:"account"` Password string `ini:"password"` Port int `ini:"port"` SMTP string `ini:"smtp"` SkipTLSVerify bool `ini:"skip_tls_verify"` // 仅自签名 / 内网 SMTP 需要 } // Notify 站级通知通道配置([notify] 段)。与 [mail] 的「邮件给具体收件人」不同, // 这两个是「广播式」通道: // - WebhookURL:通用 Webhook(Discord / Slack / 企业微信群机器人等),站级事件 // (新提问 / 新回答 / 新追问 / 新私聊 / 新投票 / 安全告警 / 举报)统一推送。 // - TelegramBotToken:Telegram Bot token,配合每个箱主自填的 chat_id 实现 // 「新提问」点对点通知。 // 两者都可留空:留空的通道对应发送静默跳过(不影响邮件等其他通道)。 Notify struct { WebhookURL string `ini:"webhook_url"` TelegramBotToken string `ini:"telegram_bot_token"` } Service struct { Backends []struct { Prefix string `ini:"prefix"` ForwardURL string `ini:"forward_url"` } TLSInsecureSkipVerify bool `ini:"tls_insecure_skip_verify"` // 仅内网自签名后端需要 } )