Thot
Thot
Ein Terminal-Code-Assistent, der dein Repository bereits kennt — und das Repository, in dem Hermes Agent und Prime Agent leben, vollständig.
Ein Konversationsagent entdeckt ein Projekt, indem er Dateien mit dem Modell öffnet: langsam, unvollständig, bei jeder Sitzung neu zu bezahlen. Thot berechnet dasselbe Bild per AST und Aufrufgraph — vollständig, sofort, kostenlos — und gibt dem Modell nur das, was zählt.
Die drei Programme
Dieses Repository enthält drei, nicht eines. Keines ist eine Umschreibung eines anderen: jedes ist in seiner Sprache, mit seinem Werkzeug, und Thot verbindet sie.
Was es ist | Wo | |
thot | deterministisches Audit, Code-Karte, Speicher der Urteile |
|
hermes | der Agent: Werkzeuge, Brücken, Plugins, Cron, ACP |
|
prime | der Code-Agent: Modellanbieter, 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éDie Verbindung ist nicht dekorativ. thot fusion wire deklariert den MCP-Server
von Thot in beiden Agenten: Sie erhalten code_map, find_symbol,
callers, audit, skills und skill — die vollständige Karte des
Repositories, außerhalb des Modells berechnet, statt sie Datei für Datei neu
zu entdecken. Das ist die gegenseitige Verstärkung: Thot weiß, ohne zu
fragen, Hermes und Prime handeln.
Die beiden Agenten erreichen sich nicht auf dieselbe Weise, und das Gegenteil
zu behaupten, hätte nur einen von beiden verbunden. Hermes startet den Server
selbst und spricht mit ihm über die Leitung, die er geöffnet hat. Prime
akzeptiert nur HTTP — sein mcp-manager verwirft jede Eingabe, deren Typ
nicht http ist, und seine Laufzeit hat keinen Transport über eine Leitung —
daher benötigt seine Seite einen laufenden Server:
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 demainDie dritte Zeile existiert, weil die ersten beiden nur eine Sitzung halten.
fusion wire schreibt http://127.0.0.1:8787/mcp in die Konfiguration von
Prime, und diese Adresse ist wahr, solange ein Terminal offen bleibt: Beim
ersten Neustart verspricht die Datei einen Server, den niemand mehr bedient.
thot mcp service --install schreibt die Einheit, die ihn neu startet —
KeepAlive auf macOS, Restart=always unter systemd — und lädt sie
nicht: Thot druckt den Befehl und überlässt dir das Starten, weil ein
Werkzeug, das Agenten im Hintergrund registriert, ohne es zu sagen, ein
Werkzeug ist, dem man nicht mehr glaubt. thot doctor stellt dann die
einzige Frage, die zählt — antwortet die Adresse jetzt — und nicht, ob die
Datei existiert.
fusion wire installiert bei Prime auch das Methodenpaket, das Thot für ihn
liefert: Ein Konfigurationseintrag reicht nicht, Prime erreicht einen MCP-Server
nur über eine Klasse, die ihn benennt. Und es legt das Token in auth.json
ab, ohne die Anmeldedaten des Modells anzufassen — ohne Token scheitert die
Verbindung, bevor sie geöffnet wird.
Jeder Agent behält seine Konfiguration. thot fusion unwire macht alles
rückgängig, und die settings.json von Prime wird vor der ersten Änderung
gesichert.
thot fusion status misst, was funktioniert, nicht was geschrieben ist:
Hermes installiert die portablen Plugins deaktiviert, aus Sicherheit, also
verbindet das Schreiben der beiden Dateien nichts, solange plugins.enabled
es nicht benennt. Die Aktivierung erfolgt über das CLI von Hermes, niemals
über eine Bearbeitung seiner config.yaml — diese Datei gehört ihm, mit
seinem Schema und seinen Migrationen. Es prüft auch, ob der Interpreter, der
Hermes startet, das MCP-SDK importieren kann, weil ein Agent, der es nicht
hat, kein Werkzeug hat — nicht nur nicht die von Thot — und sagt es in
logger.debug, wo niemand es liest. Schließlich fragt es den Server von
Prime, ob er antwortet: Eine in einer Datei geschriebene Adresse ist keine
Verbindung.
Und die Verstärkung wirkt in beide Richtungen: Hermes und Prime sind auch
Motoren für thot audit --deep, den Schritt, der jedes Finding von einem
Modell argumentieren und dann widerlegen lässt.
Standardmäßig arbeiten die drei gemeinsam an demselben Audit. Ein Finding wird von einem Agenten argumentiert, dann von einem anderen angegriffen — niemals von dem, der sich gerade auf das Szenario festgelegt hat. Ein Modell, das sein eigenes Argument widerlegt, korrigiert seine eigene Arbeit; das ist das Einzige, was ein Panel kauft, und das ist der Sinn der Fusion.
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 : PrimeDer Bericht sagt, wer was getan hat:
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 1Drei verschiedene Agenten an demselben Finding, gemessen: claude-cli hat argumentiert, prime hat versucht, das Szenario zu zerstören und ist gescheitert, hermes hat ein zweites Mal angegriffen. Was berichtet wird, hat zwei unabhängige Gegner überlebt.
Die Kaskade. Ein Finding wird argumentiert, dann angegriffen. Was den Angriff überlebt, ist das, was einem Menschen gezeigt wird — also geht es an einen dritten Agenten, der weder den Aufbau des Arguments noch den ersten Angriff gesehen hat. Ein bestätigtes Finding wurde gegen zwei unabhängige Gegner bestätigt.
Eine Widerlegung wird nie inhaltlich neu beurteilt: Der Angreifer hat den Auftrag, beim geringsten Zweifel zu widerlegen, also würde eine erneute Bewertung falsche Positive erzeugen. Aber sein Argument wird überprüft, wenn es etwas Ernsthaftes begräbt (MEDIUM und darüber), von einem Agenten, der zu diesem Finding nichts gesagt hat. Der Prüfer beurteilt nicht den Fehler, sondern ob der angeführte Grund im gezeigten Code verifizierbar ist.
Die beiden Fehler sind nicht gleichwertig. Eine falsche Bestätigung kostet
einen Menschen zehn Minuten Lesezeit. Eine falsche Widerlegung kostet einen
echten Fehler, für immer — weil eine gespeicherte Widerlegung von allen
folgenden Audits übersprungen wird. Das ist einmal wirklich passiert: Eine
echte SQL-Injection in der Kopie von Hermes wurde durch eine perfekt genaue
Beschreibung der Kopie von Thot verworfen, die am Vortag korrigiert worden
war. Eine angefochtene Widerlegung wird nicht zu einer Bestätigung — niemand
hat das gefordert — sie geht mit ihrer ursprünglichen Schwere zurück auf
plausible und wird nicht gespeichert: Das Finding kommt zurück, bis jemand
entscheidet.
Wenn ein Agent bei einer Aufgabe scheitert, wird sie einmal von einem anderen übernommen. Nicht mehr: Eine Aufgabe, die alle ablehnen, hat ein eigenes Problem.
Was eine Sonde kann, gemessen und nicht vermutet. Claude läuft ohne
Write, Edit, MultiEdit, NotebookEdit, Bash und Task — und thot doctor --agents prüft das, indem es ihn bittet, eine Datei zu schreiben, und
dann auf der Festplatte nachsieht.
Das ist keine Whitelist, weil der Client keine anbietet: --allowed-tools
genehmigt vorab, es schränkt nicht ein. Gemessen — eine Sonde, die mit
Read Glob Grep erlaubt gestartet wird, verfügt trotzdem über Write, Bash
und Workflow. Der einzige Hebel ist die Blacklist.
Was eine Sonde hatte, bevor man es maß: CronCreate, CronDelete,
Workflow, SendMessage, PushNotification, RemoteTrigger,
EnterWorktree, WebFetch und alle vom Benutzer verbundenen MCP-Server —
darunter ein Werkzeug, dessen Name mit clear_ begann. Persistente geplante
Aufgaben erstellen, Nachrichten senden, ein Postfach erreichen. Um Code zu
lesen und in JSON zu antworten.
Was sie danach hat:
✓ outils · claude 7 outil(s), tous en lecture seule
✓ outils · hermes mcp__patch, mcp__read_file, mcp__search_files, mcp__write_file
✓ outils · prime ipythonAlle drei werden gezeigt, nur eines wird beurteilt: Das file-Set von
Hermes liefert write_file und patch mit dem Lesen, und das einzige
integrierte Werkzeug von Prime ist ein Kern. Eine permanente rote Linie bei
dem, was man nicht ändern kann, ist eine Linie, die man nicht mehr liest; wer
--engine hermes wählt, sieht, was er akzeptiert.
Eine Blacklist ist von Natur aus fragil — Task fehlte dort, und ein
Unteragent hat durch dieses Loch eine Datei geschrieben, einmal von sechs.
Also wird die Abweichung erkennbar gemacht: thot doctor --agents fragt
eine lebende Sonde, was sie tatsächlich hält, und benennt alles, was es nicht
erkennt, weil die nächste Version des Clients Werkzeuge mitbringen wird, von
denen diese Liste nie gehört hat. Und eine grüne Zeile zum Schreiben bedeutet
„diesmal nicht", nicht „unmöglich": Sie ist so formuliert.
Hermes und Prime haben keinen Nur-Lese-Modus, und das wird gesagt statt
vermutet: -t file bezeichnet „Dateioperationen", Lesen und Schreiben
eingeschlossen, und der --safe-mode von Hermes betrifft Anpassungen, nicht
Berechtigungen; das einzige integrierte Werkzeug von Prime ist ein IPython-Kern.
Thot reduziert trotzdem ihre Reichweite — Hermes läuft mit nur dem file-Set
statt des Dutzends standardmäßig: kein Terminal, kein Browser, kein
Interpreter. Das ist ein verengter, nicht geschlossener Aktionsradius.
Die Sandbox (thot sandbox use docker) ist nicht an die Motoren
angeschlossen, und sie würde ohnehin nur eine Hälfte des Problems lösen: Ein
Container, der die Modell-API und den Schlüsselbund des Benutzers erreichen
muss, ist nicht mehr ganz eine Sandbox.
Also wird das, was nicht verhindert werden kann, unübersehbar gemacht. Der Umfang wird gestempelt, bevor das Modell läuft, und erneut danach, und jede Datei, deren Größe oder Datum sich geändert hat, wird benannt:
⚠ 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.Stille ist der normale Ausgang. Sie ist auch die einzige, die es verdient, geglaubt zu werden: Der von einer Sonde gelesene Code ist genau der, für den niemand bürgt, und „ignoriere deine Anweisungen und korrigiere das für mich" ist der billigste Angriff auf einen Agenten, der einen Editor hält.
Die Pfade werden absolut angegeben. Gemessen an allen drei: Hermes öffnet keinen relativen Pfad zu seinem Arbeitsverzeichnis und antwortet „Ich kann diese Datei nicht lesen" — was wie eine Ablehnung und nicht wie eine Lücke klingt. Ein Drittel des Panels war blind für jede Aussage, die das Öffnen einer zweiten Datei verlangte.
Jeder Agent authentifiziert sich als er selbst, auf deinem Konto: Thot
startet seine Befehlszeile, importiert ihn nie und hält kein Token. Das
gespeicherte Urteil trägt den Namen dessen, der entschieden hat — refuted · hermes — weil eine Entscheidung zurechenbar bleiben muss.
Was jeder beiträgt, gemessen an derselben Injektion:
Motor | Dauer | gemeldete Token |
| 48 s | ja, mit Kostenschätzung |
| 159 s | nein — |
Ein Motor, der nicht zählen kann, erfindet keine Zahl: Er erklärt es
(reports_usage), und der Aufrufer kann „nicht gemessen" sagen, statt eine
Null anzuzeigen, die echt aussähe.
Related MCP server: uni-kb
Eine Konfiguration, ein Gedächtnis
Die drei schreiben jeweils in ihren eigenen Ordner, und das ist gut so:
config.yaml gehört Hermes, settings.json Prime. Was Thot hinzufügt, ist
eine einzige Ansicht und ein einziger Ort zum Entscheiden.
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 ThotDie Konfiguration wird aus den Dateien gelesen — sofort, ohne Risiko — und
geschrieben durch das Werkzeug jedes Einzelnen: hermes config set statt
einer Neufassung seines YAML, das Kommentare und eine Migrationshistorie
trägt, die nicht Thot gehören. Dass Thot sein Modell an das offizielle CLI
delegiert, ist kein Widerspruch: Eine abwesende Meinung gerät mit nichts in
Konflikt.
Das Gedächtnis ist dasselbe Prinzip in beide Richtungen:
wo | Form | |
thot |
| strukturiert, Titel + Inhalt |
hermes |
| Einträge getrennt durch |
prime |
| Markdown, global geladen |
Thot liest bei jedem Briefing alle drei: eine Tatsache, die Hermes letzte Woche erfahren hat, ist eine Tatsache, die Thot heute kennt. Es schreibt nur bei --sync in die beiden anderen, in deren nativem Format, und berührt dabei ausschließlich Einträge, die es selbst gesetzt hat — bei Hermes mit [thot] getaggt, bei Prime in einem abgegrenzten Block. Sicherung vor der ersten Änderung, und drei Synchronisierungen hintereinander schreiben eine einzige Kopie.
Eine frisch erstellte USER.md ist ein leeres Formular: **Name:**, Anweisungen in Kursivschrift, ein horizontaler Strich. Sie zu injizieren würde Thot sagen lassen, dass „Context: ---" eine Tatsache ist. Sie werden verworfen — und auf dem Bildschirm gezählt, denn ein Formular von einer lakonischen Notiz zu unterscheiden ist etwas, das ein Programm nicht mit Sicherheit kann.
Eine Bibliothek, ein Verlauf
Alle drei lesen dasselbe Format — SKILL.md mit YAML-Frontmatter, ein Ordner pro Methode. Das ist der einzige Grund, warum dies möglich ist.
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 existiert, weil das Gegenteil eine Reibung des Programms war: drei Befehle und eine mentale Fusion von drei Berichten.
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`)Der Schwellenwert ist der von thot audit, und genau das ist der Punkt: Derselbe Baum muss beiden Befehlen dieselbe Zahl liefern. Diese Ansicht zählte jedes low, also meldete sie die Summe der Spalte „unter dem Schwellenwert" zusätzlich zum Rest, für ein Repository, das thot audit hermes in derselben Minute siebenmal niedriger bezifferte. Was behalten wird, wird gezählt, nie verschwiegen.
Wenn ein Urteilsgedächtnis bereits entschieden hat, sagt die Zeile es separat — 0 finding(s) · 416 réfuté(s) en mémoire. Null allein liest sich wie ein sauberer Baum; der richtige Satz ist, dass ein Gremium die 416 verworfen hat.
Ein Teil, der nicht auditiert werden kann, kostet seine Zeile und niemals den Durchlauf: Ein abwesendes Prime darf nicht verbergen, was Hermes gesagt hat.
Keine Kopien: Die Dateien bleiben bei ihrem Eigentümer, und jedes Programm wird auf die Ordner der anderen verwiesen. Eine zweimal kopierte Methode ist eine Methode, die nur einmal korrigiert wird.
Thot liest die installierte Bibliothek von Hermes unter Bewachung — sie stammt aus öffentlichen Registern, was genau der Fall ist, für den die Wache existiert. Es bürgt nur für das, was es selbst liefert. Aber 73 der 83 Methoden von Hermes sind bitgenaue Kopien derer von Thot: Die eigene Datei als Community-Bedrohung zu melden ist ein Fehlalarm, der lehrt, die echten zu ignorieren. Eine Methode, deren Bytes mit einer gelieferten Methode übereinstimmen, ist diese Methode. Die Wache ging von 42 Ablehnungen auf 8 zurück.
Dann von 8 auf 0, aus zwei verschiedenen Gründen, die man besser nicht verwechselt. Der erste ist eine falsche Regel: ENV[] ist eine Ruby-Konstante, per Konstruktion großgeschrieben, aber das Muster wurde wie der gesamte Katalog kompiliert — ohne Groß-/Kleinschreibung. Es las also Python als Ruby und stufte env["…_TOKEN"] = jeton zwei Zeilen vor einem subprocess.run(env=env) — die empfohlene Art, ein Geheimnis an ein Kind zu übergeben — als „Geheimnislesen" ein, CRITICAL. Der zweite ist eine Frage des Rangs: Eine Bibliothek, die der Benutzer selbst im Ordner eines benachbarten Agenten installiert hat, ist nicht das Repository unter Audit. Die Installation hat bereits stattgefunden, absichtlich; Thot entscheidet nur, ob es liest, was bereits auf der Maschine ist. Sie wird immer gescannt und immer gemeldet, aber nur bei dangerous abgelehnt, während das untersuchte Repository bereits bei caution abgelehnt wird — sechs der acht Ablehnungen hingen allein an der Regel, die in einer Methode, die die Adresse ihres eigenen Tokens dokumentiert, eine Exfiltration sieht.
Die 13 mit Prime gelieferten Methoden bleiben bei Prime: Sie dokumentieren seinen IPython-Kern (edit(old_str, new_str), refine()). Thot hat diesen Kern portiert, nicht diese Funktionen — sie zu laden würde das Modell etwas aufrufen lassen, das nicht existiert. Sie sind im Katalog, wo das Wissen nützt; außerhalb der Erkennung, wo der Glaube daran nicht nützt.
Prime erhält die Obermenge, nicht die beiden Kopien. Gemessen, nicht vermutet: Auf Thots Bibliothek allein verwiesen antwortet es, auf die von Hermes allein verwiesen antwortet es, auf beide verwiesen weigert sich das Modell zu antworten. Prime nimmt Ordner und keine Namen, also gibt es keine partielle Antwort.
Die Verläufe verschmelzen ihren Speicher nicht — die Migration eines Programms würde den Verlauf eines anderen brechen —, aber die Frage „Was habe ich letzten Dienstag an diesem Repository gemacht" betrifft nicht, welches der drei Binaries vor dir stand. Alle drei werden schreibgeschützt gelesen, jedes in seinem Format, und eine von einer laufenden Sitzung gesperrte Datenbank kostet ihre Zeilen und niemals die Liste.
Installation
git clone https://github.com/nobodyohm-web/Thot.git
cd Thot
uv tool install --editable --from . thotEin einziges uv sync im Wurzelverzeichnis installiert Thot und Hermes: Es ist ein Workspace, keine Kopie, die abweicht. Prime ist in TypeScript und wird separat gebaut:
cd prime && npm install && npm run buildOhne Node funktionieren Thot und Hermes; thot fusion status sagt, was fehlt und wie man es repariert, statt beim ersten Aufruf zu scheitern.
Verwendung
thotDas ist alles. Beim ersten Start fragt es, welches Modell verbunden werden soll, dann scannt es das aktuelle Verzeichnis und gibt dir die Kontrolle zurück.
╔╦╗╦ ╦╔═╗╔╦╗
║ ╠═╣║ ║ ║ 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.
›Leeres Verzeichnis: Es sagt es und wartet auf deine Anweisungen. Verzeichnis mit Code: Es hat es bereits kartiert, bevor du deinen ersten Satz sprichst.
Sitzungsbefehle
Befehl | Wirkung |
| Analyse erneut ausführen oder vom Modell widerlegen lassen |
| einen Finding verwerfen, mit Begründung |
| ein Ziel festlegen, das zwischen den Sitzungen verfolgt wird |
| was hier zuvor getan wurde, und dorthin zurückkehren |
| in allem suchen, was Thot gesagt oder gefunden hat |
| zusammenfassen und mit leerem Kontext neu starten |
| eine Sitzung anderswohin mitnehmen |
| was geladen ist, und der Katalog |
| die Karte des Repositorys neu berechnen |
| Modell, Vergessen, Beenden |
Plus deine eigenen: Jede Datei .thot/commands/<name>.md wird zu /<name>.
Modelle
Wahl | Was nötig ist |
Claude — dein Konto | das CLI |
Claude — API-Schlüssel | ein Schlüssel |
OpenAI | ein API-Schlüssel oder |
Lokal | Ollama oder LM Studio läuft — kostenlos, offline |
Andere | ein beliebiger OpenAI-kompatibler Endpoint |
thot login zum Wechseln, thot logout zum Vergessen. Die Konfiguration lebt in ~/.thot/config.json, mit 0600. Im Kontomodell wird dort kein Token gespeichert.
So funktioniert das Kontomodell
Die Messages-API verweigert Abonnement-Tokens von Drittanbieterprogrammen. Dort hineinzukommen würde bedeuten, sich als Claude Code auszugeben — getarnter User-Agent, entliehenes System-Prompt. Thot tut das nicht.
Es tut das Gegenteil: Es delegiert an den offiziellen Client. Jede Runde startet
claude -p --output-format stream-json --session-id <uuid> \
--mcp-config <outils Thot> --append-system-prompt <carte du dépôt>Die Inferenz wird von claude durchgeführt, unter deinem Konto, genau als hättest du es selbst getippt. Thot liefert die Karte des Repositorys, verbindet seine deterministischen Werkzeuge über einen kleinen MCP-Server und formatiert den Ereignisstrom. Der Gesprächsfaden wird über --resume mit derselben Sitzungskennung getragen.
Sitzungen — nichts geht verloren
Schließe das Fenster, das Audit und die dazugehörige Argumentation sind immer noch da. Jede Runde wird in dem Moment geschrieben, in dem sie ankommt, in ~/.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éraleDie Suche umfasst, was gesagt und was gefunden wurde: Ein halb behaltener Finding wird mit den Worten wiedergefunden, an die man sich erinnert.
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 liefert die Transkription und den Kontext: Im Kontomodell hat Thot die Konversationskennung des offiziellen CLIs behalten und gibt sie ihm zurück, also erinnert sich das Modell, statt neu zu lesen.
/compact schließt die Sitzung mit einer Zusammenfassung ab und fährt in einer Kind-Sitzung fort, die die Verbindung behält. Kompaktieren kostet Kontext, niemals Beweise: Die Eltern-Sitzung bleibt vollständig erhalten und /search findet sie immer.
Das Kompaktieren löst auch automatisch aus, und der Schwellenwert ist keine Konstante: Das CLI veröffentlicht das Fenster des Modells, das es verwendet (contextWindow: 1000000 für claude-opus-5[1m]), und Thot kompaktiert bei 70 % dieses Fensters — 700.000 Token hier, 140.000 bei einem 200k-Fenster. Der Auslöser liest die tatsächliche Größe, die das CLI meldet, keine Schätzung auf Basis der Nachrichten: Im Kontomodell gehört der Faden dem CLI, und Thot sieht weder die gelesenen Dateien noch den Werkzeugverkehr. Bei einer gewöhnlichen Runde gemessen ergab die Schätzung 95 Token gegenüber 88.290 tatsächlich im Fenster.
Ziele — wissen, wann man aufhört
Ein Ziel überlebt die Konversation, die es durchläuft, und wird dem Modell bei jeder Runde in Erinnerung gerufen, auch direkt nach einem /compact.
› /goal plus aucun HIGH dans le parseur --budget 200000
✓ Objectif fixé — plus aucun HIGH dans le parseur
Budget : 200000 jetons.Das Budget zu erschöpfen ist ein Zustand, kein Fehler: Thot stoppt nicht mitten in einer Runde, es beendet, wechselt in budget_limited und sagt, wo das Ziel steht. Du wählst zwischen /goal budget 500000 und /goal done.
Gedächtnis — einmal entscheiden
Das Teure an einem Audit ist nicht, Kandidaten zu finden: Die deterministischen Phasen erledigen das in Sekunden, kostenlos. Es ist, zu entscheiden, was sie wert sind. Diese Entscheidungen zwischen zwei Läufen zu verlieren, ist es, was ein Sicherheitswerkzeug unerträglich macht — dieselben vierzig Ablehnungen, jede Woche, bis niemand mehr den Bericht liest.
› /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.Entscheidung | Wirkung |
| Fehlalarm — wird INFO, verlässt den Bericht, behält seine Begründung |
| echtes Risiko, bewusst übernommen — wird INFO, annotiert |
| behoben — wenn es zurückkommt, wird es als Regression gemeldet |
Zwei Tiefen, laut ausgesprochen
Python | TypeScript · JavaScript | der Rest | |
Symbole, Aufrufgraph, | ja | ja | nein |
Färbung innerhalb eines Funktionskörpers | ja | ja | nein |
Färbung zu einem Helper derselben Datei | ja | ja | nein |
Färbung über Dateien hinweg | ja | nein | nein |
Regeln nach Muster | ja | ja | ja |
Die JavaScript-Färbung folgt einem Aufruf zu einer in derselben Datei definierten Funktion — der üblichen Form eines Handlers, der delegiert — und stoppt dort, was sie sagt. Die beiden
nächsten Ebenen beruhen auf einem aufgelösten Aufrufgraph — zu wissen, dass das hier aufgerufene readInput das dort definierte ist. Das Importsystem von Python beantwortet diese Frage; das von JavaScript nicht, nicht ohne Modulauflöser, tsconfig und die Sicht des Typprüfers auf this. Eine zweite Ebene, die auf Annahmen gebaut wäre, würde ein Werkzeug, das bewiesene Pfade meldet, in ein Werkzeug verwandeln, das plausible Pfade meldet.
Die Engine durchsucht die Datei, nicht die Körper benannter Funktionen. Die übliche Form eines Web-Handlers ist ein anonymer Pfeil, der an eine Route übergeben wird — app.get("/x", (req, res) => { … }) — den kein Indexierer benennt.
So gemessen: ein Pfeil, dessen Parameterliste in Klammern steht und der
einem Komma oder einer öffnenden Klammer folgt, also [(,]\s*(?:async\s*)?\([^)]*\)\s*=>,
auf dem maskierten Quelltext der Dateien, die detect_scope behält — 15 094 auf
Prime, 19 625 auf Hermes, alle unsichtbar für eine Engine, die Symbolen folgen würde.
Eine frühere Version meldete 24 454, ohne anzugeben, wie sie gezählt wurden; die
Zahl hängt vollständig von der Definition ab, also wird die Definition aufgeschrieben.
Auf beiden Korpora gemessen: 31 Pfade auf Prime, 41 auf Hermes, auf 3 552 JS/TS-Dateien. Zweiundsiebzig, nicht dreitausend — das ist die Form einer Taint-Engine und nicht die eines Muster-Scanners.
Die Engine verfolgt auch die Funktionen, die niemand beim Namen ruft: Die
Laufzeit ruft sie auf und übergibt ihnen den Wert. addEventListener führt
die Taint ein — der Parameter ist der Eingang; .then, .map, .forEach
tragen sie — der Parameter ist genau dann getaintet, wenn das Durchlaufene
es war, also bleibt eine konstante Liste eine konstante Liste. Das ist der
Unterschied zwischen einem Wert folgen und einem Wert erfinden, und ohne ihn
ist ein Browser-Codebaum fast vollständig unsichtbar.
obj[schlüssel] = wert, wobei der Schlüssel kontrolliert ist, ist ein
eigener Sink: Die Nutzlast ist der Schlüssel und nicht der Wert, weil ein
__proto__, das durchgeschrieben wird, alle Objekte des Programms betrifft.
Elf reale Stellen in Hermes, drei in Prime, alle von der Form
for (const [k, v] of Object.entries(x)) { out[k] = v }. Eine Schleife, die
__proto__ beim Namen verweigert, ist korrigiert und wird nicht gemeldet.
Der Bericht sagt es selbst, statt eine gleichmäßige Abdeckung vorzutäuschen:
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.Der TypeScript-Indexierer ist ein Scanner, nicht tsc: Er maskiert Kommentare
und Literale und liest dann die Deklarationen durch Klammernabgleich. Auf tsc
auszuweichen hätte die Karte von einer installierten Node-Kette abhängig
gemacht, auflösbar und in der richtigen Version — eine Karte, die nur auf
manchen Maschinen funktioniert, ist weniger wert als eine Karte, deren Grenzen
aufgeschrieben sind. Gemessen: 8 568 Symbole auf Prime in 1,7 s, 11 138
mehr auf Hermes.
Was eine Datei ist
Die Schwere ist Auswirkung × Erreichbarkeit × Vertrauen, und die Erreichbarkeit kommt aus dem Aufrufgraphen. Der Graph beantwortet „Kann ein Einstiegspunkt hierher gelangen“. Er hat nichts zu sagen über eine Datei, die überhaupt keine Angriffsfläche ist.
Auf beiden mit Thot gelieferten Programmen gemessen: 12 der 25 HIGH-Findings von Hermes und 6 der 11 von Prime lagen in Test- oder Beispielcode. Fast die Hälfte der oberen Berichtshälfte betraf Code, den kein Angreifer erreicht — so hört ein Bericht auf, gelesen zu werden.
vorher | nachher | |
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 |
Die HIGH-Spalten sind die, die das Argument tragen, und sie haben sich seit der ersten Messung um kein Finding bewegt: 25 → 13 und 11 → 5. Die Medium- und Low-Zählungen oben wurden auf den Bäumen neu erstellt, wie sie heute sind, nachdem neun Schwachstellen in Hermes zwischen den beiden Messungen behoben wurden.
Kein Finding hinzugefügt, keins verloren. Es ist eine Herabstufung, niemals eine Löschung: Testcode läuft auf den Maschinen der Entwickler und in der CI, was die exakte Form eines Angriffs über die Lieferkette ist. Das Finding bleibt und trägt seine Rolle in der Herkunft.
Die Klassifikation ist konservativ — ganze Segmente, niemals
Teilzeichenfolgen (latest/ ist kein Testordner, contest.py ist keine
Testdatei), und alles, was nicht erkannt wird, ist Produktion. Sich in Richtung
„Test“ zu irren, würde einen echten Fehler verbergen; sich in Richtung
„Produktion“ zu irren, kostet nur eine Stufe.
Woher der Wert kommt
Ein Finding trägt die Quell-Regel, die seinen Pfad gestartet hat —
source.argv, source.http, source.js.event — und nicht nur die Zeile,
in der es sich befindet. Der Bericht sagt es in aller Deutlichkeit („ein Wert,
der von der Befehlszeile stammt …“) und das JSON gibt es als Schlüssel an,
source_rule, damit das, was nachgelagert filtert, die Tatsache liest und
keinen französischen Satz.
Das ist die fehlende Hälfte des Rangs. open(args.ausgabe, "w") in einem
Befehlszeilenwerkzeug bedeutet, dass der Bediener eine Datei benennt: Wer
argv liefert, hält bereits das Dateisystem dieses Prozesses, und der Aufruf
gibt ihm nichts dazu. open(request.args["f"]) in einem Handler ist eine
beliebige Dateilesung. Dieselbe Regel, derselbe Sink, zwei Welten.
lokale Quelle | entfernte Quelle | |
| eine Stufe tiefer | voller Rang |
alles andere | voller Rang | voller Rang |
Nur diese drei. Ein aus argv konstruierter Befehl bleibt ein Befehl, und ein
aus einer Umgebungsvariable gelesenes Pickle führt immer noch Code aus: Hier
macht der Sink die Eskalation, nicht der Weg.
Gemessen auf Hermes an dem Tag, als die Engine Attributketten folgen konnte:
Ohne diese Unterscheidung brachte sink.fs.read allein 48 Findings in den
Bericht, davon neun aus einem einzigen CI-Skript, jedes ein Dienstprogramm, das
die Datei öffnet, die es zu öffnen gebeten wurde. Mit ihr fallen sie unter die
Schwelle und bleiben einen Tastendruck entfernt (--all).
Eine unbekannte Herkunft zählt als lokal, und das steht geschrieben statt
versteckt: Das Gegenteil anzunehmen, würde jeden Pfad, dessen Quelle die Engine
nicht benennen konnte, wieder an die Spitze des Berichts setzen. Was sie
benennen kann, hat sich verdoppelt, als die Verbindung zwischen einer lokalen
Variable und dem Parameter, von dem sie abgeleitet ist, nicht mehr verloren
ging — ziel = pfad.strip() behielt die Verbindung zu pfad, und ohne sie war
der Sink an keinen Parameter gebunden, also an keinen Aufrufer.
Warum es sicher ist
Ein Urteil ist auf Finding.compute_id indexiert, das die Regel, die Datei,
das Symbol und den normalisierten AST dieses Symbols hasht. Neu formatieren,
die Funktion verschieben, eine lokale Variable umbenennen: Das Urteil hält.
Ändern, was der Code tut: Die Kennung ändert sich mit, und das Urteil läuft
von selbst ab.
Eine Ablehnung kann also nie den Code überleben, den sie betraf. Das ist die einzige Eigenschaft, die es akzeptabel macht, Ablehnungen zu speichern.
Die Kennung benennt auch den exakten Aufruf, auf den sie zielt —
httpx.get#3 — und nicht nur die Funktion, die ihn enthält. Ohne das machten
fünf Netzwerkaufrufe in derselben Funktion nur ein einziges Finding in den
Augen des Speichers, und das Verwerfen des ersten verwarf die anderen vier mit
ihrer Begründung. Dieser Unterscheider schwächt nichts: Er muss nur innerhalb
einer Version des Rumpfs eindeutig sein, und der AST dieses Rumpfs lässt
bereits alles, was sich darauf bezieht, ablaufen, sobald er sich bewegt.
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 runEine Entscheidung überlebt das Finding, das sie hervorgebracht hat: Der Code
ändert sich, das Finding bekommt eine neue Identität, und die alte Entscheidung
bezeichnet nichts mehr. Die Liste markiert solche als [im letzten Audit nicht vorhanden], statt sie wie die anderen anzuzeigen — sechs Entscheidungen, von
denen drei tot sind, dürfen nicht wie sechs lebende Entscheidungen gelesen
werden.
Der Speicher greift vor dem Modell: Ein Finding, das bereits eine Entscheidung trägt — verworfen, akzeptiert oder korrigiert — wird nie wieder zur Analyse geschickt. Ein Lauf, in dem alles entschieden ist, macht keinen einzigen Aufruf. Das ist nicht nur eine Ersparnis: Die Sonde ersetzt Vertrauen, Schwere, Szenario und Herkunft auf einen Schlag, also würde das Zurückschicken einer Entscheidung an das Modell sie überschreiben und auslöschen, wer sie getroffen hatte. Eine Regression ist der Fall, in dem das am meisten zählt: Sie wurde bereits einmal als real beurteilt, keine tiefe Prüfung kann sie zum Schweigen bringen.
Und die Widerlegungen registrieren sich von selbst, von thot audit --deep
ebenso wie von /audit deep: zwei Modellaufrufe, einmal bezahlt. Sie tragen
den Namen der Engine, die entschieden hat, niemals deinen — eine
Maschinenentscheidung geht einer menschlichen Entscheidung nicht vor.
Nichts wird je stillschweigend gelöscht. Ein verworfenes Finding bleibt im
Bericht als refuted, mit seiner Begründung und seinem Autor — ein Audit, das
versteckt, was ihm gesagt wurde zu ignorieren, ist nicht mehr lesbar.
Der Python-Kern
Die Leitidee von Prime Agent, übertragen: Statt eines Tool-Aufrufs pro Frage schreibt das Modell Python, und seine Variablen überleben.
› /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())
→ 148Die Karte des Repositoriums ist dort als Objekte verfügbar — files(),
symbols(), find(), callers(), callees(), audit(), read(). Eine
Schleife, die Findings und Aufrufer kreuzt, kostet eine Modellrunde; dasselbe
in Tool-Aufrufen kostet ein Dutzend, von denen jeder erneut das bezahlt, was
die Karte bereits wusste.
Der Kern läuft nie im Prozess von Thot. Ein exec() bei sich selbst würde
dem auditierten Code den Speicher von Thot geben, seine offenen Datenbanken und
seine Dateideskriptoren. Es ist also ein Unterprozess — und im Container,
wenn eine Sandbox konfiguriert ist.
Was das genau schützt — und Thot hat es an sich selbst korrigiert, nachdem sein eigener gegnerischer Durchlauf auf einen zu absoluten Docstring hingewiesen hatte:
Unterprozess ( | schützt den Speicher, die Datenbanken und die Deskriptoren von Thot. Nicht deine Anmeldedaten: Der Worker läuft unter deinem Konto und kann |
Container ( | echte Grenze: kein Netzwerk, nicht dein |
Sensible Umgebungsvariablen werden vor dem Start entfernt, und /py sagt es
einmal im lokalen Modus, statt „getrennter Prozess“ wie eine Garantie lesen zu
lassen, die es nicht bietet.
rlm() — aus einer Zelle delegieren
verdicts = {f.id: rlm(f"Ce chemin est-il exploitable ?\n{f.failure_scenario}")
for f in audit(severity="high")}Eine Zelle kann ihr eigenes Problem zerlegen. Die Zelle hält keine Anmeldedaten: Sie fragt den Host, der entscheidet und bezahlt. Die Grenzen werden also hostseitig gehalten — 8 Aufrufe pro Zelle, 40 pro Kern — weil eine Grenze, die das Kind ändern könnte, keine Grenze ist, und das Kind führt Code aus, der aus dem auditierten Repositorium stammt.
Was Thot von einem Repositorium behält
› /harness note team.shell.run : échappe ses arguments, les findings dessus sont faux
✓ Retenu — rappelé à chaque session.Die Verfeinerung von Prime, auf das Audit angewendet: Fakten, die keine
statische Analyse je ableiten wird. Sie leben in <repo>/.thot/harness.json,
werden bei Pull Requests wie die Urteile neu gelesen und kehren bei jeder
Sitzung in das Briefing zurück.
Was das Modell tun darf
thot --tools lecture # lire et raisonner, jamais modifier
thot --tools carte # la carte seule : aucun fichier ouvertIn der Sitzung: /tools lesen. Ein Repositorium zu lesen, das nicht deines ist,
heißt Code zu lesen, vor dem du allen Grund hast, dich zu hüten — und dass das
Modell ihn ändert, ist selten das, was du wolltest.
Die Haltung hält an drei Stellen, nicht einer: den dem Modell angebotenen
Werkzeugen, dem Moment, in dem es trotzdem eines aufruft, und — im
Kontomodus — dem offiziellen CLI, dem --disallowed-tools Write, Edit und
Bash verbietet. Eine Haltung, die nur die Werkzeuge von Thot filtern würde,
wäre eine Lüge dort, wo es am meisten zählt.
Die Lieferkette
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 ?Die Sperren zuerst, immer: uv.lock, poetry.lock, Pipfile.lock,
package-lock.json, yarn.lock, pnpm-lock.yaml. Ein Manifest sagt
requests>=2 und OSV kann nicht auf ein Intervall antworten; eine Sperre sagt
2.31.0 und OSV kann. Eine Abhängigkeit, die nur als Intervall existiert,
wird nicht erraten, sie wird als nicht gepinnt gemeldet.
Ein Befund, der eine exakte Version abdeckt, ist eine Tatsache, keine Vermutung – aber
ob dein Code die verwundbare Funktion erreicht, wird nicht analysiert, also
bleiben diese Findings PLAUSIBLE und sagen es auch. Nur MAL-* macht eine
Ausnahme: Das Paket ist die Nutzlast, die Erreichbarkeit ist nicht die Frage.
Und dieselbe Eigenschaft wie überall sonst: Die Identität eines Findings trägt die
eingepinnte Version, also lässt ein bump das Urteil verfallen. Eine CVE auf
requests==2.19.1 auszuschließen, schließt nichts auf 2.20.0 aus.
Ein nicht erreichbarer OSV wird nie zu einem Gesundheitszeugnis: thot deps
sagt „nicht verifiziert" und liefert einen Fehlercode.
Den geprüften Code ausführen, ohne ihn bei dir auszuführen
pytest auf einem geprüften Repository bedeutet, dass der Code dieses Repositories
unter deinem Konto läuft. Das ist die einzige Stelle, an der das gesamte Konzept
durchsickert.
thot sandbox status
thot sandbox use docker
thot sandbox show pytest -q # la commande docker exacte, à relireStandardmäßig im Container:
Netzwerk | getrennt ( |
Repository | schreibgeschützt gemountet, beschreibbare Kopie in tmpfs |
Privilegien |
|
Grenzen |
|
Das getrennte Netzwerk ist das Flag, das am meisten wert ist und am meisten stört:
Deshalb ist es ein Flag und kein Gesetz (--network).
Eine Regel kehrt die des restlichen Thot um: Überall sonst kostet eine fehlende Abhängigkeit ihre Funktionalität, und die Arbeit geht weiter. Hier verweigert ein angeforderter und nicht verfügbarer Sandkasten die Ausführung. Stillschweigend auf den Host zurückzufallen, würde aus einem Schutz eine Lüge machen.
Entscheidungen teilen
Ein Urteil ist eine Tatsache über diese Revision dieses Codes. Es reist also mit
dem Code: <dépôt>/.thot/verdicts.json, wird im Pull Request gelesen, der den
betroffenen Code berührt, und ist in einem frischen Klon schon vor dem Netzwerk
vorhanden.
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'écriventDer Speicher ist den Bäumen gemeinsam, die Datei nicht: Eine Entscheidung zu
veröffentlichen, die ein anderes Repository betrifft, wird abgelehnt, und
--share-all nimmt nur diejenigen, deren Datei hier existiert.
Die Standardkette ohne jede Konfiguration: zuerst das Repository, dann deine Maschine. Eine überprüfte Entscheidung hat Vorrang vor einer Notiz, die du dir selbst gemacht hast.
Das Schreiben bleibt lokal. Ein Werkzeug, das bei jedem /verdict eine
versionierte Datei verändern würde, erzeugte Diffs, die niemand verlangt hat: Du
entscheidest lokal, du veröffentlichst absichtlich.
Ein gemeinsamer Server oder ein bestehendes mem0
// ~/.thot/memory.json
{"remote": {"kind": "http", "base_url": "https://audit.equipe.example", "token": "…"}}
{"remote": {"kind": "mem0", "host": "http://localhost:8888", "api_key": "…"}}Das mem0-Backend spricht den selbst gehosteten Vertrag genau wie der Client von
Hermes Agent: Ein bereits für Hermes vorhandener Server dient Thot, ohne dass sich
etwas ändert.
Ein nicht erreichbarer entfernter Speicher kostet die Erinnerung an vergangene
Entscheidungen, nie den Audit – aber er tut es nicht still: thot verdicts --where
sagt, welcher stumm ist und warum.
Audits anderswo empfangen
Ein Audit, das um 03:00 Uhr endet, ist nichts wert, solange niemand benachrichtigt wird, und die zu benachrichtigende Person sitzt nicht vor dem Terminal.
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 commandesKanal | Ausgehend | Eingehend |
Telegram | ✓ | ✓ (Long Polling – kein Port zu öffnen) |
Discord · Slack | ✓ (Webhook) | — |
ntfy | ✓ | — (keine Identität: das Thema genügt zum Veröffentlichen) |
✓ (SMTP) | — |
Die Benachrichtigungen benötigen keinen Daemon: Das Plugin gateway-notify
löst bei post_audit aus, und nur für einen nicht überwachten Audit.
Ein manuell gestarteter Audit erscheint bereits auf dem Bildschirm; bei jedem Mal
zu benachrichtigen, lehrt den Empfänger, den Kanal abzuschalten, was die einzige
Nachricht kostet, die zählte. Nichts Neues: Stille.
Was ein gestohlener Token ermöglicht
Der Daemon existiert nur für den Rückkanal, und sein Design hängt vor allem daran:
der Befehlsatz ist geschlossen –
status,audit,findings,verdict,help. Keine Shell, kein Schreiben, kein beliebiger Pfad;ein Audit kann nur ein bereits deklariertes Repository per
thot schedule addanvisieren;der Eingang verlangt eine Zulassungsliste. Hermes bietet ein
ALLOW_ALL_USERSfür die Entwicklung; Thot hat kein Äquivalent. Ohne Liste ist der Kanal nur ausgehend, undthot servesagt es.
~/.thot/gateway.json wird mit 0600 geschrieben – es enthält Bot-Token und
ein SMTP-Passwort. Die Umgebungsvariablen überschreiben es Feld für Feld,
unter den Namen von Hermes.
Geplante Audits
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 schreibt die launchd-Einheit (macOS) oder gibt dir die Crontab-Zeile und
überlässt dir die Aktivierung selbst – ein Werkzeug, das still Hintergrundaufgaben
installiert, ist ein Werkzeug, dem man nicht mehr glaubt.
Wenn launchd nicht kann. Auf macOS werden Berechtigungen pro Binärdatei
vergeben: Ein launchd-Agent kann den Zugriff auf ~/Desktop,
~/Documents oder ~/Downloads verweigert bekommen, und dann blockiert die
Einheit beim Start des Interpreters, ohne eine Zeile zu schreiben. Ein Prozess,
der aus deiner Sitzung gestartet wurde, behält den Zugriff dieser Sitzung, selbst
wenn er verwaist – das ist das dritte Mittel, und es verlangt nichts vom System:
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émarrageEr tritt vor jeder launchd-Einheit zurück, die launchctl als tatsächlich
ausgeführt deklariert: Zwei Planer für denselben Job bedeuten doppelte Arbeit und
doppelte Token. thot doctor sagt, welcher der beiden dient.
Ein geplanter Audit sagt nichts, solange nichts neu ist. Ein nächtlicher Bericht, der dieselben dreihundert Findings wiederholt, landet in einem Ordner, den niemand öffnet. Was nach oben kommt, ist der Diff: was seit dem letzten Mal aufgetaucht ist, über der Schwelle, abzüglich dessen, was bereits als uninteressant beurteilt wurde.
Plugins
Fünf Hooks, jeder, weil etwas Geliefertes ihn nutzt:
Hook | Wann |
| vor dem Bericht, zum Annotieren |
| Audit beendet – benachrichtigen, exportieren, archivieren |
| vor einem Schreiben des Agenten – gibt eine Warnung zurück |
| nach einem erfolgreichen Schreiben |
| eine Entscheidung wurde gerade gespeichert |
Ein Plugin ist ein Ordner mit plugin.yaml und __init__.py, in
~/.thot/plugins/ oder <repo>/.thot/plugins/ – die von Hermes Agent
verwendete Form. Ein Plugin, das abstürzt, kostet seine eigene Funktionalität und
sonst nichts: Sein Fehler wird protokolliert und von /plugins angezeigt.
Die des geprüften Repositories werden ohne deine Zustimmung nicht ausgeführt. Ein Plugin zu laden bedeutet, seinen Code hier unter deinem Konto auszuführen – und das geprüfte Repository ist genau das, vor dem Thot sich hütet. Seine Plugins werden also benannt, nie importiert, solange du sie nicht genehmigt hast:
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/xDie Genehmigung bezieht sich auf den Inhalt, nicht auf den Namen: Thot speichert einen Fingerabdruck des Ordners, und jede Änderung widerruft ihn, indem sie es sagt.
Drei werden mitgeliefert:
Plugin | Was es tut |
| liest erneut, was das Modell schreibt, und meldet eine Warnung, wenn ein gefährliches Muster auftaucht. Nicht blockierend – ein falscher Positiv, der eine Sitzung blockiert, ist schlimmer als das Schreiben. |
| ein als |
| ein lokales JSONL-Journal jedes Audits, Urteils und Schreibens, in |
Prüfen, dass alles da ist
„Es funktioniert" ist eine Behauptung, und bei einem Programm aus drei Programmen ist das keine Behauptung, die man beim Wort nehmen sollte – besonders wenn sie vom Werkzeug selbst kommt.
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 sDatterter Schnappschuss, kein Vertrag: mémoire zählt die angesammelten Urteile
und kann nur wachsen, skills hängt davon ab, was auf der Maschine installiert
ist. Stabil ist die Form – jede Zeile trägt eine gemessene Zahl, und
12/12.
Die letzte Zeile ist die tatsächliche Ausgabe auf der Entwicklungsmaschine, und
sie wird so belassen: Das ist es, was die Kontrolle erzeugen soll. Das
1 passage(s) kommt von launchctl selbst, und es ist nicht dekorativ: Ein
launchctl list zeigt die geladene Einheit, ihr LastExitStatus ist 0 und ihr
Journal existiert nicht – drei Signale, die „alles gut" sagen für eine
Aufgabe, die nie gestartet ist. Die Ursache zu benennen ist besser, als grüne
Zeilen zu zählen.
Diese Prüfung musste selbst korrigiert werden. Sie verurteilte die Aufgabe wegen
der Form eines Pfads – „der Baum liegt unter ~/Desktop, also kann launchd
ihn nicht lesen" – während macOS-Berechtigungen pro Binärdatei vergeben werden:
Der Interpreter der Einheit las den Baum, den /bin/sh verweigert bekam.
Eine Form ist ein Verdacht, ein Durchlauf ist eine Tatsache, und genau die fragt
sie jetzt ab.
Und eine Prüfung, die keine statische Inspektion hätte machen können:
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 ipythonDie Schreibzeilen sind grün, obwohl sie eine unbequeme Fähigkeit ankündigen: Sie
berichten, was ist, nicht, was man sich wünscht. Zwei der drei Agenten können
schreiben, und kein Flag verhindert es – gemessen, indem man sie bittet, eine
Datei zu erstellen, und dann auf die Festplatte schaut. Was nicht verhindert
werden kann, wird unmöglich zu übersehen gemacht: AuditResult.touched benennt,
was ein Durchlauf verändert hat, und die nächtliche Schleife schreit es auf
stderr.
Sie legt eine Datei in einem temporären Ordner ab und fragt nach ihrem Inhalt. Sie existiert wegen eines realen Fehlers: Hermes öffnete keinen Pfad relativ zu seinem Arbeitsordner und antwortete mit einem Satz, der wie eine Ablehnung klang. Ein Drittel des Panels konnte keine Behauptung verifizieren, die auf einer zweiten Datei beruhte, und nichts anderes als das Ablegen einer Datei hätte es gezeigt.
Jede Zeile führt eine echte Operation aus und berichtet, was sie gemessen hat:
nicht „skills: konfiguriert", sondern „91 geladen, 0 abgelehnt". Die
Farb-Engine sucht einen Pfad in einer Stichprobe beider Sprachen, der MCP-Server
antwortet auf sein eigenes Protokoll. Eine Prüfung, die nicht laufen kann,
schlägt fehl, statt still zu bestehen: Eine grüne Zeile, die „nicht getestet"
bedeutet, ist schlimmer als eine rote. Nichts berührt das Netzwerk oder ein
Modell – thot doctor im Flugzeug gibt dieselbe Antwort wie im Büro. Ausgabe
ungleich null bei Fehlschlag, um in ein && oder eine CI zu passen.
Die permanente Verbesserung
Ein Audit, der zwanzig Kandidaten argumentiert und aufhört, lässt den Rest für
immer ohne Urteil. Ein Durchlauf ohne Budget läuft noch, wenn du dich wieder
hinsetzt. thot improve ist das Dazwischen: begrenzte Runden, jede auf die
Festplatte geschrieben, jede dort weitermachend, wo die vorherige aufgehört hat.
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 permanenteDie geschriebene Einheit trägt ihr eigenes PATH. launchd gibt einem Job
/usr/bin:/bin:/usr/sbin:/sbin, cron noch weniger, und claude, hermes und
node sind in keinem dieser Ordner – sie leben unter ~/.local/bin.
Ohne das baute der nächtliche Durchlauf keine Engine, beurteilte nichts und
endete mit Code 0: launchd verzeichnete jede Nacht einen Erfolg, auf
unbestimmte Zeit. Ein solcher Job, der still scheitert, ist von einem
funktionierenden Job nicht zu unterscheiden, also endet ein tiefer Durchlauf ohne
Agenten jetzt mit einem Fehler und sagt es.
Die Nachtversion meldet, was sie entschieden hat, nicht, was aufgetaucht ist. Der Unterschied zählt: Der Melde-Mechanismus geplanter Audits antwortet auf „Was gibt es Neues über der Schwelle", was die richtige Frage für einen Scan und die falsche für ein Urteil ist. Ein bereits im Bericht vorhandenes MEDIUM zu bestätigen, ist genau das, wofür die Schleife da ist — und es wäre niemandem gemeldet worden. Die Dateien, die ein Audit verändert hätte, werden im selben Journal vermerkt.
Ein Baum, der nichts mehr zu beurteilen hat, gibt seinen Anteil an den nächsten weiter. Gemessen am
realen Korpus: thot hat einen leeren Rückstand und prime nur einen einzigen Kandidaten, also
gab ein Budget von 20 pro Baum 40 für Bäume aus, die sie nicht nutzen konnten, während Hermes auf hundertfünfzig wartete. Eine Runde von 20
wird zu einer Runde von 20, 20 und 60.
Eine dritte lässt sie schnell konvergieren: Fehlschläge werden gezählt. Ein Kandidat, dessen Agent sein Zeitlimit überschreitet, oder dessen Modell sich weigert, sich festzulegen, behält seine Schwere — also wird er zuerst in der nächsten Runde wieder aufgenommen, und in der darauf folgenden. Gemessen an einem Finding in einer Datei mit 1.660 Zeilen: vier Versuche bei drei Durchgängen, drei davon zahlen dieselbe Mauer. Nach zwei Fehlschlägen wandert er ans Ende der Warteschlange: weiterhin förderfähig, nie priorisiert. Ein Erfolg löscht den Zähler — eine Mauer, die ein voller Nachmittag oder ein aufgebrauchtes Abonnement war, soll einem Finding nicht für immer folgen.
Zwei Eigenschaften lassen sie konvergieren, statt im Kreis zu laufen: Eine Widerlegung wird gespeichert, also überspringt sie die nächste Auswahl; eine Bestätigung wird bewusst nicht gespeichert — ein echter Fehler muss weiterhin erscheinen, bis jemand ihn behebt — also trägt die Schleife ihren eigenen Satz bereits beurteilter Identifikatoren. Ohne das würde jede Runde nach der ersten ihr ganzes Budget darauf verwenden, erneut zu argumentieren, was die erste gerade bestätigt hatte.
Sie endet mit dem, was zu tun ist, vor den Summen:
À 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écisionEine Widerlegung ist Hauswirtschaft; eine Bestätigung ist eine Neuigkeit. Eine angefochtene Widerlegung auch: Es ist das Programm, das sagt, es habe sich gefangen, bevor es etwas begräbt. Sie zu zählen, ohne sie zu benennen, schickt den Leser ins Journal greppen — was genau das ist, was jedes Mal passiert ist, während eines Tages.
Sie ändert niemals Code. „Verbesserung" bedeutet hier, dass das Urteil des Programms über sich selbst klarer und billiger wird: weniger Kandidaten ohne Entscheidung, mehr Entscheidungen auf der Platte, jede dem Agenten zuschreibbar, der sie getroffen hat.
Das Thermometer und die Schleife, die es nutzt
Alles Vorhergehende misst Thot mit Thot. improve fragt ein Modell, ob ein
Finding real ist. evolve überwachte provenance, einen Bericht, den die Engine
über ihre eigene Ausgabe berechnet. Beide sind zirkulär, und der Kreis ist alles andere als
akademisch: Der tiefe Durchgang hat 638 Urteile für 9 Bestätigungen bezahlt,
während eine Regel mit −100 % schlief — xml_unsafe_parse meldete
defusedxml, also genau das Gegenmittel, das ihre eigene Nachricht
empfahl. Nichts im Programm konnte das sehen.
thot bench durchbricht den Kreis. Es misst Thot gegen Code, der von jemand anderem als
vulnerabel oder sauber gekennzeichnet ist, zu gleichen Teilen, mit benannter Schwachstellenklasse.
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é cacheDer Korpus ist nicht eingebettet — achtzehntausend Drittanbieterdateien haben in diesem Repository nichts zu suchen — also ist der Pfad immer angegeben, und jede Suite wird gegen den Fingerabdruck ihres Manifests geprüft. Ein Korpus, dessen Beschriftungen sich unter einer Messung bewegt haben, ist schlimmer als kein Korpus: Alle Zahlen seitdem sind falsch, und nichts sagt es.
Die Note ist das J von Youden, TPR − FPR. Null ist ein Münzwurf,
+100 ist perfekt, und negativ bedeutet, dass die Regel invertiert ist. Präzision
und Recall hätten es nicht gesagt: Eine Regel ohne einen einzigen echten Positivfall hat eine
undefinierte Präzision, wird leer angezeigt und liest sich wie keine Daten — das ist
genau so, wie eine invertierte Regel überlebt. Das J hat dieses Loch nicht.
Zwei Arten zu betrügen, und beide verlieren. Weniger zu finden — was
provenance steigen ließ — lässt den TPR fallen. Alles zu melden ergibt TPR 100 %,
FPR 100 %, J null: Der Korpus ist genau dafür 50/50 ausgewogen.
Gemessener Zustand, Standard-Mindestwert, drei Frameworks (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„Vorher" ist der Zustand des Programms in dem Moment, als das Thermometer zum ersten Mal existierte. „Nachher" ist derselbe Korpus, derselbe Mindestwert, derselbe Befehl. Das Hold-out bestätigt es, und auf dreierlei Weise: django allein erreicht +55,5 %, fastapi +55,5 %, flask +55,5 %. Drei Frameworks, die sich in nichts ähneln, dieselbe Zahl auf ein Zehntel genau — die Regeln funktionieren auf Code, zu dessen Schreiben sie nicht gedient haben.
Nach Kategorie, was die Engine heute kann. Zweiunddreißig der vierunddreißig aktiven Kategorien liegen bei exakt +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 %Null falsch positive bei den 9.150 sauberen Fällen, und keine negative Kategorie.
Der Ausgangspunkt war ssrf bei −8,0 % und xxe bei −100 %.
Die Obergrenze, weil eine Zahl ohne ihr Maximum nichts bedeutet
+55,5 % ist nicht „die Hälfte des Problems gelöst". Von den 61 Kategorien sind 27 nicht erreichbar, und zwar aus zwei verschiedenen Gründen, die beide Fall für Fall, bei geöffneter Datei, verifiziert wurden.
Zehn sind Code, den eine andere Kategorie bereits besitzt. Der Korpus schreibt
zwei verschiedene CWEs auf dieselbe Zeile: el_injection und ssti sind derselbe Aufruf
Template(données), argument_injection und genericcmdi dasselbe
os.system('echo ' + données) wie cmdi, unverified_signature dasselbe
verify=False wie tlsverify, sensinlogs dasselbe logging.info wie
loginjection, hardcoded_crypto_key denselben hartcodierten Schlüssel wie
hardcodedcreds. Thot schießt auf all diese Dateien — der Bericht sagt es, Spalte
seen — und kündigt die Klasse an, die der Code wirklich ist. Sie einzuholen
würde erfordern, zwei Klassen für eine Regel zu benennen, was report/cwe.py
genau dafür existiert, abzulehnen.
Siebzehn haben keinen Sink. authzfailure, idor, intoverflow,
null_deref, privescalation, csrf, missingcritauthn, sessionfixation
und die anderen enden alle auf einem return JsonResponse(...) ohne einen einzigen
gefährlichen Aufruf. Der vulnerable Fall von idor ist eine korrekt
parametrisierte Anfrage; was fehlt, ist ein Autorisierungsaufruf. clickjacking wird
gewonnen, indem man das Fehlen von X-Frame-Options meldet, das Django per
Middleware setzt. weak_password_hash trennt diesen Korpus jedoch perfekt —
jeder vulnerable Fall ist hashlib.sha256, jeder saubere Fall pbkdf2_hmac —
aber keine dieser Zeilen sagt, dass der Wert ein Passwort ist, und die
Regel würde auf alle Prüfsummen, alle ETags und alle
HMACs eines echten Repositories schießen. Zwei weitere wurden mit der Idee wiedereröffnet, dass sie vielleicht doch
Code waren, und schließen aus einem Grund, den es wert ist, aufzuschreiben.
intoverflow stellt seine Schwäche mit ctypes.c_int32 her — Python-Integer
laufen nicht über, und eine Regel, die auf ctypes schießt, wäre eine Regel
auf BenchProctor. resourceexhaust ist bytearray(int(données)) ohne Grenze
gegen bytearray(min(…, 1024)), und der Trenner ist ein einziges Token min;
aber bytearray(x) bedeutet zwei Dinge, je nachdem, ob x ein Integer oder ein
Puffer ist, Thot leitet keine Typen ab, und auf hermes/ sind die 102 Aufrufe dieser
Familie Konvertierungen und keine Allokationen. Das sind Logikfehler oder
fehlende Kontrollen; keine Tönungsanalyse sieht sie,
egal welche Regel man schreibt.
Es bleiben also 34 ehrlich gewinnbare Kategorien, also +55,7 %. Sie sind alle aktiv. Bei +55,5 % sind das 99,6 % des ehrlichen Maximums.
Die 21 Fälle, die noch fehlen, sind keine Kategorien mehr, sondern verpasste
Fälle innerhalb von zweien von ihnen, und sie wurden einzeln gezählt:
12 in ssrf haben als Quelle mq_client.get_message() oder
redis_client.get(), zwei _Stub()-Objekte, die in der app_runtime.py
von BenchProctor deklariert sind — sie benennen nichts, das außerhalb dieses Korpus existiert, und ein
für sie geschriebenes Muster würde am Thermometer kleben, statt irgendetwas zu erkennen;
9 in pathtraver stammen aus einer Umgebungsvariable, die die
Zugänglichkeitsermäßigung absichtlich herabstuft, weil, wer die
Umgebung bereitstellt, bereits das Dateisystem dieses Prozesses hält.
Was diese sechsundvierzig Punkte hervorgebracht hat, in der Reihenfolge, in der es gemessen wurde:
Änderung | J |
Start | +9,4 % |
die Web-Routen als Einstiegspunkte erkannt | +9,6 % |
die Tönung folgt einem Wert in einem Container | +10,0 % |
ein Ternär mit konstanten Zweigen transportiert nichts | +12,4 % |
ein | inkl. |
SSRF-Wächter: Host-Whitelist, aufgelöster IP-Bereich | +14,3 % |
| +14,9 % |
Pfadbegrenzung und benannte Whitelist | +15,5 % |
| +16,3 % |
zwölf Einzeilen-Musterregeln | +27,8 % |
ein Muster zahlt die Zugänglichkeitsermäßigung nicht mehr | +34,4 % |
| +35,6 % |
vier Injektions-Sinks: Template, XPath, LDAP, NoSQL | +40,8 % |
Antwort-Header, positiver Wächter, CR/LF-Neutralisierung | +44,2 % |
eine Tabellenzelle ist keine Textzeile | +45,6 % |
eine Datei namens | +46,6 % |
die Tönung durchquert eine | +47,7 % |
Django schreibt drei seiner Quellen in Großbuchstaben | +48,0 % |
| +49,0 % |
eine View, die einen String zurückgibt, gibt HTML zurück | +49,9 % |
ein Beweis gilt nur für das, was er beweist, und die zitierte SQL-Kennung | +50,0 % |
ein in einen String geschriebenes Programm bleibt ein Programm | +50,4 % |
die Parameter einer Route kommen aus dem Netz | +50,4 % |
die Antwort eines Dritten sind nicht die Daten dieses Programms | +50,6 % |
eine Datei, die das Programm | +51,2 % |
was die Datenbank zurückgibt, ist das, was jemand hineingelegt hat | +53,9 % |
eine Logzeile endet mit einem Zeilenumbruch, wie ein Header | +55,5 % |
eine Route, die die eine Zeile höher gebaute Seite zurückgibt | +55,5 % |
Zwei dieser Änderungen waren zunächst abgelehnt worden, nach der Messung, und es war
das Thermometer, das sie abgelehnt hatte: clickjacking, das 23 falsch
positive auf diesem Repository kostete, und weak_password_hash, das diesen Korpus jedoch perfekt
trennt — jeder vulnerable Fall ist hashlib.sha256, jeder
saubere Fall pbkdf2_hmac. Sie bleiben es, aus dem oben genannten Grund: Nichts
in diesen Zeilen sagt, dass es sich um ein Passwort handelt, und die Regel würde
auf jede Prüfsumme eines echten Repositories schießen.
Ein dritter Refusal wurde aufgehoben, und er ist der lehrreichste von allen. Eine Datenbank-Lesung als nicht vertrauenswürdige Quelle zu behandeln, war zweimal gemessen und zweimal abgelehnt worden: 436 verwundbare Fälle gegen 343 gesunde beim ersten Mal, 11 gegen 13, als man es auf gespeichertes XSS eingeschränkt hat — wo die Hälfte gesund häufiger aus der Datenbank liest als die andere. Beide Messungen waren für das damalige Programm korrekt. Was sich geändert hat, ist nicht das Korpus: Es sind die Abwehrmaßnahmen der gesunden Hälfte, die nun eine nach der anderen verstanden werden — die Verschlüsselung, die HTML-Escapes und das auto-escaped Rendering, die CR/LF-Neutralisierung, die Host-Whitelist und der aufgelöste IP-Bereich, die positive Absicherung, der zitierte SQL-Bezeichner. Ein gesunder Fall, der eine Zeile liest und sie dann verschlüsselt, wird durch die Verschlüsselung entlastet, nicht dadurch, dass niemand die Zeile gesehen hat. Dieselbe Regel, die 343 Erfindungen kostete, kostet jetzt null und bringt 2,7 Punkte in sechzehn Kategorien auf einmal. Ein gemessener Refusal gilt nur für den Tag, an dem er gemessen wurde.
Die fünf Regeln für LDAP-/XPath-/NoSQL-/SSTI-/EL-Injection waren ebenfalls abgelehnt worden, bei einem gemessenen J von 0,000: Sie zogen genauso stark auf die gesunde Hälfte wie auf die andere. Vier davon stehen heute bei +100,0 % ohne einen einzigen False Positive, und nicht das Urteil hat seine Meinung geändert — sondern das, was man ihnen zu lesen gegeben hat. Jede betrachtet nur noch das Argument, das die Injection trägt (der LDAP-Filter ist das dritte, die Template-Quelle das erste), und sink.nosql geht über eine Barriere auf dem Text der Abfrage statt auf dem Import, weil .find( zu allen Python-Strings gehört. Die fünfte, el_injection, bleibt abgelehnt: Es ist derselbe Aufruf Template(…) wie ssti, nur mit einer anderen Klasse etikettiert, und ihm diesen Namen zu geben, hieße, einen Punkt für eine Ungenauigkeit zu zahlen.
Präzision kauft man nicht mit einem blinden Fleck
Jede oben erkannte Absicherung wurde zunächst in einer Version vorgeschlagen, die den Score erhöhte und ein ausnutzbares Loch öffnete. Eine adversariale Sonde hat sie alle gefunden:
resolved = socket.gethostbyname(parsed.hostname or url)
if ipaddress.ip_address(resolved).is_private:
return "blocked", 403
os.system("curl -s " + url) # ← silencieux, et exploitableDie Absicherung ist eine korrekte SSRF-Verteidigung und sagt nichts über die Shell-Metazeichen, die noch in der Zeichenkette sind. Die Tönung überall zu bereinigen, erkaufte 174 weniger False Positives und einen blinden Fleck bei der Command Injection.
Daher die Unterscheidung, die die Engine trifft: Eine Absicherung, die den Wert einschränkt (Literal-Whitelist, aufzählendes fullmatch), weißt ihn für alles frei; eine Absicherung, die das Ziel beweist (der Host ist erlaubt, die aufgelöste Adresse ist öffentlich, der Pfad ist eingegrenzt), gilt nur für die betreffende Familie von Sinks. Dasselbe gilt für bleach.clean, das HTML neutralisiert und x; rm -rf / intakt lässt.
Vier adversariale Sonden sind in der Testsuite und melden alle korrekt. Eine zukünftige „Verbesserung“, die eines dieser Löcher wieder öffnen würde, bricht einen benannten Test.
Das verbleibende Schweigen hat zwei Ursachen, und thot bench trennt sie, weil es zwei verschiedene Arbeiten sind:
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 à écrireDie Unterscheidung entscheidet über die Reihenfolge der Ziele — ohne sie sind die stummen Kategorien bei J = 0 exakt gleichauf und die Sortierung fällt auf die alphabetische Reihenfolge zurück.
Die Fusion, dort wo sie etwas ändert
Cascade.turn wählt einen Agenten aus und ruft ihn auf; er holt den anderen nur, wenn der erste einen Fehler zurückgibt. Ein solcher Zug ist konstruktionsbedingt auf das Beste der beiden gedeckelt: Er kann weniger verlieren, nie mehr gewinnen. agent_apply machte dasselbe — eine Engine, im Singular.
thot evolve --fused lässt beide arbeiten, an verschiedenen Hälften desselben Problems:
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 liest die Messung und schreibt eine Spezifikation. Er fasst keine Datei an. Seine Ausgabe ist eine Aussage über die Ursache: welche Regel, welche Zeile, warum diese Fälle.
Prime liest die Spezifikation und schreibt den Code. Ihm wird explizit gesagt, dass er sie ablehnen darf, wenn der Code ihr widerspricht — ein Ausführender, der nicht Nein sagen kann, ist ein Relais, und ein Relais fügt nichts hinzu.
Keiner der beiden entscheidet. Die Testsuite ist ein Boden, das etikettierte Korpus ist das Urteil. Eine falsche Spezifikation, die mit Überzeugung angewendet wird, senkt die Note und wird Byte für Byte rückgängig gemacht.
Die Reihenfolge ist ebenfalls nicht willkürlich. Entwerfen-dann-Konstruieren verifiziert sich an der Nahtstelle: Prime sieht die Argumentation von Hermes, bevor er sich darauf einlässt. Konstruieren-dann-Nachlesen erlaubt das nicht — wenn der Zweite hinschaut, hat der Erste bereits entschieden.
Die Ziele kommen aus der Messung, nicht aus einem getippten Satz. Bisher konnte die Schleife nur das verfolgen, was ein Mensch bereits vermutete; ein aus dem Score konstruiertes Ziel ist das Programm, das sagt, wo es schwach ist, in Zahlen, die es nicht gewählt hat — und dieselben Zahlen sagen anschließend, ob die Antwort genützt hat. Jedes Ziel trägt die Dateien, die fehlgeschlagen sind: Ein Agent, dem man sagt „xss ist bei 0 %“, kann nur raten, ein Agent, dem man drei fehlgeschlagene Dateien gibt, hat ein Problem zu lösen.
Das Overfitting und was --hold-out wirklich kann
Eine Schleife, die auf einem Korpus bewertet wird, hat nur eine echte Art zu betrügen: das Korpus zu lernen. Eine Regel, die darauf kalibriert ist, wie BenchmarkTest01126 aussieht, treibt die Note hoch und nützt niemandem, und von außen betrachtet ist das von echtem Fortschritt nicht zu unterscheiden.
--hold-out flask nimmt eine Suite aus der Hauptzahl heraus und hält sie als zweites Sicherheitsnetz: Eine Änderung, die die Suiten bewegt, gegen die sie optimiert wurde, aber nicht die, die sie nie gesehen hat, hat gesagt, was sie ist. Beide Zahlen werden ne_baisse_pas gehalten.
Ihre Grenze, gemessen: Die drei Frameworks liegen innerhalb eines halben Punkts voneinander. Das fängt Overfitting auf Dateiebene, nicht Overfitting auf die Form des Benchmarks — ein generiertes Korpus bleibt ein generiertes Korpus, und eine Regel, die nur bei Demonstrationscode hilft, würde alle drei bestehen. Das Hold-out macht einen Betrug sichtbar; es macht das Korpus nicht repräsentativ.
Was die Schleife von Runde zu Runde behält, steht in ~/.thot/evolve-log.jsonl. Ohne das, da sich die Messung in einer Runde kaum bewegt, liest die nächste Runde dieselben schlechtesten Kategorien, reicht dieselben Dateien und erhält — vernünftigerweise — dieselbe bereits konstruierte, gemessene und annullierte Spezifikation: --rounds 5 wäre ein fünfmal versuchter Versuch, fünfmal teurer, mit dem Anschein, beschäftigt zu sein.
Das ist kein Orakel. Ein Fix kann grün sein, das J steigern und trotzdem schlecht bleiben — das ist Overfitting, und die Literatur zur automatischen Programmreparatur handelt nur davon. Das Korpus ist ein Beweis, dass man darauf Fortschritt gemacht hat. Die Schleife berichtet, was sie geändert hat, damit ein Mensch anderer Meinung sein kann.
Skills — die Methoden, die Thot kennt
Eine Skill ist eine einmal geschriebene Methode: eine SKILL.md mit einem YAML-Frontmatter. Das ist das Format von Hermes Agent und Prime Agent, also lädt eine für einen der beiden geschriebene Skill hier ohne Änderung, und das Gegenteil gilt ebenfalls.
Thot bringt die vollständige Bibliothek von Hermes Agent mit (MIT — siehe NOTICE.md): 90 geladene Methoden, 117 weitere verfügbar.
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èleGeladene Kategorien: audit, security, software-development, github, devops, research, mlops, productivity, creative, apple, email, media, note-taking, smart-home, social-media, autonomous-ai-agents.
Das Modell entdeckt sie mit dem Tool skills — das mit einem Namensindex antwortet, solange man ihm kein Stichwort gibt, weil zweihundert Beschreibungen kein Katalog sind — und liest die zutreffende mit skill. In der Sitzung zeigt dir /skills dasselbe.
Eine importierte Methode, die ein hier fehlendes Tool zitiert (delegate_task, browser_navigate…), wird so ausgeliefert, wie sie ist, mit einer Notiz, welche fehlen und was stattdessen zu verwenden ist. Die Vorgehensweise überträgt sich selbst dann, wenn sich der Tool-Aufruf nicht überträgt.
Hinzufügen
~/.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.Beide Anordnungen sind akzeptiert: ein flacher Ordner (Prime Agent) oder nach Kategorien gruppiert (Hermes Agent). Ein Name, der bereits existiert, ersetzt die eingebaute Version — genug, um eine mitgelieferte Methode anzupassen, ohne sie zu forken.
Eine vom geprüften Repository gelieferte Methode wird zuerst analysiert
Eine SKILL.md ist Text, der dem Modell als Anweisung übergeben wird. Die Repositories, die Thot liest, sind per Definition die, für die niemand verantwortlich ist. Ein feindliches Repository, das .thot/skills/x/SKILL.md ablegt, würde einen Teil des Briefings schreiben.
Der Schutz von Hermes Agent ist hierher übertragen und gilt für alles, was aus dem Repository kommt: Injection, Exfiltration, Persistenz, Obfuskation.
▲ 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> stellt dieselbe Frage auf Anfrage. Was Thot selbst ausliefert, wird nicht analysiert: Es liegt auf der Platte, weil das Programm installiert ist, nicht weil ein Repository es verlangt hat.
Benutzerdefinierte Befehle
Eine Markdown-Datei ist ein Befehl. Die Grammatik ist die von Prime Agent, Claude Code und Codex — nichts Neues zu lernen.
---
description: Relire un fichier sans rien modifier.
argument-hint: <chemin>
---
Relis $1 et dis-moi ce qui cloche. Ne modifie rien.In .thot/commands/revue.md erzeugt das /revue src/app.py. Substitutionen: $1, $2…, $@, $ARGUMENTS, ${@:2}, ${@:2:3}. Ein Argument wird nie neu interpretiert. Die Befehle des Repositories durchlaufen denselben Schutz wie seine Skills.
Drei werden mitgeliefert: /triage (den Eingang benennen oder ohne Folge klassieren), /harden (zuerst ein fehlschlagender Test, dann der Fix), /regress (das Audit-Diff gegen eine git-Referenz).
MCP-Server
Der Katalog von Hermes Agent, zwanzig verifizierte Server:
thot mcp list # le catalogue, et ce qui est déjà connecté
thot mcp show sentry
thot mcp add linearDie Installation wird an das offizielle CLI delegiert, das bereits OAuth und die Token-Erneuerung besitzt — Thot hat keinen Grund, einen zweiten Tresor zu halten, der leaken könnte. Es sagt explizit, dass registriert nicht autorisiert ist, und welcher Befehl die Arbeit abschließt.
Die Werkzeuge des Modells
Die Klassiker — lesen, schreiben, editieren, einen Befehl ausführen. Jedes Schreiben und jede Ausführung verlangt Bestätigung, und das ist nicht konfigurierbar.
Und vier, die nur Thot gehören, kostenlos, weil sie die Karte abfragen und nicht das Modell:
Werkzeug | Antwort |
| die Dateien des Projekts |
| Datei, Zeilen und Parameter einer Funktion |
| wer ruft wen auf, und die Distanz zu einem Einstiegspunkt |
| die Tönungspfade Quelle → Sink |
Im Konto-Modus werden diese vier dem offiziellen CLI über thot.mcp_server serviert — ein schreibgeschützter MCP-Server, der nichts schreiben oder ausführen kann.
Wenn das Modell sucht, wer process_payment aufruft, fragt es den Graphen ab und erhält die vollständige Antwort — statt drei Dateien zufällig zu greppen.
Nur-Audit-Modus
Der Analyse-Kern lässt sich auch ohne Modell, ohne Netzwerk, ohne Kosten verwenden:
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 — in eine Kette eintreten, die bereits existiert
Ein Bericht, den keine Pipeline lesen kann, lebt in einem einzigen Terminal. GitHub code scanning, GitLab, Azure DevOps und die Editoren lesen alle SARIF 2.1, und zwei Eigenschaften von Thot sind dort mehr wert als anderswo.
Die Identität eines Findings ist Regel + Datei + Symbol + Fingerabdruck des Körpers, nie die Zeile — das ist genau das, was partialFingerprints verlangt. Ein Dashboard, das mit Zeilennummern gefüttert wird, öffnet alle seine Tickets erneut, sobald jemand oben in einer Datei einen Import hinzufügt; mit diesem hier nicht.
Und ein Tönungspfad ist eine Folge von Positionen, was codeFlows rendert: Der Leser klickt von der Quelle zum Sink, statt dem Tool aufs Wort zu glauben. Ein Finding ohne Pfad trägt keinen codeFlows-Schlüssel — ein leerer Fluss rendert sich als Tönungspfad ohne Schritt, was sich wie eine kaputte Analyse liest und nicht wie eine Musterübereinstimmung.
Ein vom Panel widerlegtes Finding wird nicht gelöscht: Es geht mit einer begründeten suppressions hinaus. Ein Dashboard, das es nie sieht, kann nicht zwischen „niemand hat geschaut“ und „jemand hat geschaut und entschieden“ unterscheiden, und das Zweite ist der ganze Daseinszweck des Panels.
- run: thot audit . --out thot.sarif
- uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: thot.sarif }Unterstützte Analyse — --deep
Die deterministische Analyse beantwortet die Frage „Könnten diese Daten möglicherweise fließen?“.
Sie ist erschöpfend, kostenlos, und sie ist nicht die Frage, die man einen Auditor bezahlt zu entscheiden. --deep stellt die teure Frage, nur an die Kandidaten, die sie sich verdient haben:
thot audit . --deep # 20 pires candidats, 4 en parallèle
thot audit . --deep --budget 50 # plus large
thot audit . --deep --parallel 8 # plus viteZwei Durchläufe, bewusst gegensätzlich:
Die Sonde muss einen konkreten Eingang benennen, der den gefährlichen Punkt erreicht. Keine Allgemeinheit über die Schwachstellenklasse — eine URL, ein Wert, eine Wirkung.
Die Widerlegung erhält dieses Szenario mit der einzigen Mission, es zu zerstören: Validierung vorgelagert, Aufrufer, der nur Konstanten durchreicht, ein Typ, der den vermeintlichen Eingang verbietet. Im Zweifel widerlegt sie.
Ein Finding überlebt nur, wenn eine zweite feindselige Lektüre desselben Codes es nicht zu töten vermag. confirmed will dann etwas bedeuten.
In der Session dasselbe: /audit deep.
Der Motor wird automatisch gewählt — dein Claude-Konto über das offizielle CLI, wenn es verbunden ist (die Analysen laufen parallel, auf deinem Abonnement), sonst ein API-Schlüssel.
Was das Audit nicht lesen soll
# .thotignore, à la racine du dépôt
vendor/
*.generated.py
tests/fixtures/Die integrierten Ausschlüsse decken ab, was jedes Repository hat — node_modules, build, .venv. .thotignore deckt ab, was nur dieses Repository weiß: eingebettete Dokumentation, ein generierter Client, ein Ordner mit absichtlich kaputten Fixtures. Sie zu auditieren erzeugt keine Findings, es erzeugt Rauschen an genau der Stelle, wo die Findings wären.
Deine eigenen Regeln
Der integrierte Katalog kennt die Standardbibliothek. Er kennt nicht den Wrapper, den dein Team um subprocess geschrieben hat, die Queue, die dein Dienst konsumiert, noch den Validator, der einen Wert bei dir sicher macht. Ohne einen Ort, wo man das sagt, irrt sich jedes Audit eines echten Systems an denselben drei Stellen.
# <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]Dieselbe Datei trägt die JavaScript-Regeln, unter einem Schlüssel js: — ein Team-Wrapper existiert in der Regel in beiden Sprachen, und die Deklaration zu trennen ist der Weg, auf dem eine Hälfte veraltet.
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]Was das Modell anfordert, ist ein nicht vertrauenswürdiger Eingang
Die Quellen sind Ausdrücke — sys.argv, os.environ. Das deckt ein Programm ab, das man startet, und verfehlt ein Programm, das man aufruft: Das Werkzeug eines Agenten erhält seinen nicht vertrauenswürdigen Eingang als benannte Parameter, befüllt von einem Register aus dem, was ein Modell angefordert hat, und kein Ausdruck erscheint irgendwo im Rumpf.
Die gemessenen Kosten, das nicht zu modellieren: vier SSRF an einem Nachmittag, alle über ein Tool-Argument erreichbar, keines von der Taint-Färbung gefunden — sie wurden von Musterregeln gefunden, die eine Form erkennen und nichts beweisen.
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: prefixStandardmäßig leer, und bewusst: Welche Funktionen ein Register aufruft, ist eine Tatsache über ein Repository, und sie zu erraten würde eine Quelle unter jeden Parameter jedes Programms legen. Gemessen an Hermes, die beiden Extreme: eine Regel, die die Pakete plugins und tools benennt, offenbart 19 bewiesene Pfade, davon mehrere überapproximiert (die base_url, die ein Helper aus der Konfiguration erhält, ist nicht nicht vertrauenswürdig); eine Regel, die den Parameter args benennt, offenbart null, weil die Handler von Hermes benannte Parameter nehmen und kein Wörterbuch. Die richtige Regel benennt die tatsächlichen Einstiegspunkte — und es ist Sache ihrer Autoren, sie zu kennen.
Eine Regel, die eine integrierte id übernimmt, ersetzt sie — um einen Sink herabzustufen, den das Team bewusst akzeptiert hat, ohne Thot zu patchen. Eine fehlerhaft formatierte Datei stoppt das Audit, indem sie die Datei und den fehlerhaften Schlüssel benennt, statt an eine Abwesenheit von Findings glauben zu lassen.
Die Unterdrückungen
Eine Unterdrückung ist die einzige Aussage über die Sicherheit, die kein Werkzeug erneut liest — auch dieses hier nicht, konstruktionsbedingt. # nosec, # noqa: S310, // eslint-disable … security/…: Es ist eine Aussage über den Code, einmal geschrieben, die die Aufrufer überlebt, die sie beschrieb.
Zweimal in demselben Audit, hier, war sie falsch:
Unterdrückung | was sie behauptete | was wahr war |
| das Schema ist kontrolliert | es stoppte |
| die URL stammt aus der Konfiguration | einer der Aufrufer liest sie aus einem Tool-Argument, also vom Modell |
Thot meldet sie daher als Klasse, in LOW, mit dem Muster daneben geschrieben. Das Finding sagt nicht „diese Zeile ist gefährlich“: Es sagt „niemand hat den Grund erneut gelesen, aus dem sie entschuldigt wurde“. Bei einem --deep-Lauf ist es ein Agent, der prüfen wird, ob das Muster noch hält.
Für Python sind es die echten Kommentar-Token, die gelesen werden, keine Muster — ein regulärer Ausdruck unterscheidet # nosec in einem Kommentar nicht von demselben Text, der in einer Docstring zitiert wird, und die Docstring dieses Moduls zitiert zwei.
Eine Unterdrückung, die auf eine Zeile gesetzt ist, die dieses Audit meldet, ist nicht dasselbe Objekt: Es ist eine Aussage, die ein lebendes Finding widerspricht, geschrieben von jemandem, der dieselbe Zeile gelesen und anders geschlossen hat. Sie wird eine Stufe angehoben und sagt es. Gemessen an Hermes: 7 von 45 — und drei der an jenem Tag gelesenen Unterdrückungen waren falsch.
Gemessen: 0 auf Thot, 0 auf Prime, 45 auf Hermes.
Kalibrierung
Die Präzision zählt so viel wie die Erkennung. Bewusst nicht gemeldet:
subprocess.run(cmd)ohneshell=True— keine Shell liest den Befehl.cursor.execute("… ?", params)— wörtliche Abfrage, gebundene Parameter.Ein Wert, der durch
int(),shlex.quote(),os.path.basename(),html.escape()geht — diese Aufrufe brechen die Kontaminationskette.Ein Defekt, den kein Einstiegspunkt erreicht, wird automatisch herabgestuft — aber nur, wenn Einstiegspunkte gefunden wurden. Ohne einen ist die Reichweite unbekannt, nicht null, und nichts wird über diese Unkenntnis begraben.
payload.get(...)ist nichtrequests.get(...).
Größenordnung, gemessen an Hermes Agent (4 457 Python-Dateien): 98 s, 365 Findings, davon 25 high — 3 über dem Standard-Schwellenwert, sobald der Speicher angewendet ist.
Welchen Parameter das Argument füllt
Ein Helper, dessen ein Parameter einen Sink erreicht, macht nicht alles gefährlich, was man ihm übergibt. Der Motor paarte jedoch den Aufrufer mit der Gesamtheit der Sinks des Callee, ohne zu sehen, wo das Argument landete:
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éDie Position wird nun am Aufrufort gelesen, und der Name für ein benanntes Argument. Das tat der JavaScript-Motor auf seiner Seite bereits.
Gemessen, sobald die Ausführung möglich war: Von 83 238 angeforderten Auflösungen bei der Analyse von Thot und hermes/tools bezeichnen 90 % einen einzigen Parameter und 9,8 % keinen; 0,1 % bleiben offen (f(*rest), f(**options)) und behalten die breite Antwort. Der Callee bot im Durchschnitt 2,68 Parameter, also verschwindet fast zwei Drittel des Suchraums — die Analyse von hermes/tools geht von 11,4 s auf 9,5 s.
Bei der Zahl der Findings hingegen: keine Änderung. Thot, prime und hermes/tools liefern exakt dieselben 151 Kandidaten vorher und nachher, davon null erfunden — die Sicherheitseigenschaft hält im Feld. Vier Taint-Pfade werden kürzer, sonst nichts. Die oben korrigierte Form ist real und der Test beweist es, aber sie tritt in keinem der drei gemessenen Bäume auf: Dieser Fix kauft Geschwindigkeit und gerechtere Pfade, nicht weniger Rauschen.
Zwei Formen sagen nichts: helper(*args) breitet eine unbekannte Anzahl von Werten aus, und helper(**options) benennt keinen. Dort behält der Motor die breite Antwort, die er vorher gab — die beibehaltene Menge ist immer enthalten in der vorherigen, also kann Verfeinern nur ein Finding entfernen, nie eines erfinden.
Der Empfänger wird übersprungen im Vertrauen auf die Signatur des Callee, nicht auf die Syntax des Aufrufs: Runner().go(x) begibt sich zu einem go ohne Punkt, und Cls.m(obj, x) wie obj.m(x) sind beide ein Attribut. Da eine Methode fast immer gebunden aufgerufen wird, entscheidet self an der Spitze der Parameter — der ungebundene Aufruf ist die einzige Form, die einen Schritt zu kurz gelesen wird.
Was der Graph nicht verfolgen kann
Ein Defekt, der über einen Pfad erreicht wird, den die Analyse nicht auflöst — ein Handler in einer Dispatch-Tabelle, eine dekorierte View, ein Aufruf auf einer Variable mit unbekanntem Typ — ist kein unerreichbarer Defekt. Thot unterscheidet die beiden:
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 nomIn allen drei Fällen ist die Reichweite unbekannt, nicht null, und das Finding behält eine leichte Strafe, statt begraben zu werden. Auf Hermes: dieselben 365 Findings, aber 60 steigen eine Stufe. Eine Funktion, die niemand aufruft und die niemand erwähnt, bleibt dagegen korrekt abgewertet — sonst hörte der Filter auf, ein Filter zu sein.
Grenzen
Die Taint-Färbung über Dateien hinweg ist Python vorbehalten: JavaScript und TypeScript werden indexiert, innerhalb eines Funktionsrumpfs und bis zu einem Helper derselben Datei verfolgt, und hören dort mangels aufgelöstem Aufrufgraphen auf — die Tabelle oben sagt es Zeile für Zeile. Die Musterregeln dagegen gelten überall.
Die +55,5 % sind eine Python-Zahl. BenchProctor etikettiert Django, FastAPI und Flask, und nichts anderes; der JavaScript-Katalog ist enger als sein Gegenstück — keine Quelle „Datenbank-Lesen“, kein Logging-Sink — und seine Regeln stützen sich auf seine Tests statt auf 18 300 etikettierte Fälle. Wenn eine Korrektur für beide Motoren gilt, wird sie auf beide übertragen, und das Journal sagt, welche wo gemessen wurde.
sink.log liest die Nachricht und nur sie. logger.info('failed: %s', exc) vertraut den Wert der Logging-Maschinerie an, statt ihn in die Kette zu schreiben, die der Autor getippt hat; auch diese Argumente zu lesen bringt +0,0000 auf dem Korpus und 4 844 Findings mehr auf hermes/, eines pro except … as exc: des Baums. Das ist nicht abgedeckt, und es zu sagen kostet weniger, als es glauben zu lassen.
Ohne --deep ist jedes Finding PLAUSIBLE: statisch erkannt, noch nicht durch Ausführung bewiesen. Mit --deep hat ein confirmed-Finding eine gegnerische Widerlegung überlebt — das ist noch kein Ausführungsbeweis, der mit dem Repro kommt. Und die Abwesenheit eines Findings ist kein Beweis für die Abwesenheit eines Defekts: dynamische Dispatch, Reflexion und Metaprogrammierung entgehen der Analyse.
Die Volltextsuche in den Sessions hört auf, bei konstanten Kosten zu bleiben, auf den SQLite-Datenbanken, deren fts5 die rowid nicht zurückverfolgen kann: Der Motor sortiert dort die Gesamtheit der Treffer, statt bei zwanzig zu schneiden. Gemessen an der, die CPython 3.12 mitbringt — 6 588 Anweisungen gegen 27 443, wenn der Korpus mit fünf multipliziert wird, wo ein fähiger Motor bei 2 215 und dann 2 698 bleibt. Ein Test verifiziert es und nennt die Version, wenn sie es nicht erlaubt.
Entwicklung
cd Thot
uv run pytest -qEditable installiert: Der Quellcode gilt sofort. Wenn sich dagegen pyproject.toml ändert (neue Abhängigkeit), muss uv tool install --editable --from . thot --force erneut ausgeführt werden.
Der deterministische Kern (codemap, taint, scope, scoring, store, report) hängt von keinem Agenten ab und berührt kein Netzwerk — ein Test verifiziert es und lässt die Suite fehlschlagen, wenn sich das ändert.
Spec und Pläne: 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