- 2026-10-29.md:第三批 7 项改动与验证数据 - MEMORY.md:三条长期结论 —— 「用户主动删除 = 真删」(模型全带 DeletedAt,默认 Delete 是软删)、 「配额必须与内容同事务」(incrementQuotaTx 是唯一实现,且投票口径是票数不是个数)、 「错误页不套模板」(f.NotFound 不经过组级中间件,注入 template.Data 会 panic) - topics/quality.md:标注第三批范围 新踩到的三个坑: 1. gofmt 会把注释列表 `// 1) ...` 改写成 `// 1. ...`,被标为未格式化时先排除这个 2. Select(...) 漏列 = 字段静默为空(摘要永远是空串,只有一项测试红) 3. CDP 验证纯前端:chrome 必须作为独立后台进程启动,跟在别的命令后面 `&` 会随 shell 退出被回收;headless=new 的空标签页 type 常是 other
9.8 KiB
9.8 KiB
TamaBox-v4 长期笔记(索引版)
明细已拆分到 topics/*.md(按需 Read 全文):
coop 协作 / build 构建测试 / embed 行尾·模板·embed / review 审核处置举报 /
gorm GORM / style 前端样式 / theme 主题 / builder 主题生成器 / share 分享卡 /
honeypot 蜜罐多家族伪装 / stats 全站数据页 / sec 安全评估·客户端 IP 信任 /
quality 业务正确性·运维·前端(2026-10-29) / misc 其它约定。
协作
- 结论分级 P0/P1/P2;先根因取证 + 实测复现,别只静态推断。
- 改代码前先出变更清单;重大问题显式指出。用户常授权自主推进。
- 用户是运营者也是圈内人:处置必须可解释、可留痕、可申诉,不做自动级联封号。
构建与测试(详见 topics/build.md)
- 无 libwebp/GCC:
internal/storage/s3.go挡编译 → overlay 换桩dev/s3stub/s3_stub.go。 - 「编译卡死」=
proxy.golang.org不可达。每条 go 命令前export GOPROXY=https://goproxy.cn,direct GOSUMDB=off GOFLAGS=-mod=mod。 - DB 测试:
DB_TYPE=postgres DB_USER=postgres DB_PASSWORD=15207786347hXPm DB_HOST=127.0.0.1 DB_PORT=5432(DSN 不带库名)。 - store 方法内用
NewXStore(db.DB),别引用包级单例。 本沙箱起不了2026-10-27 起可在 WSL2 Debian 上真跑(PG+Redis+systemd,端口 5877): 详见 topics/build.md「本地部署」小节。要点:TamaBox webenv -u GOROOT(bashrc 指着 1.26.3)、[recaptcha] captcha_type = math、Windows 侧 127.0.0.1:5877 被占要用 WSL eth0 IP。 HTTP 端到端与 P1-3 的双路径 A/B 都能在这里做(回环=可信、跨主机=不可信)。- python urllib 不稳定(用 curl);python 别用
/tmp;PIL 在~/.workbuddy/binaries/python/envs/default。 - Windows 上
Path.write_text()默认把\n写成\r\n—— 改 LF 文件必须传newline="\n"或走 bytes,否则整个文件行尾翻车。症状:gofmt -l突然把整文件标为未格式化。 排查看git diff --stat:真实改动应是几十行,整文件重写就是行尾变了; 恢复用git show HEAD:<file>取其行尾再统一替换。 另:本项目core.autocrlf=true,仓库存 LF、工作区 checkout 成 CRLF —— 判断基准要看git show。
行尾 / 模板 / embed(topics/embed.md)
- 行尾混合不可推断,改前用 Python 数
b'\r\n';gofmt 走dev/gofmt_keep_crlf.py。 templates/embed.go是显式清单:新增顶层模板必须加//go:embed。//go:embed的*不匹配_/.开头文件。
审核 / 处置 / 举报(topics/review.md)
- 线上通道 = 七牛;裁决在
censor/policy.go+moderation.Check,新增调用点必须走moderation.Check,不可用时 fail-closed。 - 严格度存
users.moderation_strictness;受处置用户强制 strict(moderation.EffectiveStrictness),查询失败回退用户设置。 - 同请求渲染只能用
ctx.SetError/SetInternalError,不能*Flash。 - 写路径查功能开关用
db.FeatureStateOfStrict(失败即拒);FeatureStateOf的 fail-open 只给读展示用 —— 它的默认值对提问/投票是「开启」,等于让一次配置读失败 绕过箱主的关闭与定量上限。 - 处置四级 L1–L4;到期靠
expires_at无 cron;restrict是替换语义;比较用RestrictionAtLeast。 - L2 强制私密须在审核通过后、与写回答同一条 UPDATE(
AnswerByIDPrivate)。 ListAPI不经 Pager 中间件,L4 与页面密码须自校验;分享卡拦截须在缓存查询之前。- 举报:免登录+验证码+限流;同 IP 同题唯一索引 → 幂等;箱主只看次数不给 IP。
- 告警 24h≥8 或 7d≥20 → 邮件;
[security] security_alert_email为空则静默跳过。 - 处置覆盖面:新增任何「用户发起」的写入口都要问一句「这里要不要查
ActiveLevel」。 提问链路 7 处齐全;投票链路曾 0 处(L4 后/vote/{token}照常可投、可发新投票、探索页照常曝光), 2026-10-29 已补齐:vote.Page/vote.Submit拦 L4、VotePublish拦 L2、探索页按ActiveLevels批量过滤。
GORM(topics/gorm.md)
- 默认值必须带引号;事务里每句
tx.WithContext(ctx);链式对象带状态,统计/更新各自重建 query。 - 软删除+唯一索引 = 槽位永久占用;级联删除一律
Unscoped()。 - 「用户主动删除」= 真删:模型全带
dbutil.Model的DeletedAt,默认 Delete 是软删 —— 提交记录里的voter_ip/文本、提问里的FromIP会永久留在库里。删投票/删提问已改Unscoped并收进一个事务(Votes.DeleteWithCascade、Questions.DeleteByID)。 - 配额必须与内容同事务:
incrementQuotaTx(tx,...)是唯一实现;别在 handler 里 再补一次IncrementQuota(那是独立写入,失败只 Warn →quota_count永久偏低)。 注意口径按功能不同:提问/私聊是「条数」,投票是「累计收到的票数」。
前端样式(topics/style.md)
static/css/base.css(?v=取conf.StaticVersion,改完 +1)。令牌在:root+html[data-theme="dark"],新样式一律var(--tb-*)。- 移动端(≤640px)语言/主题按钮在固定底栏
#tb-mobile-utils;箱主页有意无语言切换。 - 错误页不套模板:
f.NotFound不经过组级中间件(template.Templater/Contexter连同 session、CSRF 都在f.Group里),在里面注入template.Data会 panic。 404/500 统一走internal/route/errorpage.go的内联 HTML +i18n.T, 语言从Accept-Language猜(pickErrorPageLang)。Recovery 也是自写的。 - 提问者找回自己的提问:
templates/question/{success,list}.html+POST /api/v1/ask-status。 记录只在本机 localStorage;接口必须同时给对 token 才返回信息,三种失败情形响应完全一致。 - UIkit 3.3.3:
.uk-flex::before/::after{display:none};主色是编译期常量,需在 base.css 末尾映射。 - 视觉自检:
TEST_PREVIEW=1 go test ./templates -run TestGenPreview;截图用chrome --headless=old。
主题(topics/theme.md、topics/builder.md)
- 三个注入点按箱主/发布者的
Theme;投票页语言也跟发布者(applyOwnerLocale)。 - 一套主题 =
static/css/themes/<slug>.css,只覆盖令牌;显示名用{{ themeName ... }},别直接printf "theme.name.%s"。 [theme] available是硬白名单(启动时读一次);css 必须成对写亮/暗两个选择器。- 装饰层固定 10 槽,三组
--tb-deco/-size/-position必须等长;位置用两值calc(0%|50%|100% ± N)。 - 主题生成器
dev/theme-builder/:非破坏性图片处理、layerSize/unitGeom必须共用同一函数、hideDeco会把 @media 10 槽全写 none(窄屏素材消失的头号来源)。
安全(topics/sec.md)
- 权威头
CF-Connecting-IP/EO-Client-IP只在 TCP 对端可信(回环 或trusted_proxies命中) 才采信,公网对端一律忽略;唯一读取点internal/shared/ip.go,别在别处另读 header。 - 生产回源 = cloudflared Tunnel 回本机(对端 127.0.0.1)→ 零配置;
trust_proxy两套环境都 true。 - EdgeOne 已取消(境内速度差)。重新启用前必须把 EO 回源网段写进
trusted_proxies, 否则 EO 线退化成按边缘 IP 聚合(限流互挤、自动封禁误伤整片)。app.sample.ini已注明。 - 跳转参数只走
shared.SafeRedirectTarget;url.Parse会解%0d%0a,必须逐 rune 查u.Path。 - 同一 handler 里 GET 有所有权校验 ≠ POST 有(私聊
SendMessage曾漏,可跨箱骚扰)。逐分支核对。 - CSP 默认 enforce(逃生口
csp = report|off后重启);script-src必须含mami.shiroko.one(umami 动态注入,grep 抓不到)与按sentry_dsn条件放行的browser.sentry-cdn.com。 改 CSP 白名单后必须跑csp_sweep.py(真浏览器巡检),静态检查不算数。 - 注册防枚举:邮箱重复/停用与正常注册共用同一个成功出口;代价是忘密只能走找回。
flamego.Classic()自带Logger()会记RequestURI(含 query) ——?t=私密题钥匙、?code=改密凭证都会明文进日志(已实测)。要自写访问日志,只记URL.Path。- 报告
outputs/security-audit-2026-10-25.md+ 复现脚本outputs/sec-audit-2026-10-25/; 非安全向的复盘见outputs/review-2026-10-29.md。
其它约定(topics/misc.md)
- 健康检查:
/healthz永远 200(不碰任何依赖,只看进程活着)+/readyz查 DB(失败 503)。 别把 DB 检查并进 healthz —— 数据库抖动会让进程被反复重启,对恢复毫无帮助。 - 校验用
valid:"required;maxlen:N"标签。 - 只有箱主
/user面板,无管理员角色、无站方后台(用户要求不要加):站方动作只走 CLI / 邮件 / 改配置重启。 - CLI 有两个构建入口必须同步:根
go build .与 Dockerfile 的./cmd/。 - 现有 CLI:
web censor uid ban restore reports restrict unrestrict hide-public。 - git
https://git.shiroko.one/tamakyi/tamabox(main),git push可直推;outputs/已 gitignore。 - ImageGen 的
background: "transparent"至今不生效(返回 RGB、无 alpha 通道)。要透明素材: 提示词把背景写成一个具体纯色(不要写 white,白衬衫/光环会被一起吃), 再按该色值算 alpha:alpha = clip((maxChannelDiff - 6) / 12),最后MinFilter(3)收 1px。 闭合图形(如光环圆环)内部的白/底色用 floodfill 填不到,按色值抠才干净。 - 生成图的右下角有 "AI生成 WORKBUDDY" 水印,做素材时统一裁掉底部约 10%(切口平直,正好贴页面下沿)。