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 でない入力をすべて捨て、そのランタイムにはパイプ上のトランスポートがない — したがってPrime側には稼働中のサーバーが必要だ:
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自身のもので、独自のスキーマとマイグレーション履歴を持つ。また、Hermesを起動するインタープリターがMCP SDKをインポートできるかも確認する。なぜなら、それを持たないエージェントはどのツールも持たないからだ — Thotのものだけではない — そしてそれを logger.debug で報告するが、そこは誰も読まない。最後に、Primeのサーバーに応答するか尋ねる:ファイルに書かれたアドレスは接続ではない。
そして補強は双方向に働く:HermesとPrimeはまた、thot audit --deep のエンジンでもある。これは各findingをモデルに論証させ、その後反駁させるステップだ。
デフォルトでは、三つは同じ監査で協力する。 あるfindingはあるエージェントによって論証され、その後別のエージェントによって攻撃される — 決して、そのシナリオにコミットしたばかりのエージェントではない。自分の論証を自分で反駁するモデルは、自分の答案を修正するだけだ;それはパネルが買う唯一のものであり、融合の存在理由だ。
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同じfindingに対する三つの異なるエージェント、測定済み:claude-cliが論証し、primeがシナリオを破壊しようとして失敗し、hermesが二度目の攻撃を行った。報告されたものは、二つの独立した敵対者を生き延びた。
カスケード。 あるfindingは論証され、その後攻撃される。攻撃を生き延びたものが人間に示される — したがって、それは三番目のエージェントに送られる。そのエージェントは論証の構築も最初の攻撃の記述も見ていない。確認されたfindingは、二つの独立した敵対者に対して確認されたものだ。
反駁は決して実質的に再審理されない:攻撃者は少しでも疑わしければ反駁するよう指示されているため、それを再審理すれば偽陽性を生むことになる。しかし、その論証は再読される — 深刻なもの(MEDIUM以上)を葬るときに、そのfindingについて何も発言していないエージェントによって。再読者は欠陥を判断しない。示されたコードで、挙げられた理由が検証可能かどうかを判断する。
二つの誤りは同等ではない。誤った確認は人間の読書時間を10分費やす。誤った反駁は実際の欠陥を永久に失わせる — 記憶された反駁は、その後のすべての監査でスキップされるからだ。これは一度、決定的に起こった:Hermesのコピーにある非常に現実的なSQLインジェクションが、前日に修正されたThotのコピーの完全に正確な記述によって退けられた。異議申し立てされた反駁は確認にはならない — 誰もそれを主張していない — それは元の深刻度とともに plausible に戻り、記憶されない:findingは誰かが判断するまで戻ってくる。
エージェントがタスクに失敗した場合、それは別のエージェントによって一度だけ再試行される。それ以上はない:全員が拒否するタスクには、タスク自体に問題がある。
プローブが保持できるもの、推測ではなく測定。 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 がそこに欠けており、サブエージェントが6回に1回、その穴からファイルを書いた。そこで、そのギャップは検出可能にされる:thot doctor --agents は生きたプローブに実際に保持しているものを尋ね、認識しないものをすべて名指しする。なぜなら、クライアントの次のバージョンは、このリストが聞いたこともないツールをもたらすからだ。そして書き込みに関する緑の線は「今回は違う」を意味し、「不可能」ではない:そのように表現されている。
HermesとPrimeには読み取り専用モードはない。そしてそれは推測ではなく明言されている:-t file は「File Operations」を指し、読み取りと書き込みの両方を含み、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は作業ディレクトリに対する相対パスを開かず、「このファイルを読めません」と応答する — これは欠陥ではなく拒否として読まれる。パネルの3分の1は、2番目のファイルを開くことを要求するあらゆる主張に対して盲目だった。
各エージェントは自分自身として、あなたのアカウントで認証する:Thotはそのコマンドラインを起動し、決してインポートせず、トークンを保持しない。記憶された判定は、決定した者の名前を帯びる — refuted · hermes — 決定は帰属可能であり続けなければならないからだ。
それぞれがもたらすもの、同じインジェクションで測定:
エンジン | 所要時間 | 報告されたトークン |
| 48 秒 | はい、コスト見積もり付き |
| 159 秒 | いいえ — |
数えられないエンジンは数字を作らない:それを宣言し(reports_usage)、呼び出し側は真実に見えるゼロを表示する代わりに「未測定」と言える。
Related MCP server: uni-kb
一つの設定、一つのメモリ
三つはそれぞれ自分のフォルダに書き込む。そしてそれは非常に良いことだ: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設定はファイルから読まれ(スナップショット、リスクなし)、各ツール自身のツールによって書かれる:YAMLの書き換えではなく hermes config set だ。そのYAMLにはコメントと、Thotのものではないマイグレーション履歴が含まれている。Thotがモデルを公式CLIに委任することは不一致ではない:存在しない意見は何とも衝突しない。
メモリは双方向で同じ原則だ:
場所 | 形式 | |
thot |
| 構造化、タイトル + 内容 |
hermes |
|
|
prime |
| markdown、グローバルにロード |
Thot は毎回のブリーフィングで3つすべてを読み取る:Hermes が先週学んだ事実は、今日 Thot が知っている事実である。他の2つには --sync のときだけ、それぞれのネイティブ形式で書き込み、自分自身が置いたエントリ——Hermes では [thot] タグ、Prime では区切られたブロック——にしか触れない。最初の変更の前にバックアップし、3回連続の同期で1つのコピーだけが書き込まれる。
新しく作成された USER.md は空のフォームである:**Name:**、イタリックの説明、水平線。これらを注入すると、Thot は「Context: ---」を事実だと判断してしまう。それらは除外され——そして画面にカウントされる。なぜなら、フォームと簡潔なメモを区別することは、プログラムが確実にできることではないからだ。
ライブラリ、そして履歴
3つは同じ形式——YAML フロントマター付きの SKILL.md、メソッドごとに1つのフォルダ——を読む。これがこれを可能にしている唯一の理由である。
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 が存在するのは、その逆がプログラムの摩擦だったからだ:3つのコマンドと、3つのレポートの頭の中での融合。
thot 227 fichiers 4 finding(s) — 3 high · 1 medium · 17 sous le seuil
hermes 7080 fichiers 190 finding(s) — 8 high · 182 medium · 839 sous le seuil
prime 952 fichiers 14 finding(s) — 3 high · 11 medium · 27 sous le seuil
208 finding(s) sur l'ensemble — 14 high · 194 medium · 883 sous le seuil (`--all`)しきい値は thot audit のものと同じで、それが要点だ:同じツリーは両方のコマンドに同じ数を返さなければならない。このビューは各 low を数えていたので、「しきい値未満」の列の合計を残りに加えて報告していた——同じ分に thot audit hermes が7倍低く見積もるリポジトリに対して。保持されたものは数えられ、決して黙らされない。
判定メモリがすでに判断を下したとき、行はそれを別途示す——0 finding(s) · 416 réfuté(s) en mémoire。ゼロだけではクリーンなツリーのように読める;正しい文は、パネルが416件を却下したということだ。
監査できない部分は、その行を失うだけで、決してパスを失わない:Prime が不在でも、Hermes が言ったことを隠してはならない。
コピーはない:ファイルは所有者の元に残り、各プログラムは他者のフォルダを指す。2回コピーされたメソッドは、1回しか修正されないメソッドである。
Thot は Hermes のインストール済みライブラリをガード下で読む——それは公開レジストリから来ており、まさにガードが存在するケースである。ガードは自分自身が配信するものだけを保証する。しかし Hermes の83メソッドのうち73は Thot のもののビット単位のコピーである:自分のファイルをコミュニティの脅威として報告することは、本当の脅威を無視することを学ばせる誤検知である。バイトが配信されたメソッドと一致するメソッドは、そのメソッドそのものである。ガードは42件の拒否から8件に減った。
そして8件から0件へ、混同しないほうがよい2つの異なる理由による。1つ目は誤ったルールだ:ENV[] は Ruby の定数で、構造上大文字だが、パターンはカタログ全体と同じように——大文字小文字を区別せずに——コンパイルされていた。つまり Python を Ruby として読み、subprocess.run(env=env) の2行前に env["…_TOKEN"] = jeton——子プロセスにシークレットを渡す推奨方法——を「シークレットの読み取り」、CRITICAL と分類していた。2つ目はランクの問題だ:ユーザーが隣のエージェントのフォルダに自分でインストールしたライブラリは、監査対象のリポジトリではない。インストールはすでに意図的に行われている;Thot はマシン上にすでにあるものを読むかどうかだけを決める。それは常にスキャンされ、常に報告されるが、dangerous でのみ拒否される——検査対象のリポジトリが caution から拒否されるのに対して。8件の拒否のうち6件は、自身のトークンのアドレスを文書化するメソッドに外部送信を見る唯一のルールによるものだった。
Prime に付属する13のメソッドは Prime に残る:それらはその IPython カーネル(edit(old_str, new_str)、refine())を文書化している。Thot はそのカーネルを移植したが、これらの関数は移植していない——それらをロードすると、モデルが存在しないものを呼び出すことになる。それらはカタログにはあり、そこで知ることは役立つ;ディスカバリーの外では、信じることは役立たない。
Prime はスーパーセットを受け取り、2つのコピーは受け取らない。測定され、推測されない:Thot のライブラリだけを指すと応答し、Hermes のライブラリだけを指すと応答し、両方を指すとモデルは応答を拒否する。Prime は名前ではなくフォルダを受け取るので、部分的な応答はありえない。
履歴はストレージを統合しない——あるプログラムの移行は別のプログラムの履歴を壊す——しかし「先週の火曜日にこのリポジトリで何をしていたか」という質問は、3つのバイナリのどれが目の前にあったかには関係ない。3つはそれぞれの形式で読み取り専用で読まれ、実行中のセッションによってロックされたデータベースは、その行を失うだけで、リストは失わない。
インストール
git clone https://github.com/nobodyohm-web/Thot.git
cd Thot
uv tool install --editable --from . thotルートでの1回の uv sync で Thot と Hermes の両方がインストールされる:それはワークスペースであり、漂流するコピーではない。Prime は TypeScript で、別途ビルドされる:
cd prime && npm install && npm run buildNode がなくても、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 になりすます必要がある——偽装したユーザーエージェント、借用したシステムプロンプト。Thot はそれをしない。
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 サーバーを介して決定論的ツールを接続し、イベントストリームを整形する。会話スレッドは同じセッション ID で --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 の会話 ID を保持しており、それを返すので、モデルは読み直す代わりに記憶する。
/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 のどちらを選ぶかはあなた次第だ。
メモリ — 一度だけ決める
監査で高価なのは候補を見つけることではない:決定論的フェーズは数秒で無料でそれを行う。高価なのはそれらの価値を判断することだ。実行間でこれらの判断を失うことは、セキュリティツールを耐え難いものにする——同じ40件の却下を毎週、誰もレポートを読まなくなるまで。
› /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 に移行、注釈付き |
| 修正済み — 再発したら、回帰として報告される |
2つの深さ、声に出して
Python | TypeScript · JavaScript | その他 | |
シンボル、コールグラフ、 | はい | はい | いいえ |
関数本体の中での色付け | はい | はい | いいえ |
同じファイルのヘルパーへの色付け | はい | はい | いいえ |
ファイルをまたぐ色付け | はい | いいえ | いいえ |
パターンによるルール | はい | はい | はい |
JavaScript の色付けは、同じファイル内で定義された関数への呼び出し——委任するハンドラの通常の形——を追跡し、そこで止まる。それを言っている。次の2つのレベルは解決されたコールグラフに依存する——ここで呼ばれる readInput があそこに定義されたものであることを知ること。Python のインポートシステムはこの質問に答える;JavaScript のそれは、モジュールリゾルバ、tsconfig、this に対する型チェッカーのビューなしでは答えない。仮定の上に構築された第2レベルは、証明されたパスを報告するツールを、もっともらしいパスを報告するツールに変えてしまう。
エンジンはファイルをスキャンし、名前付き関数の本体はスキャンしない。Web ハンドラの通常の形は、ルートに渡される匿名アロー関数——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 ファイルに対して。72 であって、3,000 ではない — これは色合いエンジンの形であり、パターンスキャナーの形ではない。
エンジンは、誰も名前で呼ばない関数も追跡する:ランタイムがそれらを呼び、値を渡す。addEventListener は色合いを導入する — パラメータ自体が入力である;.then、.map、.forEach は色合いを運ぶ — パラメータは、走査されるものが色合いを持つ場合にのみ色合いを持つ。したがって、定数リストは定数リストのままである。これが値に従うことと値を捏造することの違いであり、これがなければブラウザコードのツリーはほぼ完全に不可視になる。
obj[clé] = valeur でキーが制御されている場合、それは別種のシンクである:ペイロードは値ではなくキーである。なぜなら、__proto__ がプログラム内のすべてのオブジェクトを通して書き込まれるからである。Hermes では実サイト 11 件、Prime では 3 件、すべて 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 の HIGH の finding 25 件中 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 の数は、現在のツリーで再計算されたものであり、2 回の測定の間に Hermes で 9 件の脆弱性が修正されている。
追加された finding も、失われた finding もない。これは降格であり、決して削除ではない:テストコードは開発者のマシンと CI で実行される。これはサプライチェーン攻撃の正確な形である。finding は残り、その役割を出所として保持する。
分類は保守的である — セグメント全体であり、部分文字列ではない(latest/ はテストディレクトリではなく、contest.py はテストファイルではない)、認識されないものはすべて本番コードである。「テスト」側に誤ると本当の欠陥を隠すことになる;「本番」側に誤っても、せいぜい一段階のコストである。
価値はどこから来るのか
finding は、そのパスを開始したソースルールを保持する — source.argv、source.http、source.js.event — 所在する行だけでなく。レポートはそれを明言し(「コマンドライン由来の値…」)、JSON は source_rule キーでそれを提供し、下流のフィルタがフランス語の文ではなく事実を読めるようにする。
これがランクの欠けていた半分である。コマンドラインツール内の open(args.sortie, "w") は、オペレーターがファイルに名前を付けることである:argv を提供する者は、すでにそのプロセスのファイルシステムを掌握しており、この呼び出しは何も追加で与えない。ハンドラー内の open(request.args["f"]) は、任意ファイル読み取りである。同じルール、同じシンク、異なる世界。
ローカルソース | リモートソース | |
| 一段下げる | フルランク |
それ以外すべて | フルランク | フルランク |
この 3 つだけである。argv から構築されたコマンドは依然としてコマンドであり、環境変数から読み込まれた pickle は依然としてコードを実行する:そこでは、経路ではなくシンクが昇格をもたらす。
エンジンが属性チェーンを追跡できるようになった日に Hermes で測定:この区別がなければ、sink.fs.read だけでレポートに 48 件の finding が載り、そのうち 9 件は単一の CI スクリプト由来で、それぞれが要求されたファイルを開くユーティリティだった。区別があれば、それらは閾値以下になり、ワンキーストローク(--all)で済む。
未知の出所はローカルとして扱われ、隠すのではなく明記される:逆を仮定すると、エンジンがソースを特定できなかったすべてのパスがレポートの上位に戻ってしまう。ローカル変数とそれが派生するパラメータとの間のリンクが失われなくなってから、エンジンが特定できるものは倍増した — cible = chemin.strip() は chemin へのリンクを保持し、それがなければシンクはどのパラメータにも、したがってどの呼び出し元にも結び付けられなかった。
なぜ安全なのか
判定は Finding.compute_id にインデックスされ、これはルール、ファイル、シンボル、およびそのシンボルの正規化された AST をハッシュする。再フォーマット、関数の移動、ローカル変数のリネーム:判定は維持される。コードが行うことを変更する:識別子もそれに伴って変わり、判定は自動的に失効する。
したがって、却下はそれが対象としていたコードを決して生き延びない。これが、却下の記憶を許容可能にする唯一の特性である。
識別子はまた、対象となる正確な呼び出しを指定する — httpx.get#3 — それを含む関数だけでなく。これがなければ、同じ関数内の 5 つのネットワーク呼び出しはメモリにとって単一の finding であり、1 つ目を却下すると、その理由ごと他の 4 つも却下されたことになる。この判別子は何も弱めない:ボディの 1 バージョン内で一意であればよく、そのボディの 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 は新しいアイデンティティを取り、古い決定はもはや何も指さない。リストはそれらを他のもののように表示するのではなく [最後の監査に存在せず] とマークする — 6 つの決定のうち 3 つが死んでいるものを、6 つの生きた決定として読ませてはならない。
メモリはモデルの前に適用される:すでに決定 — 却下、受理、または修正 — を持つ finding は、決して分析に送り返されない。すべてが決定済みの実行では、呼び出しはゼロである。これは単なる節約ではない:プローブは信頼度、深刻度、シナリオ、出所を一括で置き換えるため、決定をモデルに送り返すとそれを上書きし、誰が下したかを消してしまう。リグレッションはこれが最も重要になるケースである:すでに一度は実在と判断されており、深いパスがそれを黙らせることはできない。
そして反論は、thot audit --deep からも /audit deep からも、自動的に記録される:モデル呼び出し 2 回、支払いは 1 回。それらは決定したエンジンの名前を保持し、決してあなたの名前ではない — 機械の決定が人間の決定に優先することはない。
何も黙って削除されることはない。却下された 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 と呼び出し元を交差させるループは、モデルの1 ターンで済む;同じことをツール呼び出しで行うと十数回かかり、そのたびにマップがすでに知っていたことの読み取りを再び支払う。
カーネルは決して Thot のプロセス内で実行されない。 自前の exec() は、監査対象のコードに Thot のメモリ、開かれたデータベース、ファイル記述子を与えることになる。したがってサブプロセスであり — サンドボックスが設定されている場合はコンテナ内で実行される。
これが正確に何を保護するか — そして Thot は、自身の敵対的パスが過度に絶対的な docstring を指摘した後、自分自身に対してこれを修正した:
サブプロセス ( | Thot のメモリ、データベース、ファイル記述子を保護する。あなたの認証情報は保護しない:ワーカーはあなたのアカウントで実行され、 |
コンテナ ( | 真の境界:ネットワークなし、あなたの |
機密の環境変数は起動前に除去され、/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。自分以外のリポジトリを再読することは、疑う理由が十分にあるコードを読むことである — そしてモデルがそれを変更することは、めったにあなたが望むことではない。
この姿勢は3 箇所で維持され、1 箇所ではない:モデルに提案されるツール、それでもモデルがツールを呼び出す瞬間、そして — アカウントモードでは — 公式 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 の他の部分とは逆のルールが一つある:他のどこでも、欠落した依存関係はその機能を失わせ、作業は継続する。ここでは、要求されたサンドボックスが利用できない場合、実行を拒否する。黙ってホストにフォールバックすれば、保護が嘘になる。
決定を共有する
判定は、このコードのこのリビジョンに関する事実である。したがって、コードとともに移動する:<リポジトリ>/.thot/verdicts.json は、対象コードに触れるプルリクエストで再読され、ネットワーク接続前の新しいクローンにも存在する。
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。シェルなし、書き込みなし、任意のパスなし;監査は、
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 へのアクセスを拒否される可能性があり、その場合ユニットは 1 行も書き込まずにインタープリタの起動時にブロックされる。あなたのセッションから起動されたプロセスは、孤児になってもそのセッションのアクセスを保持する — これが 3 番目の救済策であり、システムに何も要求しない:
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 ユニットの前に退く:同じジョブに 2 つのスケジューラがあると、作業が 2 倍になり、トークンも 2 倍になる。thot doctor は、どちらが機能しているかを示す。
スケジュールされた監査は、新しいものが何もない限り何も言わない。 同じ 300 件のフィンディングを繰り返す夜間レポートは、誰も開かないフォルダに終わる。上がってくるのは diff である:前回以降に出現したもの、しきい値を超えたもの、すでに重要でないと判断されたものを差し引いたもの。
プラグイン
5 つのフック、それぞれが出荷された何かがそれを使用するため:
Hook | タイミング |
| レポートの前に、注釈を付けるため |
| 監査完了 — 通知、エクスポート、アーカイブ |
| エージェントの書き込みの前に — 警告を返す |
| 書き込み成功の後 |
| 決定が記録された直後 |
プラグインは、~/.thot/plugins/ または <repo>/.thot/plugins/ にある plugin.yaml と __init__.py を含むフォルダである — 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 はフォルダのフィンガープリントを記録し、わずかな変更でもそれを失効させ、その旨を伝える。
3 つが出荷されている:
プラグイン | 機能 |
| モデルが書くものを再読し、危険なパターンが現れた場合に警告を上げる。ブロッキングではない — セッションをブロックする誤検知は、書き込み自体よりも悪い。 |
|
|
| 各監査、判定、書き込みのローカル JSONL ジャーナルを |
すべてが揃っていることを確認する
「動作する」は主張であり、3 つのプログラムで構成されるプログラムについては、特にツール自身からの言葉である場合、その主張を鵜呑みにすべきではない。
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 で、ジャーナルは存在しない — 決して起動したことのないタスクに対して「すべて順調」を示す 3 つのシグナル。緑の行を数えるよりも、原因を名指しする方が良い。
この検証自体も修正が必要だった。パスの形式でタスクを非難していた — 「ツリーは ~/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書き込み行は、厄介な能力を宣言しているのに緑である:それらは、望ましいことではなく、あるがままを報告する。3 つのエージェントのうち 2 つが書き込むことができ、それを防ぐフラグはない — ファイルを作成するよう依頼し、ディスクを確認することで測定される。防げないものは、見逃せないようにされる:AuditResult.touched はパスが変更したものを名指しし、夜間ループはそれを stderr で叫ぶ。
一時フォルダにファイルを置き、その内容を要求する。これは実際の欠陥のために存在する:Hermes は作業フォルダに対する相対パスを開かず、拒否のように読めるフレーズで応答していた。パネルの 3 分の 1 は、2 番目のファイルに依存する主張を検証できず、ファイルを置くこと以外にそれを示す方法はなかった。
各行は実際の操作を実行し、測定したことを報告する:「skills: 設定済み」ではなく「91 ロード、0 拒否」。色エンジンは 2 つの言語のサンプルでパスを探し、MCP サーバーは自身のプロトコルに応答する。実行できない検証は、黙って合格する代わりに失敗する:「未テスト」を意味する緑の行は、赤い行よりも悪い。ネットワークにもモデルにも触れない — thot doctor は飛行機の中でもオフィスと同じ答えを返す。失敗時に非ゼロの終了コード、&& や CI に収まるように。
継続的な改善
20 件の候補を議論して停止する監査は、残りを永遠に判断されないままにする。予算なしのパスは、あなたが座り直すときにもまだ実行されている。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 は候補が1つだけだった。つまり、ツリーあたり20の予算では、40をそれらを使えないツリーに費やし、その間Hermesは150を待っていたことになる。20のターンは、20、20、60のターンになる。
3つ目の仕組みが、これを速く収束させる。失敗がカウントされるのだ。エージェントが期限を超えた候補、またはモデルがコミットを拒否した候補は、その深刻度を保持する——つまり、次のターンで最初に再取得され、その次のターンでも同様である。1,660行のファイル内のfindingで測定したところ、3回のパスに対して4回の試行があり、そのうち3回が同じ壁にぶつかっていた。2回失敗すると、キュー末尾に移動する。依然として対象ではあるが、優先されることはない。成功するとカウントは消去される——忙しい午後やサブスクリプションの枯渇だった壁が、findingを永遠に追いかけ続けるべきではないからだ。
2つの特性により、ループはぐるぐる回るのではなく収束する。反駁は記憶されるため、次の選択はそれをスキップする。確認は意図的にされない——本当の欠陥は、誰かが修正するまで出現し続けるべきだから——そのため、ループは独自の「すでに判断済み」IDセットを持つ。これがないと、最初のターン以降の各ターンは、最初のターンが確認したばかりのことを再論証するために全予算を費やすことになる。
ループは、合計の前に、やるべきことで終わる。
À 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することになる——これは、1日の間に毎回、実際に起こったことである。
ループは決してコードを変更しない。ここで「改善」とは、プログラムの自己判断がより明確になり、より安価になることを意味する。決定のない候補が減り、ディスク上に決定が増え、それぞれがそれを下したエージェントに帰属できる。
温度計、そしてそれを使うループ
上記のすべては、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コーパスは組み込まれていない——18,000のサードパーティファイルがこのリポジトリにあるべきではない——そのため、パスは常に指定され、各スイートはマニフェストのフィンガープリントに対して検証される。測定中にラベルが動いたコーパスは、コーパスがないより悪い。それ以降のすべての数値が間違っており、それを示すものは何もない。
スコアはユーデンのJ、TPR − FPR である。ゼロはコイントス、+100は完璧、そして負はルールが逆であることを意味する。適合率と再現率はそれを示さなかっただろう。真陽性がゼロのルールは適合率が未定義で、空として表示され、データなしと読める——これはまさに逆ルールが生き残る方法である。Jにはその穴がない。
2つの不正方法があり、両方とも負ける。検出数を減らす——これが provenance を上げていた——とTPRが下がる。すべてを報告するとTPR 100 %、FPR 100 %、Jゼロになる。コーパスが正確にそのために50/50でバランスされている。
測定された状態、デフォルトのフロア、3つのフレームワーク(django、fastapi、flask):
avant après
TPR 9.9 % 55.5 %
FPR 0.5 % 0.000 %
J de Youden +9.4 % +55.5 %
catégories actives 10 34
catégories négatives 0 0「Before」は、温度計が初めて存在した時点のプログラムの状態である。「After」は、同じコーパス、同じフロア、同じコマンドである。ホールドアウトがそれを確認し、3つの方法で確認する。django単独で +55,5 %、fastapi +55,5 %、flask +55,5 %。互いにまったく似ていない3つのフレームワークが、小数点第1位まで同じ数値——ルールは、それらを書くために使われなかったコードに対して機能する。
カテゴリ別に、エンジンが今日できること。アクティブな34カテゴリのうち32が正確に+100 %:
basic_xss · cleartextstorage · cleartexttransmit · cloud_ssrf_metadata
cmdi · codeinj · cookie_no_httponly · cookie_no_samesite · corsmisconfig
crlfinjection · csv_injection · debug_code_production · default_credentials
deserial · errormessage · eval_injection · hardcodedcreds · ldapi
loginjection · nosql · redirect · securecookie · sqli · ssti · tlsverify
weakcipher · weakhash · weakkeylength · weakrand · xpathi · xss · xxe
+100,0 %
pathtraver +94,0 % ssrf +92,0 %9,150の健全なケースで偽陽性ゼロ、負のカテゴリもなし。出発点は ssrf が−8,0 %、xxe が−100 %だった。
天井。最大値なしの数値は意味をなさないから
+55,5 % は「問題の半分が解決された」という意味ではない。61カテゴリのうち、27は到達不可能であり、その理由は2つに分かれ、両方ともファイルを開いてケースバイケースで検証されている。
10は、別のカテゴリがすでに所有しているコードである。 コーパスは同じ行に2つの異なるCWEを書いている。el_injection と ssti は同じ Template(データ) 呼び出しであり、argument_injection と genericcmdi は cmdi と同じ os.system('echo ' + データ) であり、unverified_signature は tlsverify と同じ verify=False であり、sensinlogs は loginjection と同じ logging.info であり、hardcoded_crypto_key は hardcodedcreds と同じハードコードされたキーである。Thotはこれらすべてのファイルをスキャンし——レポートの seen 列にそれが示されている——コードが実際に何であるかのクラスを宣言する。それらを捕捉するには、1つのルールに2つのクラスを命名する必要があり、report/cwe.py はまさにそれを拒否するために存在する。
17にはシンクがない。 authzfailure、idor、intoverflow、null_deref、privescalation、csrf、missingcritauthn、sessionfixation などはすべて、危険な呼び出しを1つも含まない return JsonResponse(...) で終わる。idor の脆弱なケースは正しくパラメータ化されたリクエストである。欠けているのは認可呼び出しである。clickjacking は、Djangoがミドルウェアで設定する X-Frame-Options の欠如を報告することで勝てる。weak_password_hash はこのコーパスを完全に分離する——脆弱なケースはすべて hashlib.sha256 で、健全なケースはすべて pbkdf2_hmac である——しかし、これらの行のどれも値がパスワードであるとは言っておらず、ルールは実際のリポジトリのすべてのチェックサム、すべてのETag、すべてのHMACをスキャンすることになる。最後の2つは、もしかしたら結局コードかもしれないという考えで再開されたが、書く価値のある理由で閉じられる。intoverflow は ctypes.c_int32 で弱点を製造している——Pythonの整数はオーバーフローしない——そして ctypes をスキャンするルールはBenchProctorに関するルールになるだろう。resourceexhaust は、境界のない bytearray(int(データ)) と bytearray(min(…, 1024)) であり、分離子は単一のトークン min である。しかし bytearray(x) は、x が整数かバッファかによって2つの意味を持ち、Thotは型を推論せず、hermes/ ではこのファミリーの102の呼び出しはすべて変換であり割り当てではない。これらはロジックの欠陥または欠落したチェックである。どの色合い分析も、どのルールを書いても、それらを見ることはできない。
したがって、正直に獲得可能な34カテゴリ、つまり+55,7 % が残る。それらはすべてアクティブである。+55,5 % では、正直な最大値の99,6 % である。
まだ欠けている21のケースは、もはやカテゴリではなく、そのうちの2つの中の見逃されたケースであり、1つずつ数えられた。ssrf の12 は、ソースが mq_client.get_message() または redis_client.get() であり、BenchProctorの app_runtime.py で宣言された2つの _Stub() オブジェクトである——それらはこのコーパスの外に存在するものを何も命名しておらず、それらのために書かれたパターンは、何かを検出する代わりに温度計に貼り付くことになる。pathtraver の9 は環境変数に由来し、アクセシビリティ割引が意図的に格下げする。なぜなら、環境を提供する者は、すでにこのプロセスのファイルシステムを保持しているからである。
これら46ポイントを生み出したもの、測定された順序で:
変更 | J |
出発点 | +9,4 % |
Webルートがエントリポイントとして認識される | +9,6 % |
色合いがコンテナ内の値に従う | +10,0 % |
定数分岐の三項演算子は何も運ばない | +12,4 % |
否定クラスの | 含む |
SSRFガード:ホストのホワイトリスト、解決されたIP範囲 | +14,3 % |
| +14,9 % |
パス封じ込めと名前付きホワイトリスト | +15,5 % |
| +16,3 % |
1行のパターンルール12個 | +27,8 % |
パターンがアクセシビリティ割引を支払わなくなる | +34,4 % |
| +35,6 % |
4つのインジェクションシンク:テンプレート、XPath、LDAP、NoSQL | +40,8 % |
レスポンスヘッダー、ポジティブガード、CR/LF中和 | +44,2 % |
スプレッドシートのセルはテキスト行ではない | +45,6 % |
| +46,6 % |
色合いが | +47,7 % |
Djangoが3つのソースを大文字で書く | +48,0 % |
| +49,0 % |
文字列を返すビューはHTMLを返す | +49,9 % |
証明はそれが証明することだけに価値があり、引用されたSQL識別子 | +50,0 % |
文字列に書かれたプログラムはプログラムのままである | +50,4 % |
ルートのパラメータはネットワークから来る | +50,4 % |
第三者の応答はこのプログラムのデータではない | +50,6 % |
プログラムが | +51,2 % |
データベースが返すものは、誰かがそこに入れたものである | +53,9 % |
ログ行はヘッダーと同様に改行で終わる | +55,5 % |
1行上で構築されたページを返すルート | +55,5 % |
これらの変更のうち2つは、最初は測定後に拒否されていた。そして、それらを拒否したのは温度計だった。clickjacking はこのリポジトリで23の偽陽性を犠牲にし、weak_password_hash はこのコーパスを完全に分離する——脆弱なケースはすべて hashlib.sha256 で、健全なケースはすべて pbkdf2_hmac である。それらは、上で述べた理由により、拒否されたままである。これらの行のどれもパスワードであるとは言っておらず、ルールは実際のリポジトリのすべてのチェックサムをスキャンすることになる。
3つ目の拒否が解除されたが、これはその中で最も示唆に富むものだ。データベース読み取りを信頼できないソースとして扱うことは、2回測定され、2回拒否されていた。1回目は脆弱436件に対して健全343件、ストアドXSSに限定したときは11件対13件——健全な半分の方が、もう片方より頻繁にデータベースを読み取っていた。どちらの判定も、当時のプログラムに対しては正しかった。変わったのはコーパスではない。変わったのは健全な半分の防御であり、それが今では一つずつ理解されている——暗号化、HTMLエスケープと自動エスケープ付きレンダリング、CR/LF無害化、ホストのホワイトリストと解決済みIPレンジ、ポジティブガード、引用符付きSQL識別子。行を読み取ってから暗号化する健全なケースは、暗号化によって無罪とされるのであって、誰もその行を見なかったからではない。343件の発明品を犠牲にしていた同じルールは、今ではゼロ件を犠牲にし、16カテゴリすべてで同時に2.7ポイントをもたらす。測定された拒否は、測定されたその日だけ有効なのだ。
LDAP / XPath / NoSQL / SSTI / ELインジェクションの5つのルールもまた、J = 0.000と測定されて拒否されていた。それらは健全な半分にももう片方にも同じように引っかかっていた。そのうちの4つは今日、**+100.0%**で、偽陽性は一つもない。そして、見解を変えたのは判定ではない——彼らに読ませたものだ。それぞれが、インジェクションを担う引数だけを見るようになった(LDAPフィルタは3番目、テンプレートのソースは1番目)。sink.nosqlはインポートではなくクエリテキスト上のバリアを通過する。なぜなら.find(はPythonのすべての文字列に属するからだ。5つ目のel_injectionは拒否されたままである。それはsstiと同じTemplate(…)呼び出しであり、別のクラスでラベル付けされているだけだ。その名前を付けることは、不正確さのために1ポイントを支払うことになる。
精度は死角で買えない
上で認識された各ガードは、最初にスコアを上げかつ悪用可能な穴を開けるバージョンで提案された。敵対的プローブがそれらをすべて見つけた:
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防御であり、文字列にまだ残っているシェルのメタ文字については何も言っていない。どこでもティントをパージすることは、174件少ない偽陽性と、コマンドインジェクションの死角を買っていた。
そこからエンジンが担う区別が生まれる:値を制約するガード(リテラルのホワイトリスト、列挙するfullmatch)は、それをすべてに対して白とする。宛先を証明するガード(ホストが許可されている、解決されたアドレスが公開である、パスが閉じ込められている)は、関連するsinkファミリーに対してのみ有効である。bleach.cleanも同様で、HTMLを中和するがx; rm -rf /は無傷のまま残す。
4つの敵対的プローブがテストスイートに含まれており、すべて正しくシグナルを発する。これらの穴のいずれかを再び開く将来の「改善」は、名前付きテストを壊す。
残る沈黙には2つの原因があり、thot benchはそれらを分離する。なぜならそれらは2つの異なる作業だからだ:
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は1つのエージェントを選んで呼び出す。もう一方を探しに行くのは、最初のエージェントがエラーを返した場合だけだ。このようなターンは、構造上、2つのうちの良い方に上限がある。失うことは少なくできても、勝つことは決して多くできない。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の推論を、それにコミットする前に見る。構築してから再読する、ではそれはできない——2番目が見るとき、1番目はすでに決定している。
目標は測定から来るのであって、打ち込まれた文からではない。これまでループは、人間がすでに疑っていたものだけを追求できた。スコアから構築された目標は、プログラム自身がどこが弱いかを、自分で選んでいない数字で言うことだ——そして同じ数字が、その答えが役に立ったかどうかを後で言う。各目標は失敗したファイルを運ぶ:「xssは0%だ」と言われたエージェントは推測するしかないが、3つの失敗したファイルを与えられたエージェントには解決すべき問題がある。
過学習、そして--hold-outが本当にできること
コーパスで採点されるループには、不正をする本当の方法が1つだけある:コーパスを学習することだ。BenchmarkTest01126がどのように見えるかに合わせて調整されたルールは、スコアを上げ、誰の役にも立たない。そして外から見れば、それは本当の進歩と区別がつかない。
--hold-out flaskは1つのスイートを主要な数字から外し、それを第二の防波堤として保持する:最適化されたスイートは動かすが、一度も見たことのないスイートは動かさない変更は、自分が何であるかを語ったことになる。両方の数字はne_baisse_pasで保持される。
その限界は、測定されている:3つのフレームワークは互いに0.5ポイント以内で採点する。これはファイルレベルの過学習は捕まえるが、ベンチマークの形式への過学習は捕まえない——生成されたコーパスは生成されたコーパスのままであり、デモコードでのみ役立つルールは3つすべてを通過するだろう。ホールドアウトは不正を可視化する。コーパスを代表的なものにはしない。
ループがラウンドごとに保持するものは、~/.thot/evolve-log.jsonlに書かれる。それがなければ、測定値が1ラウンドでほとんど動かないため、次のラウンドは同じ最悪のカテゴリを再読し、同じファイルを提示し、そして——当然ながら——すでに構築され、測定され、取り消された同じ仕様を受け取る:--rounds 5は、5回試みられた試み、5倍のコスト、忙しいふりをしたものになるだろう。
これはオラクルではない。修正がグリーンで、Jを上げ、それでも悪いままでいることはあり得る——それは過学習であり、自動プログラム修復の文献はそれについてしか語っていない。コーパスは、そのコーパスに対して進歩したという証拠である。ループは、人間が反対できるように、自分が何を変えたかを報告する。
スキル——Thotが知っているメソッド
スキルとは、一度書かれたメソッドである:YAMLフロントマター付きのSKILL.md。これはHermes AgentとPrime Agentのフォーマットである。したがって、どちらかのために書かれたスキルは、変更なしでここに読み込まれ、その逆も同様である。
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ツールでそれらを発見する——キーワードを与えない限り名前のインデックスで応答する。なぜなら200の説明はカタログではないからだ——そして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)。すでに存在する名前は、同梱バージョンを置き換える——フォークせずに提供されたメソッドを適応させる方法である。
監査済みリポジトリから提供されたメソッドは、まず分析される
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 <dossier>は、オンデマンドで同じ質問をする。Thot自身が提供するものは分析されない:それはプログラムがインストールされているからディスク上にあるのであって、リポジトリが要求したからではない。
カスタムコマンド
マークダウンファイルはコマンドである。文法は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}。引数が再解釈されることは決してない。リポジトリのコマンドは、そのスキルと同じガードを通過する。
3つが同梱されている:/triage(入力を命名するか、フォローアップなしで分類する)、/harden(最初に失敗するテスト、次に修正)、/regress(git参照に対する差分監査)。
MCPサーバー
Hermes Agentのカタログ、検証済み20サーバー:
thot mcp list # le catalogue, et ce qui est déjà connecté
thot mcp show sentry
thot mcp add linearインストールは公式CLIに委任されており、それはすでにOAuthとトークン更新を所有している——Thotが漏らすための第二の金庫を保持する理由はない。登録済みは承認済みではないこと、そしてどのコマンドが作業を完了するかを明示的に言う。
モデルのツール
定番——読む、書く、編集する、コマンドを実行する。すべての書き込みと実行は確認を要求し、これは設定可能ではない。
そしてThotにのみ属する4つ、無料——なぜならそれらはモデルではなくマップを照会するからだ:
ツール | 応答 |
| プロジェクトのファイル |
| 関数のファイル、行、パラメータ |
| 誰が何を呼ぶか、そしてエントリポイントまでの距離 |
| ソース → sinkのティントパス |
アカウントモードでは、これら4つはthot.mcp_serverによって公式CLIに提供される——読み取り専用のMCPサーバーであり、何も書いたり実行したりできない。
モデルがprocess_paymentを誰が呼ぶかを探すとき、グラフを照会して完全な答えを得る——ランダムに3つのファイルを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の2つのプロパティはそこで他のどこよりも価値がある。
ファインディングのアイデンティティはルール + ファイル + シンボル + ボディのフィンガープリントであり、決して行番号ではない——これはまさにpartialFingerprintsが要求するものだ。行番号で供給されるダッシュボードは、誰かがファイルの先頭にインポートを追加するたびにすべてのチケットを再開する。これで供給されれば、そうはならない。
そしてティントパスは一連の位置であり、codeFlowsがそれをレンダリングする:読者はソースからsinkまでクリックして移動でき、ツールを言葉通りに信じる必要がない。パスのないファインディングはcodeFlowsキーを持たない——空のフローはステップのないティントパスとしてレンダリングされ、それはパターンマッチではなく壊れた分析として読まれる。
パネルによって反駁されたファインディングは削除されない:正当化された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、値、効果である。
反駁(レフュタシオン) は、このシナリオを受け取り、それを破壊することだけを使命とする:事前検証、定数のみを渡す呼び出し元、想定された入力を禁止する型。疑わしい場合は、反駁する。
発見(ファインディング)は、同じコードに対する敵対的な第二の読みがそれを殺すことに失敗した場合にのみ生き残る。confirmed はそのとき意味を持つ。
セッションでも同じこと:/audit deep。
エンジンは自動的に選択される——公式CLI経由のあなたのClaudeアカウントが接続されていればそれ(分析はあなたのサブスクリプション上で並列に実行される)、そうでなければAPIキー。
監査が読んではならないもの
# .thotignore, à la racine du dépôt
vendor/
*.generated.py
tests/fixtures/組み込みの除外は、あらゆるリポジトリが持つものをカバーする——node_modules、build、.venv。.thotignore は、このリポジトリだけが知っているものをカバーする:埋め込まれたドキュメント、生成されたクライアント、意図的に壊されたフィクスチャのフォルダ。それらを監査しても発見は生まれず、発見があるはずのまさにその場所にノイズが生まれるだけだ。
あなた自身のルール
組み込みカタログは標準ライブラリを知っている。それは、あなたのチームが 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。これは、起動するプログラムをカバーし、呼び出すプログラムを見逃す:エージェントのツールは、モデルが要求したものからレジストリによって満たされる名前付きパラメータで信頼できない入力を受け取り、本体のどこにも式は現れない。
これをモデル化しないことの測定されたコスト:ある午後に4件の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の証明された経路を明らかにし、そのうち複数は過剰近似である(ヘルパーが設定から受け取る base_url は信頼できないわけではない);args パラメータを指名するルールはゼロを明らかにする。なぜならHermesのハンドラは名前付きパラメータを取り、辞書は取らないからだ。正しいルールは実際のエントリポイントを指名する——そしてそれを知っているのはその作者たちである。
組み込みの id を引き継ぐルールはそれを置き換える——チームが意図的に受け入れたシンクを、Thotにパッチを当てずに劣化させることができる。不正な形式のファイルは、発見がないと思わせるのではなく、ファイルと問題のあるキーを指名して監査を停止させる。
抑制(サプレッション)
抑制は、どのツールも再読しない唯一のセキュリティに関する主張である——このツールも、構造上、含まれない。# nosec、# noqa: S310、// eslint-disable … security/…:それはコードに関する主張であり、一度書かれ、それが記述していた呼び出し元よりも長生きする。
ここで、同じ監査の中で二回、それは誤りだった:
抑制 | それが主張していたこと | 実際に真実だったこと |
| スキームは制御されている | それは |
| URLは設定から来る | 呼び出し元の一つがそれをツール引数から読む、つまりモデルから |
Thotはしたがってそれらをクラスとして、LOWで、パターンを横に書いて報告する。発見は「この行は危険だ」とは言わない:「その行が免除された理由を誰も再読していない」と言う。--deep パスでは、パターンがまだ成立するかを検証するエージェントである。
Pythonでは、パターンではなく実際のコメントトークンが読まれる——正規表現は、コメント内の # nosec と、docstring内で引用された同じテキストを区別できない。そしてこのモジュールのdocstringはそのうちの二つを引用している。
この監査が報告する行に置かれた抑制は、同じオブジェクトではない:それは、同じ行を読んで別の結論に達した誰かによって書かれた、生きた発見と矛盾する主張である。それは一段階引き上げられ、そのことを明示する。Hermesで測定:45件中7件——そしてその日に読まれた抑制のうち3つは誤りだった。
測定:Thotで0、Primeで0、Hermesで45。
キャリブレーション
精度は検出と同じくらい重要である。意図的に報告されないもの:
shell=Trueなしのsubprocess.run(cmd)— シェルはコマンドを読まない。cursor.execute("… ?", params)— リテラルクエリ、バインドされたパラメータ。int()、shlex.quote()、os.path.basename()、html.escape()によって渡される値 — これらの呼び出しは汚染チェーンを断ち切る。どのエントリポイントも到達しない欠陥は自動的に格下げされる——ただし、エントリポイントが見つかった場合に限る。一つもなければ、スコープは不明であり、ゼロではなく、その無知の上に何も埋もれない。
payload.get(...)はrequests.get(...)ではない。
桁のオーダー、Hermes Agent(4,457のPythonファイル)で測定:98秒、365件の発見のうち25件がhigh——メモリを適用した後、デフォルトのしきい値を超えたのは3件。
引数が満たすパラメータ
一つのパラメータがシンクに到達するヘルパーは、渡されるすべてのものを危険にするわけではない。しかしエンジンは、引数がどこに着地するかを見ずに、呼び出し元をcalleeのシンクの集合全体と照合していた:
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))で、広い回答を保持する。calleeは平均2.68パラメータを提供していたため、検索空間のほぼ3分の2が消える——hermes/tools の分析は11.4秒から9.5秒に短縮される。
発見の数については、逆に:変化なし。Thot、prime、hermes/tools は、前後でまったく同じ151の候補を返し、そのうち発明されたものはゼロ——安全性の性質は実地で成立する。色分けの経路が4つ短くなるだけで、それ以上はない。上記の修正された形式は実在し、テストがそれを証明するが、それは測定された3つのツリーのいずれでも発生しない:この修正は速度とより正確な経路を買うものであり、ノイズの減少ではない。
二つの形式は何も言わない:helper(*args) は未知の数の値を広げ、helper(**options) はどれも指名しない。そこでは、エンジンは以前与えた広い回答を保持する——保持される集合は常に以前のものの部分集合であるため、精緻化は発見を削除することしかできず、発明することは決してない。
レシーバは、呼び出しの構文ではなくcalleeのシグネチャの信頼に基づいてスキップされる:Runner().go(x) はドットなしの go に到達し、Cls.m(obj, x) も obj.m(x) も両方とも属性である。メソッドはほぼ常にバインドされて呼び出されるため、パラメータの先頭にある self が決定する——非バインド呼び出しだけが、一段階短く読まれる唯一の形式である。
グラフが追跡できないもの
分析が解決しない経路によって到達される欠陥——ディスパッチテーブルに格納されたハンドラ、デコレートされたビュー、型が不明な変数への呼び出し——は、到達不能な欠陥ではない。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三つの場合すべてでスコープは不明であり、ゼロではなく、発見は軽いペナルティを保持し、埋もれない。Hermesでは:同じ365件の発見だが、60件が一段階引き上げられる。誰も呼ばず、誰も言及しない関数は、正しく割り引かれたままである——そうでなければフィルタはフィルタでなくなる。
限界
ファイルをまたぐ色分けはPythonに限定される:JavaScriptとTypeScriptはインデックスされ、関数本体と同一ファイルのヘルパーまで追跡され、解決された呼び出しグラフがないためそこで止まる——上の表が行ごとにそれを示している。パターンベースのルールは、どこにでも適用される。
+55.5%はPythonの数字である。 BenchProctorはdjango、fastapi、flaskをラベル付けし、それ以外はない;JavaScriptカタログはその対応物より狭い——「データベース読み取り」ソースもロギングシンクもなく——そのルールは18,300のラベル付きケースではなくテストに依存している。修正が両方のエンジンに有効な場合は両方に移植され、ログはどれがどこで測定されたかを示す。
sink.log はメッセージとそれだけを読む。logger.info('failed: %s', exc) は、値をロギング機構に委ね、作者が入力した文字列には書かない;これらの引数も読むことは、コーパス上で+0.0000、hermes/ 上でさらに4,844件の発見、ツリー内の except … as exc: ごとに1件を意味する。これはカバーされておらず、それを言うことは、信じさせるよりも安い。
--deep なしでは、各発見は PLAUSIBLE である:静的に検出され、まだ実行によって証明されていない。--deep では、confirmed の発見は敵対的な反駁を生き延びた——それはまだ実行の証明ではなく、それはreproで来る。そして発見がないことは欠陥がないことの証明ではない:動的ディスパッチ、リフレクション、メタプログラミングは分析から逃れる。
セッション内の全文検索は、fts5がrowidを遡れないSQLiteでは一定コストでなくなる:エンジンはそこで、20件で切る代わりに一致の集合全体をソートする。CPython 3.12が同梱するもので測定——コーパスが5倍になったとき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)はどのエージェントにも依存せず、ネットワークに触れない——テストがそれを検証し、変更があればスイートを失敗させる。
仕様と計画:docs/superpowers/。
This server cannot be deployed
Maintenance
Related MCP Connectors
38 MCP tools: Rust computation, Discovery, composition, counterexample scans and signed checks.
Hosted code graph over MCP: exact callers, dependencies, and cross-repo blast radius for AI agents.
Self-hosted MCP server: 26 deterministic dev, security, and EVM tools.
Multiple MCP tools, persistent graph memory, token-saving data pointers, and more.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to search code by meaning, explore codebase structure, store and query knowledge with temporal facts, and read source code through a set of MCP tools.386 npm7MIT
- AlicenseNot gradedqualityDmaintenanceEnables parsing, indexing, and querying source code as structured knowledge, providing code exploration, spec generation, and migration tools via 20 MCP tools.MIT
- FlicenseNot gradedqualityDmaintenanceProvides code intelligence by indexing source code into SQLite and offering MCP tools for symbol search, flow tracing, and context retrieval to assist with code navigation and understanding.-
- AlicenseNot gradedqualityAmaintenanceMCP server for persistent code exploration and diff-aware review, exposing explore and review tools to map call paths, blast radius, and generate explainable risk-scored reports.3 npm2MIT