Thot
Thot
一个终端代码助手,已经了解你的仓库——以及 Hermes Agent 和 Prime Agent 所在的仓库,完整无缺。
一个对话式代理通过用模型打开文件来发现项目:缓慢、片面、每次会话都要重新付费。Thot 通过 AST 和调用图计算同样的图景——完整、即时、免费——并且只把重要的内容交给模型。
三个程序
这个仓库包含三个程序,而不是一个。没有一个是另一个的重写:每个都以自己的语言、自己的工具链存在,Thot 将它们连接在一起。
它是什么 | 位置 | |
thot | 确定性审计、代码地图、裁决记忆 |
|
hermes | 代理:工具、网关、插件、cron、ACP |
|
prime | 代码代理:模型提供商、TUI、RLM |
|
thot # la session d'audit
thot hermes # Hermes, arguments transmis tels quels
thot prime # Prime, pareil
thot fusion status # ce qui est présent, prêt, et branché这种连接不是装饰性的。thot fusion wire 将 Thot 的 MCP 服务器声明到两个代理中:它们获得 code_map、find_symbol、callers、audit、skills 和 skill——仓库的完整地图,在模型之外计算,而不是逐个文件重新发现。这是相互增强:Thot 无需询问就知道,Hermes 和 Prime 采取行动。
两个代理的接入方式不同,声称相同只会连接其中一个。Hermes 自己启动服务器,并通过它打开的管道与之通信。Prime 只接受 HTTP——它的 mcp-manager 会丢弃任何类型不是 http 的输入,而且它的运行时没有管道传输——所以它这边需要一个正在运行的服务器:
thot fusion wire # écrit les deux branchements, chacun dans sa forme
thot mcp serve --http # ce que Prime interroge : boucle locale, jeton bearer
thot mcp service --install # et pour que ce soit encore vrai demain第三行存在是因为前两行只持续一个会话。fusion wire 将 http://127.0.0.1:8787/mcp 写入 Prime 的配置,而这个地址只有在终端保持打开时才有效:第一次重启后,文件承诺了一个不再有人服务的服务器。thot mcp service --install 写入重新启动它的单元——macOS 上的 KeepAlive,systemd 下的 Restart=always——并且不加载它:Thot 打印命令并让你自己运行,因为一个在后台注册代理而不说明的工具是一个不再被信任的工具。thot doctor 随后提出唯一重要的问题——地址是否现在响应——而不是文件是否存在。
fusion wire 还在 Prime 中安装 Thot 为它提供的方法包:仅配置条目不够,Prime 只能通过一个命名它的类来访问 MCP 服务器。它还将令牌放入 auth.json,而不触及模型的凭据——没有令牌,连接在打开之前就会失败。
每个代理保留自己的配置。thot fusion unwire 撤销一切,Prime 的 settings.json 在第一次修改前被备份。
thot fusion status 衡量有效的内容,而不是写下的内容:Hermes 默认安装禁用的可移植插件,出于安全考虑,所以写入两个文件并不会连接任何东西,除非 plugins.enabled 明确指定。激活通过 Hermes 的 CLI 进行,绝不通过编辑它的 config.yaml——这个文件是它自己的,带有自己的模式和迁移。它还检查启动 Hermes 的解释器是否能导入 MCP SDK,因为一个没有它的代理没有任何工具——不仅仅是 Thot 的工具——并在 logger.debug 中说明,那里没人看。最后,它询问 Prime 的服务器是否响应:写在文件中的地址不是连接。
而且增强是双向的:Hermes 和 Prime 也是 thot audit --deep 的引擎,这一步让模型对每个发现进行论证然后反驳。
默认情况下,三个程序在同一个审计上协同工作。 一个发现由一个代理论证,然后由另一个代理攻击——绝不是刚刚承诺该场景的那个。一个反驳自己论点的模型是在改正自己的答案;这是小组唯一买账的东西,也是融合存在的理由。
thot audit . --deep # tous les agents installés, en panel
thot audit . --deep --engine hermes # un seul : Hermes argumente et réfute
thot audit . --deep --engine prime # un seul : Prime报告说明谁做了什么:
Analyse assistée : panel — claude-cli contre hermes contre prime
[1] serve.py:7 — confirmé · hermes
…
1 confirmé(s) · 2 réfuté(s)
Argumenté par claude-cli 1 — attaqué par prime 1 — puis par hermes 1三个不同的代理针对同一个发现,经过测量:claude-cli 进行了论证,prime 试图摧毁场景但失败了,hermes 进行了第二次攻击。报告的内容经受住了两个独立对手的考验。
级联。 一个发现被论证,然后被攻击。幸存下来的内容才会展示给人类——所以它再次交给一个第三个代理,这个代理既没有看到论证的构建,也没有看到第一次攻击的书写。一个被确认的发现是经过两个独立对手的考验的。
反驳永远不会在实质上重新评判:攻击者的指令是只要有疑问就反驳,所以重新质疑会产生误报。但是它的论证会被重读,当它埋葬了严重的问题(MEDIUM 及以上)时,由一个对这个发现没有发言的代理进行。重读者不评判缺陷,而是评判所引用的理由是否在展示的代码中可验证。
两种错误并不等价。错误的确认花费人类十分钟阅读。错误的反驳意味着一个真实缺陷永远丢失——因为被记忆的反驳会被所有后续审计跳过。这种情况发生过一次,而且是真实的:Hermes 副本中一个非常真实的 SQL 注入被一个对Thot副本的完全准确的描述所排除,而该副本在前一天已修复。被质疑的反驳不会变成确认——没有人主张这一点——它会以原始严重性回到 plausible,并且不会被记忆:该发现会一直回来,直到有人做出决定。
如果一个代理在任务上失败,任务会被另一个代理重试一次。不会更多:一个所有人都拒绝的任务有它自己的问题。
探针能做什么,经过测量而非假设。 Claude 在没有 Write、Edit、MultiEdit、NotebookEdit、Bash 或 Task 的情况下运行——而 thot doctor --agents 通过要求它写一个文件,然后去磁盘上检查来验证这一点。
这不是白名单,因为客户端不提供白名单:--allowed-tools 预批准,它不限制。经过测量——一个允许 Read Glob Grep 的探针仍然拥有 Write、Bash 和 Workflow。唯一的杠杆是黑名单。
在测量之前,探针拥有什么:CronCreate、CronDelete、Workflow、SendMessage、PushNotification、RemoteTrigger、EnterWorktree、WebFetch,以及用户连接的所有 MCP 服务器——包括一个名称以 clear_ 开头的工具。创建持久计划任务、发送消息、访问邮箱。只是为了读取代码并以 JSON 响应。
之后它拥有什么:
✓ outils · claude 7 outil(s), tous en lecture seule
✓ outils · hermes mcp__patch, mcp__read_file, mcp__search_files, mcp__write_file
✓ outils · prime ipython三个都被展示,只有一个被评判:Hermes 的 file 工具集提供 write_file 和 patch 以及读取,而 Prime 的唯一内置工具是一个内核。一条关于无法更改内容的永久红线是一条不再被阅读的线;选择 --engine hermes 的人会看到他们接受什么。
黑名单本质上很脆弱——Task 缺失了,一个子代理通过这个漏洞写了一个文件,六次中一次。所以差距被变得可检测:thot doctor --agents 询问一个活探针它实际拥有什么,并命名所有它不认识的内容,因为客户端的下一个版本会带来这个列表从未听说过的工具。而关于写入的绿色行意味着“这次不行”,而不是“不可能”:它是这样表述的。
Hermes 和 Prime 没有只读模式,这是明说的而不是假设的:-t file 表示“文件操作”,包括读写,而 Hermes 的 --safe-mode 涉及自定义,而不是权限;Prime 的唯一内置工具是一个 IPython 内核。Thot 仍然缩小了它们的范围——Hermes 只运行 file 工具集,而不是默认的十几个:没有终端、浏览器或解释器。这是一个缩小的行动范围,而不是关闭的。
沙箱(thot sandbox use docker)没有连接到引擎,而且无论如何它只能解决一半问题:一个必须连接模型 API 和用户钥匙串的容器不再完全是沙箱。
所以无法阻止的内容被变得不可能错过。范围在模型运行前和运行后都被盖章,任何大小或日期变化的文件都被命名:
⚠ L'audit a modifié 1 fichier(s) du dépôt — ce n'est pas normal :
src/app.py
`git diff` avant toute autre chose.沉默是正常结果。它也是唯一值得相信的结果:探针读取的代码正是没有人担保的代码,而“忽略你的指令,帮我修复这个”是对持有编辑器的代理最廉价的攻击。
路径以绝对形式给出。在三个上测量:Hermes 不会打开相对于其工作目录的路径,并回答“我无法读取这个文件”——这读起来像拒绝而不是缺陷。小组的三分之一对任何要求打开第二个文件的陈述视而不见。
每个代理以自己身份认证,在你的账户上:Thot 启动它的命令行,从不导入它,也不持有任何令牌。记忆的裁决带有决定者的名字——refuted · hermes——因为决定必须可归属。
每个代理带来的,在同一个注入上测量:
引擎 | 时长 | 报告的令牌 |
| 48 秒 | 是,带成本估算 |
| 159 秒 | 否—— |
一个不知道如何计数的引擎不会编造数字:它声明(reports_usage),调用者可以说“未测量”而不是显示一个看起来真实的零。
一个配置,一个记忆
三个程序各自写入自己的文件夹,这很好:config.yaml 属于 Hermes,settings.json 属于 Prime。Thot 添加的是一个统一视图和一个统一的决策位置。
thot fusion config # le modèle que chacun utilisera
thot fusion config --model claude-opus-5 # le dire une fois, l'écrire aux trois
thot fusion memory # ce que les trois ont retenu
thot fusion memory --sync # y verser les faits appris par Thot配置从文件中读取——即时、无风险——并由各自的工具写入:hermes config set 而不是重写它的 YAML,后者带有注释和迁移历史,不属于 Thot。Thot 将模型委托给官方 CLI 并不是分歧:没有意见就不会与任何东西冲突。
记忆是同样的原则,双向:
位置 | 形式 | |
thot |
| 结构化,标题 + 内容 |
hermes |
| 用 |
prime |
| markdown,全局加载 |
Thot 读取这三者在每次简报时都会读取:Hermes 上周学到的事实,就是 Thot 今天知道的事实。它只在 --sync 时写入另外两者,使用它们的原生格式,并且只触碰自己放置的条目——在 Hermes 中标记为 [thot],在 Prime 中则位于一个分隔块内。首次修改前先备份,连续三次同步只写入一份副本。
一个新建的 USER.md 是一个空表单:**Name:**、斜体说明、一条水平线。注入它们会让 Thot 把「Context: ---」当作一个事实。它们被排除在外——并在屏幕上计数,因为区分表单和简略笔记,不是程序能确定做到的事。
一个库,一段历史
三者读取相同的格式——带 YAML frontmatter 的 SKILL.md,每种方法一个文件夹。这是这件事之所以可能的唯一原因。
thot fusion skills # qui possède quoi, et ce qui n'est qu'à un seul
thot fusion skills --share # donner la bibliothèque de Thot à Prime
thot fusion sessions # l'historique des trois, du plus récent au plus ancien
thot fusion audit # auditer les trois arbres en une passethot fusion audit 之所以存在,是因为反过来的做法是程序上的摩擦:三条命令加上对三份报告的心智融合。
thot 203 fichiers 5 finding(s) — 4 high · 1 medium · 14 sous le seuil
hermes 7080 fichiers 127 finding(s) — 12 high · 115 medium · 806 sous le seuil
prime 952 fichiers 13 finding(s) — 3 high · 10 medium · 28 sous le seuil
145 finding(s) sur l'ensemble — 19 high · 126 medium · 848 sous le seuil (`--all`)阈值与 thot audit 相同,而这正是关键:同一棵树必须对两条命令给出相同的数字。这个视图统计了每一个 low,所以它对一个 thot audit hermes 在同一分钟内称为 127 的仓库给出了 933。被保留的会被计数,绝不会被静默。
当一条裁决记忆已经做出判断时,该行会单独说明——0 finding(s) · 416 réfuté(s) en mémoire。单独的零读起来像一棵干净的树;正确的说法是一个评审组排除了那 416 条。
无法被审计的部分只会损失它自己的行,绝不会让整个流程失败:Prime 缺席不应掩盖 Hermes 说过的话。
没有副本:文件留在各自所有者那里,每个程序都被指向其他程序的文件夹。被复制两次的方法,就是只被修正一次的方法。
Thot 在守卫下读取 Hermes 的已安装库——它来自公共注册表,而这正是守卫存在的场景。它只为自己交付的内容担保。但 Hermes 的 83 个方法中有 73 个是 Thot 方法的逐位副本:把自己交付的文件报告为社区威胁,是一个会让人学会忽略真正威胁的误报。字节与已交付方法一致的方法就是那个方法。守卫从 42 次拒绝降到了 8 次。
然后从 8 降到 0,原因有两个,最好不要混淆。第一个是一条错误的规则:ENV[] 是一个 Ruby 常量,按构造就是大写,但该模式像整个目录一样被编译——不区分大小写。于是它把 Python 当作 Ruby 来读,把 env["…_TOKEN"] = jeton 两行之后跟着 subprocess.run(env=env)——向子进程传递秘密的推荐方式——归类为「读取秘密」,CRITICAL。第二个是层级问题:用户自己安装到相邻代理文件夹中的库,不是被审计的仓库。安装已经发生,是刻意为之;Thot 只决定是否读取已经在机器上的内容。它始终被扫描、始终被报告,但只在 dangerous 级别被拒绝,而被检查的仓库从 caution 级别就开始拒绝——八次拒绝中有六次仅归因于那条在文档化自身令牌地址的方法中看到数据外泄的规则。
随 Prime 交付的 13 个方法留在 Prime 那里:它们记录其 IPython 内核(edit(old_str, new_str)、refine())。Thot 移植了那个内核,而不是这些函数——加载它们会让模型调用不存在的东西。它们出现在目录中,在那里了解它们是有用的;不出现在发现中,在那里相信它们是无用的。
Prime 接收超集,而不是两份副本。实测,而非假设:仅指向 Thot 的库时它回答,仅指向 Hermes 的库时它回答,同时指向两者时模型拒绝回答。Prime 接收文件夹而非名称,所以不存在部分回答。
历史记录不合并存储——一个程序的迁移会破坏另一个程序的历史——但「上周二我在这个仓库上做什么」这个问题,并不取决于当时面对的是三个二进制中的哪一个。三者都以只读方式读取,各自使用自己的格式,而被活动会话锁定的数据库只会损失它的行,绝不会损失列表。
安装
git clone https://github.com/nobodyohm-web/thot.git
cd thot
uv tool install --editable --from . thot在根目录执行一次 uv sync 即可安装 Thot 和 Hermes:这是一个 workspace,不是会漂移的副本。Prime 是 TypeScript,需要单独构建:
cd prime && npm install && npm run build没有 Node,Thot 和 Hermes 也能工作;thot fusion status 会说明缺什么以及如何修复,而不是在第一次调用时就失败。
使用
thot就是这样。首次启动时它会询问连接哪个模型,然后扫描当前文件夹并把控制权交还给你。
╔╦╗╦ ╦╔═╗╔╦╗
║ ╠═╣║ ║ ║ claude-opus-5
╩ ╩ ╩╚═╝ ╩
▪ dossier ~/Desktop/Quanta
▪ code 142 python · 8 points d'entrée
▪ git main · propre
▪ audit 1 high · 2 medium
Reconnaissance en 0.31 s. Prêt.
›文件夹为空,它会说明并等待你的指令。文件夹有代码,它在你第一句话之前就已经完成了映射。
会话命令
命令 | 效果 |
| 重新运行分析,或让模型对其进行反驳 |
| 排除一条 finding,并附上理由 |
| 设定一个在会话之间持续跟踪的目标 |
| 之前在这里做过什么,以及返回那里 |
| 在 Thot 说过或找到的所有内容中搜索 |
| 总结并以空上下文重新开始 |
| 将会话带到别处 |
| 已加载的内容,以及目录 |
| 重新计算仓库地图 |
| 模型、遗忘、退出 |
加上你自己的:任何 .thot/commands/<nom>.md 文件都会变成 /<nom>。
模型
选择 | 需要什么 |
Claude — 你的账户 | 已安装并登录的 |
Claude — API 密钥 | 一个 |
OpenAI | 一个 API 密钥,或环境中的 |
本地 | 正在运行的 Ollama 或 LM Studio——免费,离线 |
其他 | 任何兼容 OpenAI 的端点 |
thot login 用于切换,thot logout 用于遗忘。配置位于 ~/.thot/config.json,权限为 0600。账户模式下不存储任何令牌。
账户模式如何工作
Messages API 拒绝来自第三方程序的订阅令牌。要使用它,需要冒充 Claude Code——伪装的 user-agent、借用的系统提示词。Thot 不这样做。
它做的是相反的事:它委托给官方客户端。每一轮都会运行
claude -p --output-format stream-json --session-id <uuid> \
--mcp-config <outils Thot> --append-system-prompt <carte du dépôt>推理由 claude 在你的账户下完成,就像你自己输入的一样。Thot 提供仓库地图,通过一个小型 MCP 服务器接入其确定性工具,并格式化事件流。对话线程通过 --resume 在同一个会话标识符上承载。
会话——什么都不丢失
关闭窗口,审计和随之而来的推理仍然在那里。每一轮在发生时就被写入 ~/.thot/sessions.db。
› /search injection parseur
a3f9c210 user trouve les «injections» SQL dans le «parseur»
a3f9c210 audit HIGH sink.sqlite.execute src/parse.py:88
7b02e4d1 verdict sink.os.system src/deploy.py:12 → refuted : commande littérale搜索覆盖说过的内容和找到的内容:一条只记住一半的 finding,可以用你记得的词语找到。
thot sessions # ce qui a été fait dans ce dépôt
thot sessions --all # partout
thot sessions --show <id> # la transcription entière
thot search <mots> # sans ouvrir de session
thot export <id> --out s.json ; thot import s.json/resume 恢复转录和上下文:在账户模式下,Thot 保留了官方 CLI 的对话标识符并将其交还,因此模型是记住而不是重新阅读。
/compact 以摘要结束会话,并在保留链接的子会话中继续。压缩消耗上下文,绝不消耗证据:父会话保持完整,/search 始终能找到它。
压缩也会自动触发,阈值不是常量:CLI 公布其使用的模型的窗口(claude-opus-5[1m] 为 contextWindow: 1000000),Thot 在该窗口的 70% 处压缩——这里是 700,000 个令牌,在 200k 窗口上是 140,000。触发器读取 CLI 报告的实际大小,而不是基于消息的估算:在账户模式下,线程属于 CLI,Thot 既看不到读取的文件,也看不到工具流量。在一个普通轮次上实测,估算给出 95 个令牌,而窗口中实际有 88,290 个。
目标——知道何时停止
一个目标在它所穿越的对话之后仍然存在,并在每一轮提醒模型,包括在 /compact 之后。
› /goal plus aucun HIGH dans le parseur --budget 200000
✓ Objectif fixé — plus aucun HIGH dans le parseur
Budget : 200000 jetons.耗尽预算是一种状态,而不是错误:Thot 不会在轮次中途停止,它会完成,进入 budget_limited 并说明目标的进展。由你选择 /goal budget 500000 还是 /goal done。
记忆——只做一次决定
审计中昂贵的不是找到候选者:确定性阶段在几秒内完成,免费。昂贵的是决定它们的价值。在两次运行之间丢失这些决定,正是让安全工具变得不可忍受的原因——同样的四十条拒绝,每周一次,直到没人再读报告。
› /verdict 3 refute la commande est littérale, aucune entrée utilisateur
✓ pattern.os_system_injection à app/shellutil.py:5 — refuted
Retenu tant que ce code ne change pas.决定 | 效果 |
| 误报——转为 INFO,从报告中移除,保留理由 |
| 真实风险,已接受——转为 INFO,带注释 |
| 已修复——如果它再次出现,会作为回归被标记 |
两种深度,明确说明
Python | TypeScript · JavaScript | 其他 | |
符号、调用图、 | 是 | 是 | 否 |
函数体内的着色 | 是 | 是 | 否 |
指向同一文件中的辅助函数的着色 | 是 | 是 | 否 |
跨文件的着色 | 是 | 否 | 否 |
基于模式的规则 | 是 | 是 | 是 |
JavaScript 的着色会跟随一次调用到达同一文件中定义的函数——委托的 handler 的常见形式——并止步于此,这正是它所说的。接下来的两个层级依赖于已解析的调用图——知道这里调用的 readInput 就是那里定义的那个。Python 的导入系统能回答这个问题;JavaScript 的不能,除非有模块解析器、tsconfig 和类型器对 this 的视角。建立在假设之上的第二层级,会把一个报告已证明路径的工具,变成一个报告貌似合理路径的工具。
引擎扫描的是文件,而不是命名函数体。Web handler 的常见形式是传递给路由的匿名箭头函数——app.get("/x", (req, res) => { … })——没有任何索引器会为其命名。
如此测量:一个参数列表带括号且跟在逗号或左括号之后的箭头函数,即 [(,]\s*(?:async\s*)?\([^)]*\)\s*=>,作用于 detect_scope 所保留文件的掩码源码上——Prime 上有 15 094 个,Hermes 上有 19 625 个,对于跟随符号的引擎来说全部不可见。早期版本宣称 24 454 个而未说明计数方式;数字完全取决于定义,因此定义被写了下来。
在两个语料库上测量:Prime 上有 31 条路径,Hermes 上有 41 条,覆盖 3 552 个 JS/TS 文件。七十二个,不是三千个——这是色调引擎的形态,而不是模式扫描器的形态。
引擎也跟随那些没人按名字调用的函数:运行时调用它们,并把值传进去。addEventListener 引入色调——参数就是输入;.then、.map、.forEach 携带色调——参数恰好在被遍历对象带色调时被染色,因此常量列表仍然是常量列表。这就是跟随一个值与凭空发明一个值之间的区别,没有这个区别,浏览器代码树几乎完全不可见。
obj[键] = 值 其中键受控是一个特殊的 sink:有效载荷是键而不是值,因为一个 __proto__ 会写入程序的所有对象。Hermes 中有十一个真实站点,Prime 中有三个,全部形如 for (const [k, v] of Object.entries(x)) { out[k] = v }。一个按名字拒绝 __proto__ 的循环已被修复,并且不会被报告。
报告自己说明了这一点,而不是让人以为覆盖是均匀的:
teinte au fichier près, pas au-delà : javascript 3 · typescript 912
Une exception, et une seule : un import **relatif** se résout par une règle de
fichiers, pas par une inférence. `./helpers` depuis `src/app.ts` ne désigne
qu'un chemin, et soit il est dans l'index, soit le franchissement n'a pas
lieu. Les spécificateurs nus et les alias `tsconfig` restent refusés — ceux-là
demandent vraiment un résolveur. Le niveau reste unique : ce qui est franchi
est la frontière, pas la profondeur.
Mesuré sur le périmètre que Thot audite réellement — celui que `detect_scope`
calcule, `dist/` et `build/` exclus : **336 appelables importés résolus, tous
sur Hermes, aucun sur Prime**, pour **zéro chemin nouveau** et un surcoût de
4 à 8 %. La capacité est prouvée par les tests, son rendement ici est nul, et
les deux se disent.
Une première version de ce paragraphe annonçait 1 514 appelables et +21 %.
Ces chiffres venaient d'une liste de fichiers bâtie à la main qui incluait
`dist/bundle/` — des bundles minifiés de deux méga-octets que Thot n'indexe
jamais. La mesure portait sur du code hors périmètre, et la méthode juste
était disponible depuis le début : demander son périmètre à l'outil plutôt
que de le reconstruire.TypeScript 索引器是一个扫描器,不是 tsc:它掩码注释和字面量,然后通过花括号匹配读取声明。改用 tsc 会使映射依赖于已安装的 node 链,且要求可解析且版本正确——一张只在某些机器上工作的映射,不如一张边界被写明的映射。测量结果:Prime 上 8 568 个符号,耗时 1.7 秒,Hermes 上再多 11 138 个。
一个文件是为了什么
严重性 = 影响 × 可达性 × 置信度,可达性来自调用图。调用图回答"入口点能否到达这里"。它对于一个根本不是攻击面的文件无话可说。
在随 Thot 交付的两个程序上测量:Hermes 的 25 个 HIGH findings 中有 12 个,Prime 的 11 个中有 6 个位于测试或示例代码中。报告高优先级部分近一半涉及攻击者永远无法到达的代码——这就是报告不再被阅读的方式。
之前 | 之后 | |
hermes | 25 high · 94 medium · 297 low | 13 high · 58 medium · 345 low |
prime | 11 high · 2 medium · 9 low | 5 high · 8 medium · 9 low |
HIGH 列是承载论证的列,自首次测量以来它们一个 finding 都没变过:25 → 13 和 11 → 5。上面的 medium 和 low 计数是在当前树状结构上重新计算的,因为两次测量之间 Hermes 中有九个漏洞已被修复。
没有添加任何 finding,也没有丢失任何 finding。这是降级,绝不是删除:测试代码运行在开发者的机器和 CI 中,这正是供应链攻击的形态。finding 保留,并携带其来源角色。
分类是保守的——整段分类,绝不用子串(latest/ 不是测试目录,contest.py 不是测试文件),所有未被识别的都算生产代码。误判为"测试"会隐藏真正的缺陷;误判为"生产"只损失一个等级。
价值从何而来
一个 finding 携带启动其路径的来源规则——source.argv、source.http、source.js.event——而不仅仅是它所在的行。报告明确说明("来自命令行的值……"),JSON 也以键 source_rule 给出,以便下游过滤读取事实而不是读一句法语。
这是排序中缺失的一半。命令行工具中的 open(args.sortie, "w") 是操作者在命名文件:提供 argv 的人已经拥有该进程的文件系统,这个调用没有给他任何东西。handler 中的 open(request.args["f"]) 是任意文件读取。同一条规则,同一个 sink,两个世界。
本地来源 | 远程来源 | |
| 降一级 | 满级 |
其他所有 | 满级 | 满级 |
只有这三个。从 argv 构建的命令仍然是命令,从环境变量读取的 pickle 仍然执行代码:在那里,是 sink 造成升级,而不是路径。
在引擎学会跟随属性链的那天于 Hermes 上测量:没有这个区分,仅 sink.fs.read 一项就会在报告中产生 48 个 findings,其中九个来自单个 CI 脚本,每个都是一个打开被要求打开的文件的工具。有了它,它们降到阈值以下,保持一次按键的距离(--all)。
未知来源按本地计,这是写明的而不是隐藏的:假设相反会把引擎无法命名来源的所有路径重新推回报告顶部。当本地变量与其派生参数之间的绑定不再丢失时,引擎能命名的数量翻了一倍——cible = chemin.strip() 保持与 chemin 的链接,没有它,sink 就无法关联到任何参数,因此也无法关联到任何调用者。
为什么这是安全的
一个判定以 Finding.compute_id 为索引,它哈希规则、文件、符号以及该符号的规范化 AST。重新格式化、移动函数、重命名局部变量:判定保持不变。改变代码做的事:标识符随之改变,判定自动过期。
因此,一个驳回永远不会比它所针对的代码活得更久。这是使记住驳回变得可接受的唯一属性。
标识符还命名被针对的确切调用——httpx.get#3——而不仅仅是包含它的函数。没有这个,同一函数中的五个网络调用在记忆看来只是一个 finding,排除第一个就会连同其理由一起排除另外四个。这个区分符不会削弱任何东西:它只需要在函数体的一个版本内唯一,而该函数体的 AST 一旦变动,就会使与之相关的所有内容过期。
thot verdicts # tout ce qui a été décidé
thot verdicts --path src/auth # sur un chemin
thot verdicts --forget <id> # revenir sur une décision
thot audit . --no-memory # ignorer la mémoire pour ce run一个决定比产生它的 finding 活得更久:代码改变,finding 获得新身份,旧决定不再指向任何东西。列表将这些标记为 [absent du dernier audit],而不是像其他决定一样显示——六个决定中有三个已死,不应被读作六个活着的决定。
记忆在模型之前应用:一个已携带决定——已排除、已接受或已修复——的 finding 永远不会被送回分析。所有都已决定的运行不产生任何调用。这不仅是节省:探针一次性替换置信度、严重性、场景和来源,因此把决定送回模型会覆盖它,并抹去做出决定的人。回归是最重要的情形:它已经被判定为真实一次,任何深度扫描都无法让它沉默。
而反驳会自动记录,从 thot audit --deep 和 /audit deep 都一样:两次模型调用,只付一次费。它们携带做出决定的引擎的名字,绝不是你的名字——机器决定不会凌驾于人类决定之上。
没有任何东西被静默删除。一个被排除的 finding 以 refuted 状态留在报告中,附有理由和作者——一个隐藏被告知要忽略之物的审计是不可重读的。
Python 内核
Prime Agent 的核心思想,被移植过来:模型不是每个问题一次工具调用,而是编写 Python,其变量得以存活。
› /py bas = audit(severity="low"); print(len(bas), "findings"); [f.rule for f in bas]
3 findings
→ ['sink.eval', 'sink.network', 'sink.subprocess.shell']
› /py len(files())
→ 148仓库映射以对象形式可用——files()、symbols()、find()、callers()、callees()、audit()、read()。一个交叉 findings 与调用者的循环花费一轮模型;同样的操作以工具调用方式需要大约十二次,每次都要重新支付读取映射已知信息的代价。
内核绝不在 Thot 的进程中运行。 在自己的进程里 exec() 会把 Thot 的记忆、打开的数据库和文件描述符交给被审计的代码。因此它是一个子进程——并且在配置了沙箱时位于容器内。
它确切保护什么——Thot 在自身对抗性扫描指出一个过于绝对的 docstring 后,对自己进行了修正:
子进程( | 保护 Thot 的记忆、数据库和文件描述符。不保护你的凭据:worker 以你的账户运行,可以读取 |
容器( | 真正的边界:无网络,无你的 |
敏感环境变量在启动前被移除,/py 在本地模式下说明一次,而不是让"独立进程"被读作它并不提供的保证。
rlm() — 从单元格委派
verdicts = {f.id: rlm(f"Ce chemin est-il exploitable ?\n{f.failure_scenario}")
for f in audit(severity="high")}一个单元格可以分解自己的问题。单元格不持有任何凭据:它请求宿主,宿主决定并付费。因此限制由宿主侧执行——每单元格 8 次调用,每内核 40 次——因为子进程可以修改的限制不是限制,而子进程执行的是来自被审计仓库的代码。
Thot 从仓库记住什么
› /harness note team.shell.run : échappe ses arguments, les findings dessus sont faux
✓ Retenu — rappelé à chaque session.Prime 的提炼,应用于审计:任何静态分析都永远无法推导出的事实。它们存在于 <dépôt>/.thot/harness.json 中,像判定一样在拉取请求中重新读取,并在每次会话中回到简报中。
模型有权做什么
thot --tools lecture # lire et raisonner, jamais modifier
thot --tools carte # la carte seule : aucun fichier ouvert会话中:/tools lecture。重新阅读一个不属于你的仓库,就是阅读你有充分理由警惕的代码——而让模型修改它很少是你想要的。
该姿态由三处支撑,而不是一处:提供给模型的工具、它仍然调用其中一个的时刻,以及——在账户模式下——官方 CLI,--disallowed-tools 对其禁止 Write、Edit 和 Bash。一个只过滤 Thot 工具的姿态,在最关键的地方就是谎言。
供应链
thot deps # les dépendances épinglées, contre OSV.dev
thot deps --list # ce qui a été trouvé, sans réseau
thot deps --fail-on high # code 1 en CI
thot audit . --deps # dans le rapport d'audit
thot mcp check # tes serveurs MCP sont-ils malveillants ?锁文件优先,始终如此:uv.lock、poetry.lock、Pipfile.lock、package-lock.json、yarn.lock、pnpm-lock.yaml。清单说 requests>=2,OSV 无法回答区间;锁文件说 2.31.0,OSV 就能回答。仅以区间形式存在的依赖不会被猜测,而是被报告为未锁定。
覆盖确切版本的评估是事实,而非猜测——但你的代码是否到达易受攻击的函数并未被分析,因此这些发现保持 PLAUSIBLE 并明确说明这一点。只有 MAL-* 例外:包就是载荷本身,可达性不是问题。
而与其他地方相同的属性:发现的身份携带固定的版本,因此一次 bump 会使裁决过期。对 requests==2.19.1 排除某个 CVE,并不会排除 2.20.0 上的任何内容。
OSV 不可达永远不会变成健康证明:thot deps 会显示「未验证」并返回错误代码。
执行被审计的代码,而不在你自己的机器上执行
在审计的仓库上运行 pytest,就是该仓库的代码在你的账户下运行。这是整个设计唯一会泄露的地方。
thot sandbox status
thot sandbox use docker
thot sandbox show pytest -q # la commande docker exacte, à relire默认情况下,在容器中:
网络 | 切断( |
仓库 | 只读挂载,可写副本位于 tmpfs |
权限 |
|
限制 |
|
切断网络是最有价值也是最具干扰性的标志:这就是为什么它是一个标志而不是一条法律(--network)。
一条规则与 Thot 其他部分相反:在其他任何地方,缺少依赖只会损失相应功能,工作继续。而在这里,请求了沙箱但不可用时会拒绝执行。静默回退到宿主机,会把保护变成谎言。
共享决策
裁决是关于该代码的这一修订版本的事实。因此它与代码同行:<dépôt>/.thot/verdicts.json,在涉及相关代码的 pull request 中被重新审阅,并且在网络连接之前就已存在于全新克隆中。
thot verdicts --share <id> # publier une décision locale dans le dépôt
thot verdicts --share-all # toutes celles qui concernent ce dépôt
thot verdicts --where # d'où viennent les décisions, où elles s'écrivent记忆在各树之间共享,文件则不是:发布涉及另一个仓库的决策会被拒绝,而 --share-all 只接受其文件存在于本地的决策。
默认链,无需任何配置:仓库优先,你的机器其次。经过审阅的决策优先于你给自己留下的笔记。
写入则保持本地。一个工具如果每次 /verdict 都修改版本化文件,会产生没人要求的 diff:你在本地做决定,你有意地发布。
共享服务器,或现有的 mem0
// ~/.thot/memory.json
{"remote": {"kind": "http", "base_url": "https://audit.equipe.example", "token": "…"}}
{"remote": {"kind": "mem0", "host": "http://localhost:8888", "api_key": "…"}}mem0 后端与 Hermes Agent 的客户端完全一样地使用自托管协议:一个已为 Hermes 部署的服务器无需任何改动即可服务 Thot。
远程存储不可达只会损失过往决策的记忆,绝不会损失审计——但它不会沉默地这样做:thot verdicts --where 会说明哪个存储静默以及原因。
在其他地方接收审计结果
在凌晨 03:00 结束的审计,在没有人被通知之前毫无价值,而需要被通知的人并不在终端前。
thot gateway add ntfy topic=thot-$(openssl rand -hex 8) # le sujet EST le secret
thot gateway add telegram token=… chat_id=…
thot gateway allow telegram <ton-id> # obligatoire pour commander
thot gateway test
thot serve # écouter les commandes渠道 | 出站 | 入站 |
Telegram | ✓ | ✓(长轮询——无需开放任何端口) |
Discord · Slack | ✓(webhook) | — |
ntfy | ✓ | —(无身份:主题足以发布) |
电子邮件 | ✓(SMTP) | — |
通知不需要任何守护进程:gateway-notify 插件在 post_audit 时触发,并且仅针对非监控的审计。手动启动的审计已经显示在屏幕上;每次都通知会让接收者学会关闭该渠道,从而损失唯一重要的那条消息。没有新内容:保持沉默。
被盗令牌能做什么
守护进程只为了回程而存在,其设计主要围绕这一点:
命令集是封闭的——
status、audit、findings、verdict、help。没有 shell,没有写入,没有任意路径;审计只能针对已由
thot schedule add声明的仓库;入站要求允许列表。Hermes 为开发提供
ALLOW_ALL_USERS;Thot 没有对应物。没有列表,渠道仅限出站,thot serve会明确说明。
~/.thot/gateway.json 以 0600 权限写入——它包含机器人令牌和 SMTP 密码。环境变量按字段覆盖它,使用 Hermes 的命名。
定时审计
thot schedule add nuit ~/mon-projet --every daily --threshold high
thot schedule list
thot schedule run nuit # ce que le planificateur appelle
thot schedule remove nuitThot 写入 launchd 单元(macOS)或给你 crontab 行,然后让你自己激活——一个静默安装后台任务的工具,是一个不再被信任的工具。
当 launchd 做不到时。 在 macOS 上,权限按二进制文件授予:launchd 代理可能被拒绝访问 ~/Desktop、~/Documents 或 ~/Downloads,此时单元会在解释器启动时卡住而不写入一行。从你的会话启动的进程保留该会话的访问权限,即使成为孤儿也是如此——这是第三种补救措施,它不需要向系统请求任何东西:
thot schedule start # un planificateur dans ta session
thot schedule status # tourne-t-il, et quand est-il passé
thot schedule stop
thot schedule autostart # le relever au premier terminal après un redémarrage它让位于 launchctl 声明已实际执行的任何 launchd 单元:两个调度器管理同一个任务,就是双倍的工作和双倍的令牌。thot doctor 会说明哪一个在服务。
定时审计在没有新内容时什么都不说。 一份重复相同三百条发现的夜间报告最终会进入一个没人打开的文件夹。上报的是 diff:自上次以来新出现的、超过阈值的、减去已被判定为无意义的。
插件
五个钩子,每个都有已交付的功能在使用:
Hook | 时机 |
| 报告之前,用于注释 |
| 审计完成——通知、导出、归档 |
| 代理写入之前——返回警告 |
| 写入成功之后 |
| 决策刚刚被记录 |
插件是一个包含 plugin.yaml 和 __init__.py 的文件夹,位于 ~/.thot/plugins/ 或 <repo>/.thot/plugins/——即 Hermes Agent 使用的形式。崩溃的插件只损失自身功能,不影响其他任何东西:其错误被记录并通过 /plugins 显示。
被审计仓库中的插件未经你的同意不会被执行。 加载插件就是在这里、在你的账户下运行其代码——而被审计的仓库恰恰是 Thot 最不信任的。因此其插件只被命名,从不被导入,直到你批准它们:
thot plugins list <dépôt> # chargés, et refusés avec la raison
thot plugins trust <dépôt>/.thot/plugins/x # après l'avoir lu
thot plugins untrust <dépôt>/.thot/plugins/x批准针对的是内容,而非名称:Thot 记录文件夹的指纹,任何修改都会撤销批准并明确说明。
三个已交付的插件:
插件 | 功能 |
| 重新阅读模型写入的内容,如果出现危险模式则发出警告。非阻塞——阻塞会话的误报比写入本身更糟。 |
| 标记为 |
| 每次审计、裁决和写入的本地 JSONL 日志,位于 |
验证一切就绪
「它能工作」是一个断言,而对于由三个程序组成的程序,这不是一个可以轻信的断言——尤其是当它来自工具本身时。
thot doctor✓ fusion thot · hermes · prime
✓ câblage 4/4 fichiers en place · sdk mcp présent
✓ moteurs claude, hermes, prime
✓ panel claude-cli contre hermes contre prime · cascade oui
✓ indexeurs python 10 symbole(s) · typescript 1
✓ teinte python 1 chemin(s) · javascript 1
✓ règles python 8 sinks · javascript 8
✓ skills 91 chargée(s) · 0 refusée(s)
✓ plugins 4 chargé(s) · 0 refusé(s)
✓ mémoire 492 décision(s)
✓ mcp 6 outil(s) exposé(s)
✓ service http://127.0.0.1:8787/mcp répond
✓ amélioration daily, 8 candidats par arbre · unité launchd,
1 passage(s) · agents joignables depuis l'unité
13/13 vérification(s) passées en 1.83 s带日期的快照,而非契约:mémoire 统计累积的裁决且只会增长,skills 取决于机器上安装的内容。稳定的是形式——每行携带一个测量到的数字,以及 12/12。
最后一行是开发机器上的实际输出,并且原样保留:这正是检查所要产出的东西。1 passage(s) 来自 launchctl 本身,它不是装饰性的:launchctl list 显示单元已加载,其 LastExitStatus 为 0,其日志不存在——三个信号表明「一切正常」,对于一个从未启动过的任务。说出原因比数绿色行数更好。
这个检查本身也曾需要修正。它曾根据路径的形式来判定任务有罪——「树在 ~/Desktop 下,所以 launchd 无法读取它」——而 macOS 的权限是按二进制文件授予的:单元的解析器读取了 /bin/sh 被拒绝访问的树。形式是怀疑,通过是事实,而它现在询问的正是事实。
还有一个任何静态检查都无法完成的验证:
thot doctor --agents # un appel modèle par agent installé✓ lecture · claude lit un fichier par chemin absolu
✓ écriture · claude n'a pas écrit cette fois
✓ outils · claude 10 outil(s), tous en lecture seule
✓ lecture · hermes lit un fichier par chemin absolu
✓ écriture · hermes peut écrire — aucun mode lecture seule
(`-t file` et `--safe-mode` ne restreignent pas les permissions)
✓ outils · hermes mcp__patch, mcp__read_file, mcp__search_files, mcp__write_file
✓ lecture · prime lit un fichier par chemin absolu
✓ écriture · prime peut écrire — outil unique : un noyau IPython
✓ outils · prime ipython写入行是绿色的,尽管它们宣告了一种令人不安的能力:它们报告的是现状,而非期望。三个代理中有两个可以写入,没有任何标志能阻止——通过要求它们创建文件然后查看磁盘来测量。无法阻止的事情被变得不可能被忽视:AuditResult.touched 指明一次运行修改了什么,夜间循环在 stderr 上大声宣告。
它在临时文件夹中放置一个文件并请求其内容。它存在是因为一个真实的缺陷:Hermes 不打开相对于其工作目录的路径,而是回复一句读起来像拒绝的话。三分之一的测试面板无法验证任何依赖第二个文件的断言,而除了放置一个文件之外,没有任何其他方法能揭示这一点。
每行执行一个真实操作并报告它测量到的内容:不是「skills:已配置」而是「91 已加载,0 已拒绝」。着色引擎在两种语言的样本中寻找路径,MCP 服务器响应自己的协议。无法运行的检查会失败而不是静默通过:表示「未测试」的绿色行比红色行更糟。不接触网络或模型——在飞机上运行 thot doctor 与在办公室给出相同的答案。失败时非零退出,以便用于 && 或 CI。
持续改进
一次审计论证了二十个候选然后停止,会让其余部分永远得不到判断。没有预算限制的一次运行在你回来坐下时仍在运行。thot improve 是中间地带:有界的轮次,每轮写入磁盘,每轮从上一轮停止的地方继续。
thot improve # un tour sur les trois arbres
thot improve --rounds 5 # jusqu'à ce qu'un tour ne juge plus rien
thot improve --every daily # la boucle devient permanente写入的单元携带自己的 PATH。launchd 给任务 /usr/bin:/bin:/usr/sbin:/sbin,cron 更少,而 claude、hermes 和 node 都不在这些文件夹中——它们位于 ~/.local/bin 下。没有这个,夜间运行不构建任何引擎,不判断任何东西,并且以代码 0 退出:launchd 每晚记录一次成功,无限期。这种静默失败的任务与正常运行的任务无法区分,因此失去代理的深度运行现在以错误退出并明确说明。
夜间版本报告的是它决定的内容,而不是出现的内容。这个区别很重要:计划审计的报告机制回答的是“阈值之上有什么新东西”,这对扫描来说是正确的问题,对判断来说却是错误的问题。确认一个已经出现在报告中的 MEDIUM 正是循环的用途所在——而它不会报告给任何人。审计可能修改过的文件会在同一日志中标记出来。
一棵已经没有东西可判断的树把它的份额传给下一棵。在真实语料上测量:thot 的积压为空,prime 只有一个候选,因此每棵树 20 的预算会在无法使用它们的树上浪费 40,而 Hermes 在等待一百五十个。一轮 20 变成一轮 20、20 和 60。
第三个特性让它快速收敛:失败会被计数。一个候选,如果其代理超时,或模型拒绝表态,会保留其严重性——因此它会在下一轮优先被重新处理,再下一轮也一样。在一个 1,660 行的文件中的一个 finding 上测量:四次尝试三次通过,其中三次撞上同一堵墙。两次失败后,它排到队尾:仍然有资格,但永远不优先。一次成功会清除计数——一堵只是下午繁忙或订阅耗尽的墙,不应该永远跟着一个 finding。
两个属性让它收敛而不是原地打转:一次反驳会被记住,因此下一次选择会跳过它;一次确认则故意不被记住——真正的缺陷必须持续出现,直到有人修复它——因此循环携带自己的一套已判定标识符。没有这一点,第一轮之后的每一轮都会把全部预算花在重新论证第一轮刚刚确认的内容上。
它最终以该做的事情结束,在总计之前:
À REGARDER — 2 finding(s) :
[hermes] plugins/platforms/a2a/tools.py:83 — confirmé · prime
L'URL vient d'un argument d'outil, donc du modèle…
[prime] packages/coding-agent/…/state-snapshot.ts:163 — réfutation contestée · hermes
Le chemin dit fixe est construit depuis un identifiant non validé…
4 tour(s) · 83 jugement(s) (80 réfuté · 1 confirmé) · 157 candidat(s) sans décision一次反驳是内务管理;一次确认是新闻。一次被质疑的反驳也是:这是程序在埋葬某样东西之前说自己已经纠正了。只计数而不点名,会让读者去 grep 日志——而这正是每一天里每次都在发生的事情。
它从不修改代码。这里的“改进”意味着程序对自身的判断变得更清晰、更便宜:更少的无决定候选,更多的决定落盘,每一个都可追溯到做出决定的代理。
温度计,以及使用它的循环
以上所有内容都是用 Thot 测量 Thot。improve 询问模型一个 finding 是否真实。evolve 监视 provenance,这是引擎对自己的输出计算的一份报告。两者都是循环的,而这个循环绝非学术问题:深度扫描为 9 个确认支付了 638 个判断,而一条标记为 −100 % 的规则在沉睡——xml_unsafe_parse 标记了 defusedxml,也就是它自己的消息所推荐的正是解药。程序内部没有任何东西能看到这一点。
thot bench 打破了这个循环。它用别人标记为易受攻击或健康的代码来测量 Thot,各占一半,并指明弱点类别。
thot bench ~/.thot/bench # les suites présentes, catégorie par catégorie
thot bench ~/.thot/bench --json # ce que la boucle d'évolution lit
thot bench ~/.thot/bench --floor info # ce que le plancher de sévérité cache语料不内置——一万八千个第三方文件不该出现在这个仓库里——因此路径总是给定的,每个套件都会对照其清单的指纹进行验证。一个标签在测量过程中发生变动的语料比没有语料更糟:之后的所有数字都是错的,而且没有任何东西会指出这一点。
分数是约登 J 指数,TPR − FPR。零是抛硬币,+100 是完美,而负值意味着规则是反的。精确率和召回率不会告诉你这一点:一条没有任何真阳性的规则,精确率未定义,显示为空,读起来像没有数据——这正是反向规则存活的方式。J 指数没有这个漏洞。
两种作弊方式,两种都会输。发现得更少——这曾让 provenance 上升——会使 TPR 下降。什么都报告则 TPR 100 %、FPR 100 %、J 为零:语料平衡为 50/50 正是为了这个。
测量状态、默认下限、三个框架(django、fastapi、flask):
avant après
TPR 9.9 % 34.4 %
FPR 0.5 % 0.0 %
J de Youden +9.4 % +34.4 %
catégories actives 10 24
catégories négatives 0 0“之前”是温度计首次存在时程序的状态。“之后”是同一语料、同一下限、同一命令。留出集证实了这一点:被隔离的 flask 得分 +33,3 %,被隔离的 django 得分 +35,9 %——与训练相差不到两个百分点,因此规则在它们未曾参与编写的代码上有效。
按类别划分,引擎今天能做什么:
xxe · tlsverify · weakhash · weakrand · weakcipher · weakkeylength
hardcodedcreds · default_credentials · cleartexttransmit · errormessage
debug_code_production · cookie_no_httponly · cookie_no_samesite
securecookie · directory_listing_exposure +100,0 %
deserial +98,0 % cmdi +93,3 % codeinj +86,0 %
eval_injection +84,0 % sqli +82,7 % cloud_ssrf_metadata +78,7 %
ssrf +64,7 % pathtraver +56,0 % xss / basic_xss +26,7 %在全部 18 300 个案例上零误报,且没有负值类别。起点是 ssrf 为 −8,0 %,xxe 为 −100 %。
产生这二十五点的内容,按测量顺序排列:
变更 | J |
起点 | +9,4 % |
Web 路由被识别为入口点 | +9,6 % |
色调跟随容器中的值 | +10,0 % |
分支恒定的三元表达式不携带任何内容 | +12,4 % |
带否定类的 | 包含 |
SSRF 防护:主机白名单、解析的 IP 范围 | +14,3 % |
| +14,9 % |
路径限制和命名白名单 | +15,5 % |
| +16,3 % |
十二条单行模式规则 | +27,8 % |
模式不再支付可访问性折扣 | +34,4 % |
其中三项变更在测量后被拒绝,而正是温度计拒绝了它们:将数据库读取视为不可信来源(436 个易受攻击案例,343 个健康案例——噪声和信号一样多)、clickjacking(此仓库上 23 个误报),以及五条 LDAP/XPath/NoSQL/SSTI/EL 注入规则,其测量的 J 恰好是 0,000:它们在健康的一半和另一半上拉动的量相同。
精确率买不到盲区
上面识别的每个防护最初都是在一个既提高分数又打开可利用漏洞的版本中提出的。一个对抗性探针找到了所有漏洞:
resolved = socket.gethostbyname(parsed.hostname or url)
if ipaddress.ip_address(resolved).is_private:
return "blocked", 403
os.system("curl -s " + url) # ← silencieux, et exploitable该防护是一个正确的 SSRF 防御,对字符串中仍然存在的 shell 元字符只字不提。到处清除色调换来了 174 个更少的误报和一个命令注入盲区。
因此引擎携带这个区别:一个约束值的防护(字面白名单、枚举的 fullmatch)为所有内容将其洗白;一个证明目的地的防护(主机被允许、解析的地址是公网的、路径被限制)只对相关的 sink 家族有效。bleach.clean 也是如此,它中和 HTML 而让 x; rm -rf / 完好无损。
四个对抗性探针在测试套件中,全部正确报告。任何未来会重新打开这些漏洞之一的“改进”都会破坏一个命名测试。
剩余的沉默有两个原因,thot bench 将它们分开,因为这是两项不同的工作:
règle muette — elle existe et ne matche jamais : elle a un motif à élargir
aucune règle pour la classe : il y en a une à écrire这个区别决定了目标的顺序——没有它,沉默的类别在 J = 0 处完全并列,排序就落回字母顺序。
融合,在它有所改变的地方
Cascade.turn 选择一个代理并调用它;只有当第一个返回错误时才会去找另一个。这样的一轮在构造上就被限制在两者中较好的那个:它可以少输,永远不会多赢。agent_apply 也是这样做的——一个引擎,单数。
thot evolve --fused 让两者工作,处理同一问题的不同部分:
thot bench ~/.thot/bench # où ça fait mal
thot evolve --from-bench --fused \
--corpus ~/.thot/bench --hold-out flask # et on répare, en boucleHermes 读取测量并编写规范。 它不触碰任何文件。它的输出是对原因的断言:哪条规则、哪一行、为什么是这些案例。
Prime 读取规范并编写代码。 它被明确告知,如果代码与规范矛盾,它可以拒绝——一个不能说“不”的执行者只是一个中继,而中继不增加任何价值。
两者都不做决定。 测试套件是下限,标记语料是裁决。一个错误的规范被坚定地应用会使分数下降,并被逐字节撤销。
顺序也不是任意的。先设计后构建在接合处可验证:Prime 在承诺之前看到 Hermes 的推理。先构建后重读则不允许——当第二个看的时候,第一个已经决定了。
目标来自测量,而不是打出来的一句话。到目前为止,循环只能追求人类已经怀疑的东西;从分数构建的目标是程序说出它哪里弱,用不是它选择的数字——然后同样的数字说明答案是否有用。每个目标携带失败的文件:一个被告知“xss 是 0 %”的代理只能猜测,一个被给予三个失败文件的代理有一个要解决的问题。
过拟合,以及 --hold-out 真正能做什么
一个在语料上打分的循环只有一种真正的作弊方式:学习语料。一条针对 BenchmarkTest01126 长什么样而调整的规则会提高分数,但对任何人都没有用,而且从外部看,它与真正的进步无法区分。
--hold-out flask 将一个套件从主数字中取出,并保留它作为第二道防线:一个改变了它被优化所针对的套件、却没有改变它从未见过的那个套件的变更,已经说明了它是什么。两个数字都保持 不下降。
它的局限,已测量:三个框架彼此相差半个百分点。这能抓住针对文件的过拟合,但抓不住针对基准测试形式的过拟合——生成的语料仍然是生成的语料,一条只对演示代码有帮助的规则会通过全部三个。留出集让一种作弊可见;它不会让语料具有代表性。
循环在一轮又一轮之间保留的内容写在 ~/.thot/evolve-log.jsonl 中。没有它,测量在一轮中几乎不动,下一轮会重读同样的最差类别,递上同样的文件,并——合理地——收到同样的已经构建、测量和撤销的规范:--rounds 5 将是一次尝试被尝试五次,贵五倍,还显得很忙。
这不是一个神谕。一个修复可以是绿色的,提高 J,但仍然很糟糕——这是过拟合,自动程序修复的文献只谈这个。语料是我们在它上面取得进展的证据。循环报告它改变了什么,以便人类可以不同意。
Skills——Thot 知道的方法
一个 skill 是一次写好的方法:一个带 YAML frontmatter 的 SKILL.md。这是 Hermes Agent 和 Prime Agent 的格式,因此为两者之一编写的 skill 在这里无需修改即可加载,反之亦然。
Thot 内置 Hermes Agent 的完整库(MIT——见 NOTICE.md):90 个已加载方法,还有 117 个可用。
thot skills list # les 91 chargées
thot skills search pentest # y compris la bibliothèque optionnelle
thot skills install ast-grep # activer une optionnelle
thot skills show plan # ce que lirait le modèle已加载类别:audit、security、software-development、github、devops、research、mlops、productivity、creative、apple、email、media、note-taking、smart-home、social-media、autonomous-ai-agents。
模型通过 skills 工具发现它们——只要不提供关键词,它就返回一个名称索引,因为两百条描述算不上目录——然后用 skill 读取适用的那一条。在会话中,/skills 向你展示同样的内容。
一个导入的方法如果引用了此处不存在的工具(delegate_task、browser_navigate……),会原样提供,并附注说明缺少哪些以及该用什么替代。即使工具调用无法迁移,这套流程本身也能迁移。
添加方法
~/.thot/skills/<nom>/SKILL.md # partout où tu travailles
<repo>/.thot/skills/<nom>/SKILL.md # versionné avec ce dépôt---
name: ma-méthode
description: Ce qu'elle fait et quand s'en servir.
---
# Ma méthode
Les étapes, dans l'ordre.两种布局都被接受:扁平目录(Prime Agent)或按类别分组(Hermes Agent)。已存在的名称会替换内置版本——这样可以在不 fork 的情况下调整一个随附的方法。
被审计仓库提供的方法首先经过分析
SKILL.md 是作为指令交给模型的文本。Thot 读取的仓库,按定义就是无人应答的那些。一个恶意仓库放入 .thot/skills/x/SKILL.md,就能写入简报的一部分。
Hermes Agent 的防护被移植到这里,并作用于所有来自仓库的内容:注入、外泄、持久化、混淆。
▲ 1 skill(s) fourni(s) par ce dépôt ont été refusés — ils seraient passés
au modèle comme instructions.
pwn curl vers l'extérieur ; accès à ~/.thot ; « ignore previous
instructions »thot skills scan <目录> 可按需提出同样的问题。Thot 自身交付的内容不经过分析:它存在于磁盘上是因为程序已安装,而不是因为某个仓库要求了它。
自定义命令
一个 markdown 文件就是一条命令。语法与 Prime Agent、Claude Code 和 Codex 相同——无需学习任何新东西。
---
description: Relire un fichier sans rien modifier.
argument-hint: <chemin>
---
Relis $1 et dis-moi ce qui cloche. Ne modifie rien.在 .thot/commands/revue.md 中,这会创建 /revue src/app.py。替换规则:$1、$2……、$@、$ARGUMENTS、${@:2}、${@:2:3}。参数永远不会被重新解释。仓库中的命令与其 skills 一样经过同一道防护。
内置三条命令:/triage(命名入口或归类为不处理)、/harden(先失败的测试,再修复)、/regress(审计差异对照 git 参考)。
MCP 服务器
Hermes Agent 的目录,二十个经过验证的服务器:
thot mcp list # le catalogue, et ce qui est déjà connecté
thot mcp show sentry
thot mcp add linear安装委托给官方 CLI,它已经具备 OAuth 和令牌续期能力——Thot 没有任何理由再持有一个可能泄露的第二保险库。它明确说明已注册不等于已授权,并指出哪条命令能完成这项工作。
模型的工具
经典工具——读取、写入、编辑、运行命令。所有写入和执行都需要确认,且此设置不可配置。
还有四个仅属于 Thot 的工具,免费,因为它们查询的是代码图谱而非模型:
工具 | 响应 |
| 项目的文件 |
| 函数的文件、行号和参数 |
| 谁调用了什么,以及到入口点的距离 |
| 污点源 → 汇聚点的路径 |
在账户模式下,这四个工具通过 thot.mcp_server 提供给官方 CLI——一个只读的 MCP 服务器,无法写入或执行任何操作。
当模型查找谁调用了 process_payment 时,它查询图谱并获得完整答案——而不是随机 grep 三个文件。
仅审计模式
分析内核也可以在没有模型、没有网络、没有成本的情况下使用:
thot init /chemin/du/repo --owner "Ton Nom" # autorisation, une fois
thot audit /chemin/du/repo --paths # chemins de teinte complets
thot audit . --all # y compris le bruit faible
thot audit . --json --out rapport.json
thot audit . --out rapport.sarif # SARIF 2.1, format déduit du nom
thot audit . --fail-on high # code 1 en CISARIF——进入已有的工具链
一份任何流水线都无法读取的报告只能活在一个终端里。GitHub code scanning、GitLab、Azure DevOps 和编辑器都能读取 SARIF 2.1,而 Thot 的两个属性在那里比其他地方更有价值。
一个 finding 的身份是规则 + 文件 + 符号 + 函数体指纹,绝不是行号——这正是 partialFingerprints 所要求的。一个以行号填充的仪表板,只要有人在文件顶部加一个 import,就会重新打开所有工单;而以指纹填充则不会。
一条污点路径是一系列位置,这正是 codeFlows 所呈现的:读者从源头点击到汇聚点,而不是盲目相信工具。没有路径的 finding 不携带任何 codeFlows 键——空流会被呈现为没有步骤的污点路径,这读起来像是分析损坏,而不是模式匹配。
被评审组驳回的 finding 不会被删除:它会带着一条有理由的 suppressions 离开。一个从未看到它的仪表板无法区分「没人看过」和「有人看过并做出了裁决」,而后者正是评审组存在的全部意义。
- run: thot audit . --out thot.sarif
- uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: thot.sarif }辅助分析——--deep
确定性分析回答的是「这些数据可能流动吗?」。它是穷尽的、免费的,而且这不是花钱请审计师来裁决的问题。--deep 提出那个昂贵的问题,只针对那些配得上它的候选者:
thot audit . --deep # 20 pires candidats, 4 en parallèle
thot audit . --deep --budget 50 # plus large
thot audit . --deep --parallel 8 # plus vite两轮,刻意对抗:
探针必须命名一个能到达危险点的具体输入。不谈论漏洞类别的泛泛之词——一个 URL、一个值、一个效果。
反驳收到这个场景,唯一使命就是摧毁它:上游验证、只传常量的调用方、禁止假设输入的类型。有疑问时,它选择反驳。
一个 finding 只有在同一代码的第二次敌意阅读未能杀死它时才能存活。这时 confirmed 才真正有意义。
在会话中,同样的事情:/audit deep。
引擎自动选择——如果已连接,则通过官方 CLI 使用你的 Claude 账户(分析并行运行,在你的订阅上);否则使用 API 密钥。
审计不应读取的内容
# .thotignore, à la racine du dépôt
vendor/
*.generated.py
tests/fixtures/内置排除项覆盖了每个仓库都有的内容——node_modules、build、.venv。.thotignore 覆盖只有该仓库自己知道的内容:嵌入的文档、生成的客户端、故意损坏的 fixtures 目录。审计它们不会产生 findings,只会在 findings 本该出现的确切位置制造噪音。
你自己的规则
内置目录了解标准库。它不了解你的团队围绕 subprocess 编写的包装器、你的服务消费的队列,也不了解在你那里使值变安全的验证器。如果没有一个地方来声明这些,对真实系统的每次审计都会在同样的三个地方出错。
# <repo>/.thot/rules/team.yaml — versionné avec le code
# ~/.thot/rules/*.yaml — ce que tu sais, partout où tu travailles
sinks:
- id: sink.team.run_shell
patterns: [run_shell, shellutil.run_shell]
impact: critical
description: Wrapper shell interne (shell=True)
match_mode: bare # qualified | method | bare | prefix
sources:
- id: source.queue
patterns: [msg.payload]
description: File de messages
match_mode: prefix # couvre msg.payload.decode(...)
sanitizers: [validate_host, team.escape]同一个文件在 js: 键下携带 JavaScript 规则——团队包装器通常两种语言都有,而分开声明正是让一半变得过时的方式。
js:
sinks:
- id: sink.js.team
names: [runShell, sh] # comparés au dernier segment, ou qualifiés
impact: critical
description: Notre wrapper shell
needs: [child_process] # ne se déclenche que si le fichier l'importe
sources:
- id: source.js.queue
patterns: [job.payload]
description: File de messages
sanitizers: [escapeArg]模型请求的内容是不可信输入
源是表达式——sys.argv、os.environ。这覆盖了被启动的程序,却漏掉了被调用的程序:代理的工具通过命名参数接收其不可信输入,这些参数由注册表根据模型请求的内容填充,而函数体中任何地方都不会出现表达式。
不对此建模的实测代价:一个下午四个 SSRF,全部通过工具参数到达,污点分析一个都没找到——它们是被模式规则找到的,而模式规则只识别形式,不证明任何东西。
entry_sources:
- id: entry.tool
patterns: [tools.image_gen] # les fonctions qu'un registre appelle
parameters: [args] # facultatif : lesquels de leurs paramètres
description: Arguments remplis par le modèle
match_mode: prefix默认为空,且是刻意的:注册表调用哪些函数是对某个仓库的事实,猜测它会在每个程序的每个参数下埋一个源。在 Hermes 上实测,两个极端:一条命名 plugins 和 tools 包的规则揭示了19 条已证明的路径,其中几条过度近似(helper 从配置中收到的 base_url 并非不可信);一条命名 args 参数的规则揭示了零条,因为 Hermes 的处理器接受命名参数而非字典。正确的规则命名真实的入口点——而这只有它们的作者才知道。
一条复用了内置 id 的规则会替换它——这样可以在不修补 Thot 的情况下降级团队有意接受的 sink。格式错误的文件会停止审计,并指出出错的文件和键,而不是让人误以为没有 finding。
抑制
抑制是唯一没有任何工具会重新审查的安全断言——包括本工具,这是构造使然。# nosec、# noqa: S310、// eslint-disable … security/…:这是对代码的一次性断言,却比它所描述的调用方活得更久。
在本次审计中,它两次是错误的:
抑制 | 它所断言的 | 实际情况 |
| 协议已被检查 | 它只拦截了 |
| URL 来自配置 | 其中一个调用方从工具参数中读取它,也就是来自模型 |
因此 Thot 将它们作为类别报告,级别为 LOW,并附上匹配模式。该 finding 不是说「这一行是危险的」:它说的是「没有人重新审查过它被豁免的理由」。在一次 --deep 扫描中,会有一个代理去验证该模式是否仍然成立。
对于 Python,读取的是真正的注释词法单元,而不是模式——正则表达式无法区分注释中的 # nosec 与 docstring 中引用的相同文本,而本模块的 docstring 就引用了两个。
一条放在本次审计所标记的行上的抑制不是同一个对象:它是一个与活跃 finding 相矛盾的断言,由读过同一行并得出不同结论的人写下。它被提升一级并明确说明。在 Hermes 上实测:45 条中有 7 条——而当天读到的抑制中有三条是错误的。
实测:Thot 上 0 条,Prime 上 0 条,Hermes 上 45 条。
校准
精度与检测同等重要。以下内容刻意不报告:
不带
shell=True的subprocess.run(cmd)——没有 shell 会读取该命令。cursor.execute("… ?", params)——字面查询,参数已绑定。经过
int()、shlex.quote()、os.path.basename()、html.escape()传递的值——这些调用切断了污染链。没有任何入口点能到达的缺陷会被自动降级——但前提是找到了入口点。如果一个都没有,范围是未知,而非无,并且不会基于这种无知埋没任何东西。
payload.get(...)不是requests.get(...)。
数量级,在 Hermes Agent(4 457 个 Python 文件)上实测:98 秒,365 个 findings,其中 25 个 high——应用记忆后,有 3 个超过默认阈值。
参数填充的是哪个参数
一个 helper 的一个参数到达 sink,并不会使其所有传入值都变得危险。然而引擎此前将调用方与被调用方的全部 sink 集合配对,而不看参数落在哪里:
def helper(safe, cmd):
os.system(cmd) # seul `cmd` atteint le shell
helper(sys.argv[1], "ls") # la donnée va dans `safe` — et c'était rapporté现在在调用点读取位置,命名参数则读取名称。这正是 JavaScript 引擎此前已经在其一侧做的事情。
实测,在可执行之后:在分析 Thot 和 hermes/tools 时请求的 83 238 次解析中,90% 指向唯一参数,9.8% 不指向任何参数;0.1% 保持开放(f(*rest)、f(**options))并保留宽泛答案。被调用方平均提供 2.68 个参数,因此近三分之二的搜索空间消失——hermes/tools 的分析从 11.4 秒降至 9.5 秒。
关于 findings 的数量,则毫无变化。Thot、prime 和 hermes/tools 在前后返回完全相同的 151 个候选,其中零个是凭空产生的——安全性属性在实测中成立。四条着色路径变短了,仅此而已。上面修正后的形式是真实存在的,测试也证明了这一点,但它不会出现在所测量的三棵树中的任何一棵:这个修复买来的是速度和更准确的路径,而不是更少的噪音。
两种形式不说明任何问题:helper(*args) 展开的是未知数量的值,而 helper(**options) 不命名任何一个。在这种情况下,引擎保持之前给出的宽泛答案——保留的集合始终包含在之前的集合中,因此细化只会移除一个 finding,绝不会凭空产生一个。
接收者是根据被调用方的签名跳过的,而不是根据调用的语法:Runner().go(x) 归结为一个不带点的 go,而 Cls.m(obj, x) 和 obj.m(x) 都是属性。由于方法几乎总是以绑定方式调用,因此是参数开头的 self 起决定作用——未绑定调用是唯一被读短了一格的形态。
图无法追踪的内容
一个通过分析无法解析的路径到达的缺陷——存放在分发表中的 handler、被装饰的视图、对类型未知的变量进行的调用——不是一个不可达的缺陷。Thot 区分这两者:
HANDLERS = {"run": run_command} # aucun appel : le graphe ne voit rien
@app.route("/ping") # enregistré à l'import par le décorateur
sandbox.run("pytest") # plusieurs `run` répondent à ce nom在三种情况下,作用域都是未知的,而不是空无,finding 保留轻微惩罚而不是被埋没。在 Hermes 上:同样是 365 个 findings,但有 60 个上升了一级。一个既无人调用也无人提及的函数则仍然被正确地降级——否则过滤器就不再是过滤器了。
局限性
跨文件的着色仅限 Python:JavaScript 和 TypeScript 会被索引,能在函数体内跟踪并到达同一文件中的 helper,然后就止步于此,因为缺少已解析的调用图——上表已逐行说明。基于模式的规则则适用于所有地方。
没有 --deep 时,每个 finding 都是 PLAUSIBLE:静态检测到,尚未通过执行证明。使用 --deep 时,confirmed 的 finding 已通过反向反驳的考验——这还不是执行证明,执行证明要等 repro 阶段。而 absence of finding 也不是 absence of defect 的证明:动态分派、反射和元编程都会逃过分析。
在会话中的全文搜索在 fts5 无法回溯 rowid 的 SQLite 上不再保持恒定成本:引擎会对全部匹配结果排序,而不是截断到二十条。在 CPython 3.12 自带的那个上实测——6 588 条指令,而当语料库扩大五倍时是 27 443 条,而一个有能力做到这一点的引擎则保持在 2 215 条然后 2 698 条。有一个测试验证这一点,并在版本不允许时指名该版本。
开发
cd thot
uv run pytest -q以可编辑模式安装:源代码立即生效。但如果 pyproject.toml 发生变化(新增依赖),则需要重新运行 uv tool install --editable --from . thot --force。
确定性核心(codemap、taint、scope、scoring、store、report)不依赖任何 agent,也不接触网络——有一个测试验证这一点,如果发生变化就会让整个测试套件失败。
Spec 和计划:docs/superpowers/。
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Remote MCP for A2A dependency inspector MCP, structured receipts, audit logs, and reviewer-ready evi
Static MCP manifest and tool-policy security preflight with signed input-redacted receipts
A paid remote MCP for CodeG, built to return verdicts, receipts, usage logs, and audit-ready JSON.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/nobodyohm-web/thot'
If you have feedback or need assistance with the MCP directory API, please join our Discord server