Why: The comparison has to say what was borrowed, what was refused, and which product behaviors changed, without copying upstream text. Evidence: The record lists the verified security gaps and the features implemented in this task. Proposal section 2.5 links to it. Impact: Deferred and rejected items stay deferred and rejected. Adopted context and secret behavior is no longer described as a later batch. Refs: feature/sillytavern-review
6.4 KiB
6.4 KiB
调研:SillyTavern 1.19.0
- 状态:记录(2026-10-04);owner:weichen;任务:Dev 仓
feature/sillytavern-review - 对象:SillyTavern/SillyTavern
06bde939(release 1.19.0,2026-09-14)。AGPL-3.0,另有标注 GPLv3 的 RisuAI 片段。按固定提交只读对照,不建长期克隆,不拷源码。 - 本仓事实以任务起点 Dev
4fd03eee为准。前序:2026-10-03-pi-zcode-second-pass.md、2026-10-02-agent-stack-completion.md。 - 规则与 pi / ZCode 两份记录相同:只借设计。不拷代码、提示词或界面文案。本仓各子仓没有 LICENSE、以单二进制分发,拷入 AGPL 代码会让整个产品落入 AGPL,网络使用条款同样生效。
结论先说:上下文工程值得借的是「统一注入登记 + 按预算组装」,平台侧值得借的是带标签的密钥、设置快照、以及听口令时拒绝无认证的对外监听。本仓的 SSRF 与账户已经更严,不借。安全缺口(输出未清洗、缺响应头、不校验来源、对外监听无认证)和下面的采纳项在同一任务里落地。扩展系统、脚本宏、群聊、角色卡、自定义 CSS、JSONL 存储、删除消息和自动触发的快捷回复不借。
1. 来源现在是什么
SillyTavern 把要注入模型的文本放进统一登记表:每条有位置、深度、角色、顺序和扫描范围,再由一次按 token 预算的组装把它们收成请求。预算不够时只裁最老的历史。世界书、作者注、记忆和向量库都走这条登记,而不是各写一条拼接。
平台侧用带标签的多密钥(同一服务一把生效)、设置快照、以及「监听且未开认证就拒绝启动」。请求侧还有 Host / Origin 检查。聊天用 JSONL,带滑动候选和父指针检查点。扩展是未沙箱的同源脚本。
本仓已经有更严的 SSRF(fetch_url 和链接预览在拨号时校验解析出的 IP,黑名单含 0.0.0.0/8 和 100.64.0.0/10)和账户(argon2id、重置密码即吊销服务端会话、登录限流)。这些不改。
2. 对照时核实的安全缺口
行号是任务起点那一版本仓源码,实施后位置会移动。
| 缺口 | 起点位置 | 处置 |
|---|---|---|
模型输出、卡片和文档预览把 HTML 直接写入页面。marked 15.0.12 不清洗 |
Frontend Conversation.tsx、previewsetup.ts;UI UIRenderer.tsx、FilePreview.tsx |
@ui 的 safeHtml 成为唯一写入点。外部图片改成「外部图片 · 域名」链接。同源地址不再用 allow-same-origin 内嵌 |
| 没有 CSP / nosniff。HTML 工件按页面直出 | Backend server.go 的静态页与 artifactContent |
页面 CSP、nosniff、Referrer-Policy。html/svg/xml 工件加 sandbox |
| 不校验 Origin / Host。SQLite 档默认不开认证 | app.go 的认证开关 |
RequestGuard 包在组合根。写请求核对 Origin 或 Sec-Fetch-Site。未开认证时 Host 只允许 IP、localhost 和 allowedHosts |
| 对外监听且未开认证时照常启动 | .env.dev 曾把 dev 听在 0.0.0.0:8788 |
非回环、未开认证、又没有 --insecure-listen 时,打开数据库之前拒绝启动 |
3. 采纳并已在本任务实施
原先记为后续批次。任务范围改为同一任务做完,落点如下。
| 主题 | 上游设计 | 本仓落点 |
|---|---|---|
| 密钥独立存储 | src/endpoints/secrets.js:多把带标签的 key,一把生效,读出只给掩码,迁移前备份 |
secret_keys。GET 只返回掩码。启动时先写设置快照,再把 models.*.apiKey 和 tools.tavily.apiKeys 迁入并从表单设置清掉。运行时优先用生效密钥 |
| 设置快照 | 启动和保存后留档。上游恢复前不留档,这一点不照搬 | settings_snapshots。正文与上一份相同则跳过。保存间隔短于 10 分钟则跳过 |
| 个人指令与会话要求 | persona 与 Author's Note | context.userInstructions 与会话 sessionNote。经 agent-native reminders.Attach 附在最后一条用户或工具消息上,系统前缀不动 |
| 条件注入 | 世界书的关键词、次关键词逻辑、扫描深度、常驻、顺序、预算 | context_entries。matchModule 仍只看本轮子串;条目在组装末尾另判 |
| 上下文快照 | 提示词分段视图 | ContextObservation 增加 sections 与 prefixHash。界面折叠条显示估算 token 和前缀哈希 |
| 估算口径 | 上游按 token 预算裁历史 | 统一为 UTF-8 字节 / 3.35,再用上一请求的 prompt token 校准。context.contextWindow 进入 schema,默认 32000,只丢掉最早的历史 |
| 消息整形 | src/prompt-converters.js |
PrepareMessages。merge 合并相邻同角色。strict 再把第一条之后的 system 收成 user。工具调用和结果不合并 |
| 复制、排除、候选回复 | 滑动候选与检查点 | 复制在界面完成。排除按消息及其同一 run 成组,记录仍在。重新生成不删旧回复,写入同一 variantGroup,并按用户消息做文件回滚 |
用户 / 命令 |
快捷指令,但不做自动触发 | user_commands。名称符合 user.<slug> 时登记为 agent-command 的 SourceUser。发送时把模板展开成普通用户消息 |
4. 暂缓
- Claude 缓存断点:等 Anthropic 原生协议。
- 编辑重发与会话分支:等候选回复用过再看父指针方案。
- 窗口外同会话召回、用户级知识库:等会话经常超出窗口再看。
- 世界书的概率、递归、包含组和定时效果。
- 密钥静态加密。
- i18n:真要做时不能拿原文当键。
- 出站代理:只能用于模型端点。
- 画像导入导出。
5. 拒绝
- 拷代码、提示词和界面文案。
- 扩展系统(同源 JS、没有沙箱)和带完整 Node 权限的服务器插件。
- STscript 和宏语言。
- 群聊、角色卡、视觉小说。
- 自定义 CSS 和平滑打字流式:与 DESIGN.md 冲突。
- JSONL 整文件覆盖和逐行扫描搜索。
- 删除消息。用「排除出上下文」代替。
- 自动执行的快捷回复触发器。
6. 不回退的既有决定
认证默认仍是 auto:显式 on,或不是 off 且配置了 DSN,才开认证。不加 CSRF token,不加 CSP 上报端点。不改 SSRF 黑名单,不改账户哈希和会话吊销。