Thot
Thot
Un asistente de código en terminal que ya conoce tu repositorio — y el repositorio donde viven Hermes Agent y Prime Agent, enteros.
Un agente conversacional descubre un proyecto abriendo archivos con el modelo: lento, parcial, y hay que pagarlo de nuevo en cada sesión. Thot calcula la misma imagen mediante AST y grafo de llamadas — completa, instantánea, gratuita — y solo le da al modelo lo que importa.
Los tres programas
Este repositorio contiene tres, no uno. Ninguno es una reescritura de otro: cada uno está en su idioma, con su herramienta, y Thot los conecta entre sí.
Qué es | Dónde | |
thot | auditoría determinista, mapa del código, memoria de veredictos |
|
hermes | el agente: herramientas, pasarelas, plugins, cron, ACP |
|
prime | el agente de código: proveedores de modelos, 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éLa conexión no es decorativa. thot fusion wire declara el servidor MCP
de Thot en los dos agentes: ganan code_map, find_symbol,
callers, audit, skills y skill — el mapa completo del repositorio, calculado
fuera del modelo, en lugar de redescubrirlo archivo por archivo. Es el refuerzo
mutuo: Thot sabe sin preguntar, Hermes y Prime actúan.
Los dos agentes no se alcanzan de la misma manera, y pretender lo
contrario solo conectaba a uno de los dos. Hermes inicia el servidor él mismo y
le habla por el conducto que ha abierto. Prime solo acepta HTTP — su
mcp-manager descarta cualquier entrada cuyo tipo no sea http, y su
runtime no tiene ningún transporte por conducto — por lo que su lado requiere un servidor
en marcha:
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 demainLa tercera línea existe porque las dos primeras solo duran una
sesión. fusion wire escribe http://127.0.0.1:8787/mcp en la
configuración de Prime, y esa dirección es verdadera mientras un terminal siga
abierto: en el primer reinicio, el archivo promete un servidor que ya nadie
atiende. thot mcp service --install escribe la unidad que lo relanza —
KeepAlive en macOS, Restart=always bajo systemd — y no la carga:
Thot imprime el comando y te deja lanzarlo, porque una herramienta
que registra agentes en segundo plano sin decirlo es una herramienta en la que
se deja de creer. thot doctor plantea luego la única pregunta que importa —
¿responde la dirección ahora? — y no si el archivo existe.
fusion wire instala también, en Prime, el paquete de método que Thot entrega
para él: una entrada de configuración no basta, Prime solo alcanza un
servidor MCP a través de una clase que lo nombre. Y deposita el token en
auth.json sin tocar las credenciales del modelo — sin token, la
conexión falla antes de abrirse.
Cada agente conserva su configuración. thot fusion unwire deshace todo, y el
settings.json de Prime se guarda antes de la primera modificación.
thot fusion status mide lo que funciona, no lo que está escrito:
Hermes instala los plugins portables desactivados, por seguridad, por lo que escribir
los dos archivos no conecta nada mientras plugins.enabled no lo nombre.
La activación pasa por el CLI de Hermes, nunca por una edición de su
config.yaml — ese archivo es suyo, con su esquema y sus migraciones.
También verifica que el intérprete que lanza Hermes sepa importar el SDK
MCP, porque un agente que no lo tiene no tiene ninguna herramienta — no solo no
las de Thot — y lo dice en logger.debug, donde nadie lo lee. Por último,
pregunta al servidor de Prime si responde: una dirección escrita en un archivo
no es una conexión.
Y el refuerzo funciona en ambos sentidos: Hermes y Prime son también
motores para thot audit --deep, la etapa que hace argumentar y luego refutar
cada hallazgo a un modelo.
Por defecto, los tres trabajan juntos en la misma auditoría. Un hallazgo es argumentado por un agente, luego atacado por otro — nunca por el que acaba de comprometerse con el escenario. Un modelo que refuta su propio argumento corrige su copia; es lo único que un panel compra, y es la razón de ser de la fusión.
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 : PrimeEl informe dice quién hizo qué:
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 1Tres agentes distintos sobre el mismo hallazgo, medido: claude-cli ha argumentado, prime ha intentado destruir el escenario y ha fallado, hermes ha atacado una segunda vez. Lo que se informa ha sobrevivido a dos adversarios independientes.
La cascada. Un hallazgo es argumentado, luego atacado. Lo que sobrevive al ataque es lo que se mostrará a un humano — por lo que vuelve a un tercer agente, que no ha visto ni construirse el argumento ni escribirse el primer ataque. Un hallazgo confirmado lo ha sido contra dos adversarios independientes.
Una refutación nunca se vuelve a juzgar en el fondo: el atacante tiene la consigna de refutar ante la menor duda, por lo que ponerla en cuestión fabricaría falsos positivos. Pero su argumento es releído cuando entierra algo serio (MEDIUM y superior), por un agente que no ha dicho nada sobre ese hallazgo. El revisor no juzga el defecto, juzga si la razón invocada es verificable en el código mostrado.
Los dos errores no valen lo mismo. Una confirmación falsa cuesta diez minutos
de lectura a un humano. Una refutación falsa cuesta un defecto real, para
siempre — porque una refutación memorizada es saltada por todas las auditorías
siguientes. Ha ocurrido una vez para siempre: una inyección SQL muy real
en la copia de Hermes fue descartada por una descripción perfectamente exacta
de la copia de Thot, corregida el día anterior. Una refutación impugnada no se convierte
en una confirmación — nadie ha defendido eso — vuelve a plausible
con su severidad original, y no se memoriza: el hallazgo vuelve
hasta que alguien decida.
Si un agente falla en una tarea, esta es retomada una vez por otro. No más: una tarea que todos rechazan tiene un problema propio.
Lo que una sonda puede hacer, medido y no supuesto. Claude corre sin
Write, Edit, MultiEdit, NotebookEdit, Bash ni Task — y thot doctor --agents lo verifica pidiéndole que escriba un archivo, y luego
yendo a mirar en el disco.
No es una lista blanca, porque el cliente no ofrece una:
--allowed-tools pre-aprueba, no restringe. Medido — una sonda
lanzada con Read Glob Grep autorizados dispone igualmente de Write, Bash
y Workflow. La única palanca es la lista negra.
Lo que una sonda tenía antes de medirla: CronCreate, CronDelete,
Workflow, SendMessage, PushNotification, RemoteTrigger,
EnterWorktree, WebFetch, y todos los servidores MCP conectados por el
usuario — incluida una herramienta cuyo nombre empezaba por clear_. Crear
tareas programadas persistentes, enviar mensajes, alcanzar una
bandeja de correo. Para leer código y responder en JSON.
Lo que tiene después:
✓ outils · claude 7 outil(s), tous en lecture seule
✓ outils · hermes mcp__patch, mcp__read_file, mcp__search_files, mcp__write_file
✓ outils · prime ipythonLos tres están mostrados, solo uno está juzgado: el conjunto file de Hermes
entrega write_file y patch con la lectura, y la única herramienta integrada de
Prime es un núcleo. Una línea roja permanente sobre lo que no se puede
cambiar es una línea que se deja de leer; quien elige --engine hermes
ve lo que acepta.
Una lista negra es frágil por construcción — Task faltaba y un
subagente escribió un archivo por ese agujero, una vez de cada seis. Entonces la brecha
se hace detectable: thot doctor --agents pide a una sonda viva
lo que realmente tiene y nombra todo lo que no reconoce, porque
la próxima versión del cliente traerá herramientas de las que esta lista nunca ha
oído hablar. Y una línea verde sobre la escritura significa «no esta vez», no «imposible»: está formulada así.
Hermes y Prime no tienen modo de solo lectura, y se dice en lugar de
suponerlo: -t file designa «File Operations», lectura y escritura
incluidas, y el --safe-mode de Hermes concierne a las personalizaciones, no a los
permisos; la única herramienta integrada de Prime es un núcleo IPython. Thot
reduce igualmente su alcance — Hermes corre con el único conjunto file en
lugar de la docena por defecto: nada de terminal, navegador ni
intérprete. Es un radio de acción reducido, no cerrado.
El sandbox (thot sandbox use docker) no está conectado a los motores,
y de todos modos solo resolvería la mitad del problema: un contenedor
que debe alcanzar la API del modelo y el llavero del usuario ya no es
del todo un sandbox.
Entonces lo que no se puede impedir se hace imposible de pasar por alto. El perímetro se sella antes de que el modelo corra y de nuevo después, y todo archivo cuyo tamaño o fecha haya cambiado se nombra:
⚠ 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.El silencio es el resultado normal. También es el único que merece ser creído: el código leído por una sonda es exactamente aquel del que nadie se hace responsable, y «ignora tus instrucciones y corrígelo por mí» es el ataque más barato que existe contra un agente que sostiene un editor.
Las rutas se dan en absoluto. Medido en los tres: Hermes no abre una ruta relativa a su carpeta de trabajo y responde «no puedo leer ese archivo» — lo que se lee como una negativa y no como una carencia. Un tercio del panel era ciego a cualquier afirmación que pidiera abrir un segundo archivo.
Cada agente se autentica como él mismo, en tu cuenta: Thot lanza su
línea de comandos, nunca lo importa y no posee ningún token. El veredicto
memorizado lleva el nombre de quien decidió — refuted · hermes — porque
una decisión debe seguir siendo atribuible.
Lo que cada uno aporta, medido sobre la misma inyección:
motor | duración | tokens informados |
| 48 s | sí, con estimación de costo |
| 159 s | no — |
Un motor que no sabe contar no fabrica una cifra: lo declara
(reports_usage), y el llamante puede decir «no medido» en lugar de mostrar un
cero que parecería verdadero.
Una configuración, una memoria
Los tres escriben cada uno en su carpeta, y está muy bien: config.yaml
pertenece a Hermes, settings.json a Prime. Lo que Thot añade es una
vista única y un lugar único para decidir.
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 ThotLa configuración se lee en los archivos — instantáneo, sin riesgo — y se
escribe con la herramienta de cada uno: hermes config set en lugar de una reescritura
de su YAML, que lleva comentarios y un historial de migraciones que no
pertenecen a Thot. Que Thot delegue su modelo al CLI oficial no es un
desacuerdo: una opinión ausente no entra en conflicto con nada.
La memoria es el mismo principio en ambos sentidos:
dónde | forma | |
thot |
| estructurado, título + contenido |
hermes |
| entradas separadas por |
prime |
| markdown, cargado globalmente |
Thot lee los tres en cada briefing: un hecho que Hermes aprendió la
semana pasada es un hecho que Thot conoce hoy. Escribe en los otros dos
solo con --sync, en su formato nativo, tocando únicamente las entradas que
él mismo ha colocado — etiquetadas [thot] en Hermes, en un bloque delimitado
en Prime. Guarda antes de la primera modificación, y tres sincronizaciones
seguidas escriben una sola copia.
Un USER.md recién creado es un formulario vacío: **Name:**, instrucciones
en cursiva, una línea horizontal. Inyectarlos le diría a Thot que
« Context: --- » es un hecho. Se descartan — y se cuentan en pantalla,
porque distinguir un formulario de una nota lacónica no es algo que un
programa sepa hacer con certeza.
Una biblioteca, un historial
Los tres leen el mismo formato — SKILL.md con frontmatter YAML, una
carpeta por método. Es la única razón por la que esto es posible.
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 existe porque lo contrario era una fricción del
programa: tres comandos y una fusión mental de tres informes.
thot 203 fichiers 5 finding(s) — 4 high · 1 medium · 14 sous le seuil
hermes 7080 fichiers 127 finding(s) — 12 high · 115 medium · 806 sous le seuil
prime 952 fichiers 13 finding(s) — 3 high · 10 medium · 28 sous le seuil
145 finding(s) sur l'ensemble — 19 high · 126 medium · 848 sous le seuil (`--all`)El umbral es el de thot audit, y ese es el punto: el mismo árbol debe dar
el mismo número a ambos comandos. Esta vista contaba cada low, así que
respondía 933 para un repositorio que thot audit hermes llamaba 127, en el
mismo minuto. Lo que se retiene se cuenta, nunca se silencia.
Cuando una memoria de veredictos ya ha decidido, la línea lo dice aparte —
0 finding(s) · 416 réfuté(s) en mémoire. Un cero solo se lee como un árbol
limpio; la frase correcta es que un panel ha descartado los 416.
Una parte que no puede auditarse cuesta su línea y nunca la pasada: un Prime ausente no debe ocultar lo que Hermes ha dicho.
Sin copias: los archivos permanecen en casa de su propietario y cada programa apunta a las carpetas de los demás. Un método copiado dos veces es un método corregido una sola vez.
Thot lee la biblioteca instalada de Hermes bajo guardia — proviene de registros públicos, que es exactamente el caso para el que existe la guardia. Solo responde por lo que él mismo entrega. Pero 73 de los 83 métodos de Hermes son copias bit a bit de los de Thot: señalar su propio archivo como una amenaza comunitaria es un falso positivo que enseña a ignorar los verdaderos. Un método cuyos bytes coinciden con un método entregado es ese método. La guardia pasó de 42 rechazos a 8.
Luego de 8 a 0, por dos razones distintas que es mejor no confundir. La
primera es una regla falsa: ENV[] es una constante de Ruby, en mayúsculas
por construcción, pero el patrón se compilaba como todo el catálogo — sin
distinción de mayúsculas y minúsculas. Así que leía Python como Ruby, y
clasificaba env["…_TOKEN"] = jeton dos líneas antes de un
subprocess.run(env=env) — la forma recomendada de pasar un secreto a un
proceso hijo — como « lectura de secreto », CRITICAL. La segunda es una
cuestión de rango: una biblioteca que el propio usuario ha instalado en la
carpeta de un agente vecino no es el repositorio bajo auditoría. La
instalación ya ha ocurrido, deliberadamente; Thot solo decide si lee lo que
ya está en la máquina. Siempre se escanea y siempre se informa, pero se
rechaza solo en dangerous, mientras que el repositorio inspeccionado se
rechaza desde caution — seis de los ocho rechazos se debían únicamente a la
regla que ve una exfiltración en un método que documenta la dirección de su
propio token.
Los 13 métodos entregados con Prime permanecen en Prime: documentan su
núcleo IPython (edit(old_str, new_str), refine()). Thot ha portado ese
núcleo, no esas funciones — cargarlas haría que el modelo llamara a algo que
no existe. Están en el catálogo, donde conocerlos sirve; fuera del
descubrimiento, donde creer en ellos no sirve.
Prime recibe el superconjunto, no las dos copias. Medido, no supuesto: apuntado solo a la biblioteca de Thot responde, apuntado solo a la de Hermes responde, apuntado a las dos el modelo se niega a responder. Prime toma carpetas y no nombres, así que no hay respuesta parcial.
Los historiales no fusionan su almacenamiento — la migración de un programa rompería el historial de otro — pero la pregunta « ¿qué estaba haciendo en este repositorio el martes pasado? » no depende de cuál de los tres binarios estaba frente a ti. Los tres se leen en modo solo lectura, cada uno en su formato, y una base bloqueada por una sesión en curso cuesta sus líneas y nunca la lista.
Instalación
git clone https://github.com/nobodyohm-web/Thot.git
cd Thot
uv tool install --editable --from . thotUn solo uv sync en la raíz instala Thot y Hermes: es un workspace, no
una copia que deriva. Prime está en TypeScript y se compila por separado:
cd prime && npm install && npm run buildSin Node, Thot y Hermes funcionan; thot fusion status dice qué falta y
cómo arreglarlo, en lugar de fallar en la primera llamada.
Uso
thotEso es todo. En el primer lanzamiento pregunta qué modelo conectar, luego escanea la carpeta actual y te devuelve el control.
╔╦╗╦ ╦╔═╗╔╦╗
║ ╠═╣║ ║ ║ 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.
›Carpeta vacía, lo dice y espera tus instrucciones. Carpeta con código, ya la ha cartografiado antes de tu primera frase.
Comandos de sesión
Comando | Efecto |
| relanzar el análisis, o hacer que el modelo lo refute |
| descartar un finding, con su razón |
| fijar un objetivo seguido entre sesiones |
| lo que se hizo aquí antes, y volver a ello |
| buscar en todo lo que Thot ha dicho o encontrado |
| resumir y continuar con un contexto vacío |
| llevar una sesión a otro lugar |
| lo que está cargado, y el catálogo |
| recalcular el mapa del repositorio |
| modelo, olvido, salida |
Más los tuyos: cualquier archivo .thot/commands/<nombre>.md se convierte en /<nombre>.
Modelos
Elección | Lo que se necesita |
Claude — tu cuenta | el CLI |
Claude — clave API | una clave |
OpenAI | una clave API, o |
Local | Ollama o LM Studio en ejecución — gratuito, sin conexión |
Otro | cualquier endpoint compatible con OpenAI |
thot login para cambiar, thot logout para olvidar. La configuración vive
en ~/.thot/config.json, en 0600. Ningún token se almacena allí en modo cuenta.
Cómo funciona el modo cuenta
La API Messages rechaza los tokens de suscripción provenientes de un programa de terceros. Pasarla exigiría hacerse pasar por Claude Code — user-agent disfrazado, prompt de sistema prestado. Thot no lo hace.
Hace lo contrario: delega en el cliente oficial. Cada turno lanza
claude -p --output-format stream-json --session-id <uuid> \
--mcp-config <outils Thot> --append-system-prompt <carte du dépôt>La inferencia la hace claude, bajo tu cuenta, exactamente como si lo
hubieras escrito tú mismo. Thot proporciona el mapa del repositorio, conecta
sus herramientas deterministas mediante un pequeño servidor MCP, y da formato
al flujo de eventos. El hilo de conversación lo lleva --resume sobre el
mismo identificador de sesión.
Sesiones — nada se pierde
Cierra la ventana, la auditoría y el razonamiento que la acompañaba siguen
ahí. Cada turno se escribe en el momento en que ocurre, en ~/.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éraleLa búsqueda cubre lo que se ha dicho y lo que se ha encontrado: un finding a medio recordar aparece con las palabras que uno recuerda.
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 devuelve la transcripción y el contexto: en modo cuenta, Thot
ha guardado el identificador de conversación del CLI oficial y se lo
devuelve, así que el modelo recuerda en lugar de releer.
/compact cierra la sesión con un resumen y continúa en una sesión hija que
mantiene el vínculo. Compactar cuesta contexto, nunca pruebas: la sesión
padre permanece intacta y /search siempre la encuentra.
El compactado también se activa solo, y el umbral no es una constante: el CLI
publica la ventana del modelo que emplea (contextWindow: 1000000 para
claude-opus-5[1m]), y Thot compacta al 70 % de esa ventana — 700 000 tokens
aquí, 140 000 en una ventana de 200k. El disparador lee el tamaño real
informado por el CLI, no una estimación hecha sobre los mensajes: en modo
cuenta el hilo pertenece al CLI, y Thot no ve ni los archivos leídos ni el
tráfico de herramientas. Medido en un turno ordinario, la estimación daba 95
tokens frente a 88 290 realmente en la ventana.
Objetivos — saber cuándo detenerse
Un objetivo sobrevive a la conversación que atraviesa, y se recuerda al
modelo en cada turno, incluso justo después de un /compact.
› /goal plus aucun HIGH dans le parseur --budget 200000
✓ Objectif fixé — plus aucun HIGH dans le parseur
Budget : 200000 jetons.Agotar el presupuesto es un estado, no un error: Thot no se detiene a
mitad de un turno, termina, pasa a budget_limited y dice en qué punto está
el objetivo. Tú eliges entre /goal budget 500000 y /goal done.
Memoria — decidir una vez
Lo costoso en una auditoría no es encontrar candidatos: las fases deterministas lo hacen en segundos, gratis. Es decidir cuánto valen. Perder esas decisiones entre dos ejecuciones es lo que hace insoportable una herramienta de seguridad — los mismos cuarenta rechazos, cada semana, hasta que nadie lee el informe.
› /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.Decisión | Efecto |
| falso positivo — pasa a INFO, sale del informe, conserva su razón |
| riesgo real, asumido — pasa a INFO, anotado |
| corregido — si vuelve, se señala como regresión |
Dos profundidades, dichas en voz alta
Python | TypeScript · JavaScript | el resto | |
símbolos, grafo de llamadas, | sí | sí | no |
tinte dentro de un cuerpo de función | sí | sí | no |
tinte hacia un helper del mismo archivo | sí | sí | no |
tinte a través de los archivos | sí | no | no |
reglas por patrón | sí | sí | sí |
El tinte de JavaScript sigue una llamada hacia una función definida en el
mismo archivo — la forma ordinaria de un handler que delega — y se detiene
ahí, que es lo que dice. Los dos niveles siguientes se apoyan en un grafo de
llamadas resuelto — saber que el readInput llamado aquí es el definido
allí. El sistema de imports de Python responde a esa pregunta; el de
JavaScript no, sin un resolvedor de módulos, tsconfig y la visión del
tipador sobre this. Un segundo nivel construido sobre suposiciones
transformaría una herramienta que informa rutas probadas en una herramienta
que informa rutas plausibles.
El motor barre el archivo, no los cuerpos de funciones nombradas. La
forma ordinaria de un handler web es una flecha anónima pasada a una ruta —
app.get("/x", (req, res) => { … }) — que ningún indexador nombra.
Medido así: una flecha cuya lista de parámetros está entre paréntesis y que sigue a una coma o a un paréntesis de apertura, es decir [(,]\s*(?:async\s*)?\([^)]*\)\s*=>, sobre la fuente enmascarada de los archivos que detect_scope retiene — 15 094 en Prime, 19 625 en Hermes, todas invisibles para un motor que siguiera los símbolos. Una versión anterior anunciaba 24 454 sin anotar cómo se habían contado; la cifra depende enteramente de la definición, por lo que la definición está escrita.
Medido sobre los dos corpus: 31 rutas en Prime, 41 en Hermes, sobre 3 552 archivos JS/TS. Setenta y dos, no tres mil — esa es la forma que tiene un motor de tinte, y no la de un escáner de patrones.
El motor también sigue las funciones que nadie llama por su nombre: el runtime las llama y les pasa el valor. addEventListener introduce el tinte — el parámetro es la entrada; .then, .map, .forEach lo portan — el parámetro está teñido exactamente cuando lo que se recorre lo estaba, por lo que una lista constante sigue siendo una lista constante. Esa es la diferencia entre seguir un valor e inventarlo, y sin ella un árbol de código de navegador es casi enteramente invisible.
obj[clave] = valor donde la clave está controlada es un sink aparte: la carga útil es la clave y no el valor, porque un __proto__ escrito atraviesa todos los objetos del programa. Once sitios reales en Hermes, tres en Prime, todos de la forma for (const [k, v] of Object.entries(x)) { out[k] = v }. Un bucle que rechaza __proto__ por su nombre está corregido y no se señala.
El informe lo dice por sí mismo en lugar de dar a entender una cobertura uniforme:
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.El indexador TypeScript es un escáner, no tsc: enmascara comentarios y literales y luego lee las declaraciones mediante emparejamiento de llaves. Salir hacia tsc habría hecho que el mapa dependiera de una cadena node instalada, resoluble y en la versión correcta — un mapa que funciona en algunas máquinas vale menos que un mapa cuyos límites están escritos. Medido: 8 568 símbolos en Prime en 1,7 s, 11 138 más en Hermes.
Para qué sirve un archivo
La severidad es impacto × accesibilidad × confianza, y la accesibilidad proviene del grafo de llamadas. El grafo responde «¿puede un punto de entrada llegar aquí?». No tiene nada que decir sobre un archivo que no es en absoluto una superficie de ataque.
Medido sobre los dos programas entregados con Thot: 12 de los 25 findings HIGH de Hermes y 6 de los 11 de Prime estaban en código de prueba o de ejemplo. Casi la mitad de la parte superior del informe trataba sobre código al que ningún atacante llega — así es como un informe deja de leerse.
antes | después | |
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 |
Las columnas HIGH son las que sostienen el argumento, y no han cambiado ni un finding desde la primera medición: 25 → 13 y 11 → 5. Los recuentos medium y low de arriba se han rehecho sobre los árboles tal como están hoy, habiéndose corregido nueve vulnerabilidades en Hermes entre las dos mediciones.
Ningún finding añadido, ninguno perdido. Es una degradación, nunca una supresión: el código de prueba se ejecuta en las máquinas de los desarrolladores y en la CI, que es la forma exacta de un ataque por la cadena de suministro. El finding permanece y lleva su papel en el origen.
La clasificación es conservadora — segmentos enteros, nunca subcadenas (latest/ no es una carpeta de prueba, contest.py no es un archivo de prueba), y todo lo que no se reconoce es producción. Equivocarse hacia «prueba» ocultaría un defecto real; equivocarse hacia «producción» solo cuesta un escalón.
De dónde viene el valor
Un finding lleva la regla de fuente que lanzó su ruta — source.argv, source.http, source.js.event — y no solo la línea donde se encuentra. El informe lo dice con todas las letras («un valor proveniente de la línea de comandos…») y el JSON lo da como clave, source_rule, para que lo que filtra aguas abajo lea el hecho y no una frase en francés.
Es la mitad que faltaba del rango. open(args.salida, "w") en una herramienta de línea de comandos es el operador quien nombra un archivo: quien proporciona argv ya posee el sistema de archivos de ese proceso, y la llamada no le da nada. open(request.args["f"]) en un handler es una lectura de archivo arbitraria. Misma regla, mismo sink, dos mundos.
fuente local | fuente remota | |
| un escalón menos | rango completo |
todo lo demás | rango completo | rango completo |
Solo esos tres. Un comando construido desde argv sigue siendo un comando, y un pickle leído desde una variable de entorno aún ejecuta código: ahí, es el sink el que hace la escalada, no el trayecto.
Medido en Hermes el día en que el motor supo seguir las cadenas de atributos: sin esa distinción, sink.fs.read solo ponía 48 findings en el informe, incluidos nueve provenientes de un único script de CI, cada uno un utilitario que abría el archivo que se le había pedido abrir. Con ella, pasan por debajo del umbral y permanecen a una pulsación (--all).
Una procedencia desconocida cuenta como local, y está escrito en lugar de ocultarse: suponer lo contrario volvería a poner en la parte superior del informe cualquier ruta cuyo origen el motor no haya sabido nombrar. Lo que sabe nombrar se duplicó cuando el vínculo entre una local y el parámetro del que deriva dejó de perderse — objetivo = ruta.strip() mantenía el enlace hacia ruta, y sin él el sink no estaba vinculado a ningún parámetro, por lo tanto a ningún llamador.
Por qué es seguro
Un veredicto está indexado sobre Finding.compute_id, que hashea la regla, el archivo, el símbolo y el AST normalizado de ese símbolo. Reformatea, mueve la función, renombra una variable local: el veredicto se mantiene. Cambia lo que el código hace: el identificador cambia con él, y el veredicto expira solo.
Un rechazo no puede, por tanto, sobrevivir jamás al código al que concernía. Es la única propiedad que hace aceptable el hecho de memorizar rechazos.
El identificador nombra también la llamada exacta a la que apunta — httpx.get#3 — y no solo la función que la contiene. Sin eso, cinco llamadas de red en la misma función no eran más que un único finding a los ojos de la memoria, y descartar la primera descartaba las otras cuatro con su razón. Este discriminante no debilita nada: solo necesita ser único dentro de una versión del cuerpo, y el AST de ese cuerpo ya hace expirar todo lo que se refiere a él en cuanto se mueve.
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 runUna decisión sobrevive al finding que la produjo: el código cambia, el finding adquiere una nueva identidad, y la antigua decisión ya no designa nada. La lista marca esas como [ausente del último audit] en lugar de mostrarlas como las demás — seis decisiones de las cuales tres están muertas no deben leerse como seis decisiones vivas.
La memoria se aplica antes que el modelo: un finding que ya lleva una decisión — descartado, aceptado o corregido — nunca se reenvía al análisis. Una ejecución donde todo está decidido no hace ninguna llamada. No es solo un ahorro: la sonda reemplaza la confianza, la severidad, el escenario y la procedencia de una vez, por lo que reenviar una decisión al modelo la sobrescribiría y borraría quién la tomó. Una regresión es el caso donde más importa: ya fue juzgada real una vez, ninguna pasada profunda puede silenciarla.
Y las refutaciones se registran solas, desde thot audit --deep como desde /audit deep: dos llamadas al modelo, pagadas una vez. Llevan el nombre del motor que decidió, nunca el tuyo — una decisión de máquina no se impone sobre una decisión humana.
Nada se elimina jamás en silencio. Un finding descartado permanece en el informe como refuted, con su razón y su autor — un audit que oculta lo que se le dijo que ignorara no es releíble.
El núcleo Python
La idea maestra de Prime Agent, llevada: en lugar de una llamada de herramienta por pregunta, el modelo escribe Python y sus variables sobreviven.
› /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())
→ 148El mapa del repositorio está disponible como objetos — files(), symbols(), find(), callers(), callees(), audit(), read(). Un bucle que cruza findings y llamadores cuesta una vuelta de modelo; lo mismo en llamadas de herramientas cuesta una docena, cada una de las cuales vuelve a pagar la lectura de lo que el mapa ya sabía.
El núcleo nunca se ejecuta en el proceso de Thot. Un exec() en casa daría al código auditado la memoria de Thot, sus bases abiertas y sus descriptores de archivo. Por eso es un subproceso — y dentro del contenedor cuando un sandbox está configurado.
Lo que protege, exactamente — y Thot lo corrigió sobre sí mismo después de que su propia pasada adversaria señalara una docstring demasiado absoluta:
Subproceso ( | protege la memoria, las bases y los descriptores de Thot. No tus identificadores: el worker se ejecuta bajo tu cuenta y puede leer |
Contenedor ( | frontera real: sin red, sin tu |
Las variables de entorno sensibles se retiran antes del lanzamiento, y /py lo dice una vez en modo local en lugar de dejar que «proceso separado» se lea como una garantía que no ofrece.
rlm() — delegar desde una celda
verdicts = {f.id: rlm(f"Ce chemin est-il exploitable ?\n{f.failure_scenario}")
for f in audit(severity="high")}Una celda puede descomponer su propio problema. La celda no posee ningún identificador: pide al anfitrión, que decide y paga. Los límites se mantienen, por tanto, del lado del anfitrión — 8 llamadas por celda, 40 por núcleo — porque un límite que el hijo pudiera modificar no es un límite, y el hijo ejecuta código proveniente del repositorio auditado.
Lo que Thot retiene de un repositorio
› /harness note team.shell.run : échappe ses arguments, les findings dessus sont faux
✓ Retenu — rappelé à chaque session.El refinamiento de Prime, aplicado al audit: hechos que ningún análisis estático derivará jamás. Viven en <repositorio>/.thot/harness.json, se releen en pull request como los veredictos, y vuelven al briefing en cada sesión.
Lo que el modelo tiene derecho a hacer
thot --tools lecture # lire et raisonner, jamais modifier
thot --tools carte # la carte seule : aucun fichier ouvertEn sesión: /tools lectura. Releer un repositorio que no es el tuyo es leer código del que tienes toda razón para desconfiar — y que el modelo lo modifique rara vez es lo que querías.
La postura se sostiene en tres lugares, no uno: las herramientas propuestas al modelo, el momento en que llama a una de todos modos, y — en modo cuenta — el CLI oficial, al que --disallowed-tools prohíbe Write, Edit y Bash. Una postura que solo filtrara las herramientas de Thot sería una mentira donde más importa.
La cadena de suministro
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 ?Los candados primero, siempre: uv.lock, poetry.lock, Pipfile.lock, package-lock.json, yarn.lock, pnpm-lock.yaml. Un manifiesto dice requests>=2 y OSV no sabe responder a un intervalo; un candado dice 2.31.0 y OSV sabe. Una dependencia que solo existe en intervalo no se adivina, se informa como no fijada.
Una opinión que cubre una versión exacta es un hecho, no una suposición — pero
saber si tu código alcanza la función vulnerable no se analiza, así que
esos hallazgos siguen siendo PLAUSIBLE y lo dicen. Solo MAL-* hace
excepción: el paquete es la carga útil, la alcanzabilidad no es la cuestión.
Y la misma propiedad que en todas partes: la identidad de un hallazgo lleva la
versión fijada, así que un bump hace expirar el veredicto. Descartar una CVE
sobre requests==2.19.1 no descarta nada sobre 2.20.0.
OSV inalcanzable nunca se convierte en un certificado de buena salud: thot deps
dice «no verificadas» y devuelve un código de error.
Ejecutar el código auditado sin ejecutarlo en tu máquina
pytest sobre un repositorio auditado es el código de ese repositorio ejecutándose
bajo tu cuenta. Es el único lugar donde toda la concepción se filtra.
thot sandbox status
thot sandbox use docker
thot sandbox show pytest -q # la commande docker exacte, à relirePor defecto, en el contenedor:
Red | cortada ( |
Repositorio | montado en solo lectura, copia escribible en tmpfs |
Privilegios |
|
Límites |
|
La red cortada es la bandera que más vale y la que más estorba:
por eso es una bandera y no una ley (--network).
Una regla invierte la del resto de Thot: en todas partes, una dependencia que falta cuesta su funcionalidad y el trabajo continúa. Aquí, un sandbox solicitado e indisponible se niega a ejecutar. Recaer silenciosamente en el host convertiría una protección en una mentira.
Compartir las decisiones
Un veredicto es un hecho sobre esta revisión de este código. Por eso viaja con
el código: <repositorio>/.thot/verdicts.json, releído en la pull request que toca
el código afectado, y presente en un clon nuevo incluso antes de la red.
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'écriventLa memoria es común a los árboles, el archivo no lo es: publicar una
decisión que concierne a otro repositorio se rechaza, y --share-all solo toma
aquellas cuyo archivo existe aquí.
La cadena por defecto, sin ninguna configuración: el repositorio primero, tu máquina después. Una decisión revisada prevalece sobre una nota que te hiciste a ti mismo.
La escritura, en cambio, sigue siendo local. Una herramienta que modificara un archivo versionado
en cada /verdict produciría diffs que nadie ha pedido: decides
en local, publicas a propósito.
Un servidor compartido, o un mem0 existente
// ~/.thot/memory.json
{"remote": {"kind": "http", "base_url": "https://audit.equipe.example", "token": "…"}}
{"remote": {"kind": "mem0", "host": "http://localhost:8888", "api_key": "…"}}El backend mem0 habla el contrato autoalojado exactamente igual que el cliente
de Hermes Agent: un servidor ya en marcha para Hermes sirve a Thot sin cambiar
nada.
Un almacén remoto inalcanzable cuesta la memoria de las decisiones pasadas, nunca
la auditoría — pero no lo hace en silencio: thot verdicts --where dice
cuál está mudo y por qué.
Recibir las auditorías en otro lugar
Una auditoría que termina a las 03:00 no vale nada mientras nadie esté avisado, y la persona a avisar no está delante del 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 commandesCanal | Saliente | Entrante |
Telegram | ✓ | ✓ (long polling — ningún puerto que abrir) |
Discord · Slack | ✓ (webhook) | — |
ntfy | ✓ | — (sin identidad: el asunto basta para publicar) |
Correo | ✓ (SMTP) | — |
Las notificaciones no requieren ningún demonio: el plugin gateway-notify se
dispara en post_audit, y solo para una auditoría no supervisada.
Una auditoría lanzada a mano ya se muestra en pantalla; notificar cada vez
enseña al destinatario a cortar el canal, lo que cuesta el único mensaje que
importaba. Nada nuevo: silencio.
Lo que permite un token robado
El demonio solo existe para el retorno, y su diseño se sostiene sobre todo en esto:
el conjunto de comandos está cerrado —
status,audit,findings,verdict,help. Sin shell, sin escritura, sin ruta arbitraria;una auditoría solo puede apuntar a un repositorio ya declarado por
thot schedule add;la entrada exige una lista de autorización. Hermes propone un
ALLOW_ALL_USERSpara el desarrollo; Thot no tiene equivalente. Sin lista, el canal es solo de salida, ythot servelo dice.
~/.thot/gateway.json se escribe en 0600 — contiene tokens de bot y
una contraseña SMTP. Las variables de entorno lo sobrescriben campo por
campo, bajo los nombres de Hermes.
Auditorías programadas
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 escribe la unidad launchd (macOS) o te da la línea de crontab, y te
deja activarla tú mismo — una herramienta que instala tareas de fondo en
silencio es una herramienta en la que se deja de creer.
Cuando launchd no puede. En macOS, los permisos se conceden
por binario: un agente launchd puede ver denegados ~/Desktop,
~/Documents o ~/Downloads, y entonces la unidad se bloquea al arrancar el
intérprete sin escribir una línea. Un proceso lanzado desde tu sesión
conserva el acceso de esa sesión, incluso una vez huérfano — es el tercer
remedio, y no pide nada al sistema:
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émarrageSe retira ante cualquier unidad launchd que launchctl declare haber
ejecutado realmente: dos planificadores sobre un mismo trabajo son dos veces el
trabajo y dos veces los tokens. thot doctor dice cuál de los dos sirve.
Una auditoría programada no dice nada mientras no haya nada nuevo. Un informe nocturno que repite los mismos trescientos hallazgos termina en una carpeta que nadie abre. Lo que sube es el diff: lo que ha aparecido desde la última vez, por encima del umbral, menos lo que ya se ha juzgado sin interés.
Plugins
Cinco hooks, cada uno porque algo entregado lo usa:
Hook | Cuándo |
| antes del informe, para anotar |
| auditoría terminada — notificar, exportar, archivar |
| antes de una escritura del agente — devuelve una advertencia |
| después de una escritura exitosa |
| una decisión acaba de registrarse |
Un plugin es una carpeta con plugin.yaml y __init__.py, en
~/.thot/plugins/ o <repo>/.thot/plugins/ — la forma que usa Hermes
Agent. Un plugin que se cae cuesta su propia funcionalidad y nada más:
su error se registra y se muestra con /plugins.
Los del repositorio auditado no se ejecutan sin tu consentimiento. Cargar un plugin es lanzar su código aquí, bajo tu cuenta — y el repositorio auditado es precisamente aquel del que Thot desconfía. Sus plugins se nombran, nunca se importan, hasta que los hayas aprobado:
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/xLa aprobación recae sobre el contenido, no sobre el nombre: Thot registra una huella de la carpeta, y cualquier modificación la revoca diciéndolo.
Se entregan tres:
Plugin | Lo que hace |
| relee lo que el modelo escribe y hace subir una advertencia si aparece un patrón peligroso. No bloqueante — un falso positivo que bloquea una sesión es peor que la escritura. |
| un defecto marcado |
| un diario JSONL local de cada auditoría, veredicto y escritura, en |
Verificar que todo está ahí
«Funciona» es una afirmación, y sobre un programa hecho de tres programas, no es una afirmación para creer a ciegas — sobre todo viniendo de la propia herramienta.
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 sInstantánea fechada, no un contrato: mémoire cuenta los veredictos acumulados y
solo puede crecer, skills depende de lo que está instalado en la máquina.
Lo estable es la forma — cada línea lleva un número medido, y
12/12.
La última línea es la salida real en la máquina de desarrollo, y
se conserva tal cual: es lo que el control sirve para producir. El
1 passage(s) viene de launchctl mismo, y no es decorativo: un
launchctl list muestra la unidad cargada, su LastExitStatus vale 0 y su
diario no existe — tres señales que dicen «todo va bien» para una
tarea que nunca ha arrancado. Nombrar la causa vale más que contar las
líneas verdes.
Esta verificación tuvo que corregirse a sí misma. Condenaba la tarea por la
forma de una ruta — «el árbol está bajo ~/Desktop, así que launchd no
podrá leerlo» — cuando los permisos de macOS se conceden por
binario: el intérprete de la unidad leía el árbol que a /bin/sh se le
denegaba. Una forma es una sospecha, un paso es un hecho, y es el hecho
lo que ahora interroga.
Y una verificación que ninguna inspección estática habría podido hacer:
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 ipythonLas líneas de escritura son verdes aunque anuncian una capacidad
incómoda: informan de lo que hay, no de lo que se querría. Dos de los tres
agentes pueden escribir y ninguna bandera lo impide — medido pidiéndoles
crear un archivo, y luego mirando el disco. Lo que no puede
impedirse se hace imposible de pasar por alto: AuditResult.touched nombra lo que una
pasada ha modificado, y el bucle nocturno lo grita en stderr.
Planta un archivo en una carpeta temporal y pide su contenido. Existe por un defecto real: Hermes no abría una ruta relativa a su carpeta de trabajo y respondía con una frase que se leía como una negativa. Un tercio del panel no podía verificar ninguna afirmación que descansara en un segundo archivo, y nada más que plantar un archivo lo habría mostrado.
Cada línea ejecuta una operación real e informa de lo que ha medido:
no «skills: configurado» sino «91 cargadas, 0 rechazadas». El motor de
tinte busca una ruta en una muestra de los dos lenguajes, el servidor MCP
responde a su propio protocolo. Una verificación que no puede ejecutarse
falla en lugar de pasar en silencio: una línea verde que significa «no
probado» es peor que una roja. Nada toca la red ni un modelo —
thot doctor en un avión da la misma respuesta que en una oficina. Salida no
nula en caso de fallo, para caber en un && o una CI.
La mejora permanente
Una auditoría que argumenta veinte candidatos y se detiene deja el resto sin
juicio para siempre. Una pasada sin presupuesto sigue corriendo cuando
vuelves a sentarte. thot improve es el punto intermedio: rondas acotadas, cada una
escrita en disco, cada una retomando donde la anterior se detuvo.
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 permanenteLa unidad escrita lleva su propio PATH. launchd da a un trabajo
/usr/bin:/bin:/usr/sbin:/sbin, cron aún menos, y claude, hermes y
node no están en ninguna de esas carpetas — viven bajo ~/.local/bin.
Sin eso, la pasada nocturna no construía ningún motor, no juzgaba nada, y
salía con el código 0: launchd registraba un éxito cada noche,
indefinidamente. Un trabajo de este tipo que falla en silencio es indistinguible de un
trabajo que funciona, así que una pasada profunda privada de agente sale ahora en
error y lo dice.
La versión nocturna reporta lo que ha decidido, no lo que ha aparecido. La distinción importa: el mecanismo de informe de las auditorías programadas responde a «qué hay de nuevo por encima del umbral», que es la pregunta correcta para un barrido y la incorrecta para un juicio. Confirmar un MEDIUM ya presente en el informe es exactamente para lo que sirve el bucle — y eso no se lo habría reportado a nadie. Los archivos que una auditoría hubiera modificado se señalan en el mismo registro.
Un árbol que ya no tiene nada que juzgar cede su turno al siguiente. Medido
sobre el corpus real: thot tiene una cola vacía y prime un solo candidato,
por lo que un presupuesto de 20 por árbol gastaba 40 en árboles incapaces de
utilizarlos mientras Hermes esperaba ciento cincuenta. Una ronda de 20
se convierte en una ronda de 20, 20 y 60.
Una tercera propiedad la hace converger rápido: los fallos se cuentan. Un candidato cuyo agente supera su plazo, o cuyo modelo se niega a comprometerse, conserva su severidad — por lo que se retoma en primer lugar en la ronda siguiente, y en la siguiente. Medido sobre un hallazgo en un archivo de 1 660 líneas: cuatro intentos en tres pasadas, tres de ellos chocando contra el mismo muro. Tras dos fallos, pasa al final de la cola: sigue siendo elegible, nunca prioritario. Un éxito borra el recuento — un muro que era una tarde ocupada o una suscripción agotada no debe perseguir a un hallazgo para siempre.
Dos propiedades la hacen converger en lugar de dar vueltas: una refutación se memoriza, por lo que la siguiente selección la omite; una confirmación deliberadamente no se memoriza — un defecto real debe seguir apareciendo hasta que alguien lo corrija — por lo que el bucle lleva su propio conjunto de identificadores ya juzgados. Sin esto, cada ronda después de la primera gastaría todo su presupuesto en reargumentar lo que la primera acababa de confirmar.
Termina con lo que hay que hacer, antes de los totales:
À 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écisionUna refutación es administración; una confirmación es una noticia. Una refutación impugnada también: es el programa el que dice haberse recuperado antes de enterrar algo. Contarlas sin nombrarlas envía al lector a buscar en el registro — que es exactamente lo que ha ocurrido, cada vez, durante un día.
Nunca modifica código. «Mejora» significa aquí que el juicio del programa sobre sí mismo se vuelve más nítido y más barato: menos candidatos sin decisión, más decisiones en disco, cada una atribuible al agente que la tomó.
El termómetro, y el bucle que lo utiliza
Todo lo anterior mide a Thot con Thot. improve pregunta a un modelo si un
hallazgo es real. evolve vigilaba provenance, un informe que el motor
calcula sobre su propia salida. Ambos son circulares, y el círculo no tiene
nada de académico: la pasada profunda pagó 638 juicios por 9 confirmaciones
mientras una regla calificada con −100 % dormía — xml_unsafe_parse
señalaba defusedxml, es decir, exactamente el remedio que su propio mensaje
recomendaba. Nada en el programa podía verlo.
thot bench rompe el círculo. Mide a Thot contra código etiquetado como
vulnerable o sano por otra persona, a partes iguales, con la clase de
debilidad nombrada.
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é cacheEl corpus no está embebido — dieciocho mil archivos de terceros no tienen nada que hacer en este repositorio — por lo que la ruta siempre se indica, y cada suite se verifica contra la huella de su manifiesto. Un corpus cuyas etiquetas se han movido bajo una medición es peor que no tener corpus: todas las cifras posteriores son falsas y nada lo dice.
La puntuación es el J de Youden, TPR − FPR. Cero es un lanzamiento de
moneda, +100 es perfecto, y negativo significa que la regla está invertida.
Precisión y recall no lo habrían dicho: una regla sin ningún verdadero positivo
tiene una precisión indefinida, se muestra vacía y se lee como sin datos —
es exactamente así como sobrevive una regla invertida. El J no tiene ese
hueco.
Dos formas de hacer trampa, y ambas pierden. Encontrar menos — lo que hacía
subir provenance — hace caer el TPR. Señalarlo todo da TPR 100 %, FPR
100 %, J cero: el corpus está equilibrado 50/50 precisamente para eso.
Estado medido, umbral por defecto, tres frameworks (django, fastapi, flask):
avant après
TPR 9.9 % 34.4 %
FPR 0.5 % 0.0 %
J de Youden +9.4 % +34.4 %
catégories actives 10 24
catégories négatives 0 0«Antes» es el estado del programa en el momento en que el termómetro existió por primera vez. «Después» es el mismo corpus, el mismo umbral, el mismo comando. El hold-out lo confirma: flask mantenido aparte puntúa +33,3 %, django mantenido aparte +35,9 % — a menos de dos puntos del entrenamiento, por lo que las reglas funcionan sobre código que no sirvieron para escribir.
Por categoría, lo que el motor sabe hacer hoy:
xxe · tlsverify · weakhash · weakrand · weakcipher · weakkeylength
hardcodedcreds · default_credentials · cleartexttransmit · errormessage
debug_code_production · cookie_no_httponly · cookie_no_samesite
securecookie · directory_listing_exposure +100,0 %
deserial +98,0 % cmdi +93,3 % codeinj +86,0 %
eval_injection +84,0 % sqli +82,7 % cloud_ssrf_metadata +78,7 %
ssrf +64,7 % pathtraver +56,0 % xss / basic_xss +26,7 %Cero falsos positivos sobre el conjunto de los 18 300 casos, y ninguna
categoría negativa. El punto de partida era ssrf a −8,0 % y xxe a −100 %.
Lo que produjo estos veinticinco puntos, en el orden en que se midió:
cambio | J |
partida | +9,4 % |
las rutas web reconocidas como puntos de entrada | +9,6 % |
el matiz sigue un valor dentro de un contenedor | +10,0 % |
un ternario con ramas constantes no transporta nada | +12,4 % |
un | incl. |
guardas SSRF: lista blanca de hosts, rango de IP resuelto | +14,3 % |
| +14,9 % |
confinamiento de ruta y lista blanca nombrada | +15,5 % |
| +16,3 % |
doce reglas de patrón de una línea | +27,8 % |
un patrón ya no paga el descuento de accesibilidad | +34,4 % |
Tres de estos cambios fueron rechazados tras la medición, y es el
termómetro quien los rechazó: tratar una lectura de base de datos como fuente
no fiable (436 casos vulnerables, 343 sanos — tanto ruido como señal),
clickjacking (23 falsos positivos en este repositorio), y las cinco reglas
de inyección LDAP/XPath/NoSQL/SSTI/EL, cuyo J medido es exactamente
0,000: tiran tanto sobre la mitad sana como sobre la otra.
La precisión no se compra con un punto ciego
Cada guarda reconocida arriba se propuso primero en una versión que hacía subir la puntuación y abría un hueco explotable. Una sonda adversaria los encontró todos:
resolved = socket.gethostbyname(parsed.hostname or url)
if ipaddress.ip_address(resolved).is_private:
return "blocked", 403
os.system("curl -s " + url) # ← silencieux, et exploitableLa guarda es una defensa SSRF correcta y no dice nada sobre los metacaracteres de shell que siguen en la cadena. Purgar el matiz en todas partes compraba 174 falsos positivos menos y un punto ciego en inyección de comandos.
De ahí la distinción que porta el motor: una guarda que restringe el valor
(lista blanca literal, fullmatch enumerativo) lo blanquea para todo; una
guarda que prueba el destino (el host está autorizado, la dirección
resuelta es pública, la ruta está confinada) solo vale para la familia de
sinks concernida. Lo mismo para bleach.clean, que neutraliza HTML y deja
x; rm -rf / intacto.
Cuatro sondas adversarias están en la suite de pruebas y todas señalan correctamente. Una «mejora» futura que reabriera uno de estos huecos rompe un test con nombre.
El silencio restante tiene dos causas, y thot bench las separa porque son
dos trabajos diferentes:
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 à écrireLa distinción decide el orden de los objetivos — sin ella las categorías mudas están en empate perfecto a J = 0 y la ordenación recae en el orden alfabético.
La fusión, allí donde cambia algo
Cascade.turn elige un agente y lo llama; solo va a buscar al otro si el
primero devuelve un error. Una ronda así está limitada al mejor de los dos por
construcción: puede perder menos, nunca ganar más. agent_apply hacía lo
mismo — un motor, en singular.
thot evolve --fused hace trabajar a ambos, sobre mitades diferentes del
mismo problema:
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 lee la medición y escribe una especificación. No toca ningún archivo. Su salida es una afirmación sobre la causa: qué regla, qué línea, por qué esos casos.
Prime lee la especificación y escribe el código. Se le dice explícitamente que puede rechazarla si el código la contradice — un ejecutor incapaz de decir no es un relevo, y un relevo no añade nada.
Ninguno de los dos decide. La suite de pruebas es un umbral, el corpus etiquetado es el veredicto. Una especificación falsa aplicada con convicción hace bajar la puntuación y se anula byte a byte.
El orden tampoco es arbitrario. Diseñar-luego-construir se verifica en la unión: Prime ve el razonamiento de Hermes antes de comprometerse con él. Construir-luego-releer no lo permite — cuando el segundo mira, el primero ya ha decidido.
Los objetivos vienen de la medición, no de una frase tecleada. Hasta ahora el bucle solo podía perseguir lo que un humano ya sospechaba; un objetivo construido desde la puntuación es el programa que dice dónde es débil, en cifras que no ha elegido — y las mismas cifras dicen después si la respuesta ha servido. Cada objetivo porta los archivos que fallaron: un agente al que se le dice «xss está al 0 %» solo puede adivinar, un agente al que se le dan tres archivos fallidos tiene un problema que resolver.
El sobreajuste, y lo que --hold-out puede realmente
Un bucle puntuado sobre un corpus tiene una única forma real de hacer trampa:
aprender el corpus. Una regla calibrada sobre cómo se ve
BenchmarkTest01126 hace subir la puntuación y no sirve a nadie, y visto
desde fuera es indistinguible de un progreso real.
--hold-out flask saca una suite de la cifra principal y la guarda como
segundo guardarraíl: un cambio que mueve las suites contra las que se ha
optimizado y no la que nunca ha visto ha dicho lo que es. Ambos números se
mantienen ne_baisse_pas.
Su límite, medido: los tres frameworks puntúan a medio punto unos de otros. Eso atrapa el sobreajuste al archivo, no el sobreajuste a la forma del benchmark — un corpus generado sigue siendo un corpus generado, y una regla que solo ayuda sobre código de demostración pasaría los tres. El hold-out hace visible una trampa; no hace representativo al corpus.
Lo que el bucle retiene de una ronda a otra está escrito en
~/.thot/evolve-log.jsonl. Sin esto, como la medición apenas se mueve en una
ronda, la siguiente relee las mismas peores categorías, tiende los mismos
archivos y recibe — razonablemente — la misma especificación ya construida,
medida y anulada: --rounds 5 sería un intento probado cinco veces, cinco
veces más caro, con apariencia de estar ocupado.
No es un oráculo. Un parche puede ser verde, hacer subir el J y seguir siendo malo — eso es sobreajuste y la literatura sobre reparación automática de programas no habla de otra cosa. El corpus es una prueba de que se ha progresado sobre él. El bucle reporta lo que ha cambiado para que un humano pueda estar en desacuerdo.
Skills — los métodos que Thot conoce
Una skill es un método escrito una vez: un SKILL.md con un frontmatter YAML.
Es el formato de Hermes Agent y de Prime Agent, por lo que una skill
escrita para uno de los dos se carga aquí sin modificación, y lo inverso es
cierto.
Thot incorpora la biblioteca completa de Hermes Agent (MIT — ver
NOTICE.md): 90 métodos cargados, 117 más disponibles.
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èleCategorías cargadas: audit, security, software-development, github,
devops, research, mlops, productivity, creative, apple, email,
media, note-taking, smart-home, social-media,
autonomous-ai-agents.
El modelo las descubre con la herramienta skills — que responde con un índice de
nombres mientras no se le dé una palabra clave, porque doscientas
descripciones no son un catálogo — y lee la que corresponde con
skill. En sesión, /skills te muestra lo mismo.
Un método importado que cita una herramienta ausente aquí (delegate_task,
browser_navigate…) se sirve tal cual, con una nota que indica cuáles
faltan y qué usar en su lugar. El enfoque se traslada incluso cuando
la llamada a la herramienta no se traslada.
Añadir
~/.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.Ambas disposiciones son aceptadas: una carpeta plana (Prime Agent) o agrupada por categorías (Hermes Agent). Un nombre que ya existe reemplaza la versión integrada — suficiente para adaptar un método entregado sin hacerle un fork.
Un método proporcionado por el repositorio auditado se analiza primero
Un SKILL.md es texto entregado al modelo como instrucción. Los repositorios
que Thot lee son, por definición, aquellos de los que nadie responde. Un repositorio
hostil que deposite .thot/skills/x/SKILL.md escribiría una parte del briefing.
El guardián de Hermes Agent se traslada aquí y se aplica a todo lo que proviene del repositorio: inyección, exfiltración, persistencia, ofuscación.
▲ 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 <carpeta> plantea la misma pregunta bajo demanda. Lo que Thot
entrega él mismo no se analiza: está en disco porque el programa está
instalado, no porque un repositorio lo haya solicitado.
Comandos personalizados
Un archivo markdown es un comando. La gramática es la de Prime Agent, Claude Code y Codex — nada nuevo que aprender.
---
description: Relire un fichier sans rien modifier.
argument-hint: <chemin>
---
Relis $1 et dis-moi ce qui cloche. Ne modifie rien.En .thot/commands/revue.md, esto crea /revue src/app.py. Sustituciones:
$1, $2…, $@, $ARGUMENTS, ${@:2}, ${@:2:3}. Un argumento nunca se
reinterpreta. Los comandos del repositorio pasan por el mismo guardián que sus skills.
Se entregan tres: /triage (nombrar la entrada o clasificar sin seguimiento),
/harden (prueba que falla primero, corrección después), /regress (la auditoría
se compara contra una referencia git).
Servidores MCP
El catálogo de Hermes Agent, veinte servidores verificados:
thot mcp list # le catalogue, et ce qui est déjà connecté
thot mcp show sentry
thot mcp add linearLa instalación se delega en el CLI oficial, que ya posee OAuth y la renovación de tokens — Thot no tiene ninguna razón para tener una segunda caja fuerte que pueda filtrarse. Dice explícitamente que registrado no es autorizado, y qué comando termina el trabajo.
Las herramientas del modelo
Las clásicas — leer, escribir, editar, lanzar un comando. Toda escritura y toda ejecución requieren confirmación, y eso no es configurable.
Y cuatro que pertenecen solo a Thot, gratuitas porque interrogan el mapa y no al modelo:
Herramienta | Respuesta |
| los archivos del proyecto |
| archivo, líneas y parámetros de una función |
| quién llama a qué, y la distancia a un punto de entrada |
| los caminos de tinte fuente → sumidero |
En modo cuenta, estas cuatro se sirven al CLI oficial mediante
thot.mcp_server — un servidor MCP de solo lectura, incapaz de escribir o
ejecutar nada.
Cuando el modelo busca quién llama a process_payment, interroga el grafo y
obtiene la respuesta completa — en lugar de hacer grep a tres archivos al azar.
Modo solo auditoría
El núcleo de análisis también se usa sin modelo, sin red, sin coste:
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 — entrar en una cadena que ya existe
Un informe que ningún pipeline sabe leer vive en un solo terminal. GitHub code scanning, GitLab, Azure DevOps y los editores leen todos SARIF 2.1, y dos propiedades de Thot valen más allí que en cualquier otro lugar.
La identidad de un hallazgo es regla + archivo + símbolo + huella del cuerpo,
nunca la línea — es exactamente lo que pide partialFingerprints. Un panel
alimentado con números de línea reabre todos sus tickets en cuanto
alguien añade un import al principio de un archivo; alimentado con esto, no.
Y un camino de tinte es una secuencia de posiciones, lo que codeFlows representa:
el lector hace clic de la fuente al sumidero en lugar de creerle a la herramienta
bajo palabra. Un hallazgo sin camino no lleva ninguna clave codeFlows — un flujo vacío
se representa como un camino de tinte sin pasos, lo que se lee como un análisis roto
y no como una coincidencia de patrón.
Un hallazgo refutado por el panel no se elimina: sale con una
suppressions justificada. Un panel que nunca lo ve no puede
distinguir «nadie ha mirado» de «alguien miró y decidió»,
y la segunda es toda la razón de ser del panel.
- run: thot audit . --out thot.sarif
- uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: thot.sarif }Análisis asistido — --deep
El análisis determinista responde a «¿estos datos podrían circular?».
Es exhaustivo, gratuito, y no es la pregunta que se le paga a un auditor para
decidir. --deep plantea la pregunta cara, solo sobre los candidatos que la han
merecido:
thot audit . --deep # 20 pires candidats, 4 en parallèle
thot audit . --deep --budget 50 # plus large
thot audit . --deep --parallel 8 # plus viteDos pasadas, deliberadamente adversarias:
La sonda debe nombrar una entrada concreta que alcanza el punto peligroso. Nada de generalidades sobre la clase de vulnerabilidad — una URL, un valor, un efecto.
La refutación recibe ese escenario con la única misión de destruirlo: validación previa, llamador que solo pasa constantes, tipo que prohíbe la entrada supuesta. En caso de duda, refuta.
Un hallazgo solo sobrevive si una segunda lectura hostil del mismo código falla al
matarlo. confirmed entonces significa algo.
En sesión, lo mismo: /audit deep.
El motor se elige automáticamente — tu cuenta de Claude vía el CLI oficial si está conectada (los análisis se ejecutan en paralelo, con tu suscripción), una clave API en caso contrario.
Lo que la auditoría no debe leer
# .thotignore, à la racine du dépôt
vendor/
*.generated.py
tests/fixtures/Las exclusiones integradas cubren lo que todo repositorio tiene — node_modules,
build, .venv. .thotignore cubre lo que solo ese repositorio sabe: documentación
embebida, un cliente generado, una carpeta de fixtures rotas
a propósito. Auditarlas no produce hallazgos, produce ruido en
el lugar exacto donde estarían los hallazgos.
Tus propias reglas
El catálogo integrado conoce la biblioteca estándar. No conoce el wrapper
que tu equipo escribió alrededor de subprocess, la cola que tu servicio
consume, ni el validador que hace segura una valor en tu entorno. Sin un lugar
donde decirlo, cada auditoría de un sistema real se equivoca en los mismos tres sitios.
# <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]El mismo archivo lleva las reglas JavaScript, bajo una clave js: — un
wrapper de equipo suele existir en ambos lenguajes, y separar la
declaración es la forma en que una mitad queda obsoleta.
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]Lo que el modelo pide es una entrada no confiable
Las fuentes son expresiones — sys.argv, os.environ. Esto cubre
un programa que se lanza y omite un programa al que se llama: la herramienta de un
agente recibe su entrada no confiable en parámetros nombrados, rellenados por
un registro a partir de lo que un modelo ha pedido, y ninguna expresión
aparece en ninguna parte del cuerpo.
El coste medido de no modelar esto: cuatro SSRF en una tarde, todas alcanzadas por un argumento de herramienta, ninguna encontrada por el tinte — las encontraron reglas de patrón, que reconocen una forma y no prueban nada.
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: prefixVacío por defecto, y deliberadamente: qué funciones llama un registro
es un hecho sobre un repositorio, y adivinarlo pondría una fuente bajo cada
parámetro de cada programa. Medido en Hermes, los dos extremos: una
regla que nombra los paquetes plugins y tools revela 19 caminos probados
varios de ellos sobre-aproximados (el base_url que un helper recibe de la
configuración no es no confiable); una regla que nombra el parámetro args
revela cero, porque los manejadores de Hermes toman
parámetros nombrados y no un diccionario. La regla correcta nombra los puntos
de entrada reales — y les corresponde a sus autores conocerla.
Una regla que retoma un id integrado lo reemplaza — suficiente para degradar un
sumidero que el equipo ha aceptado deliberadamente, sin parchear Thot. Un archivo mal
formado detiene la auditoría nombrando el archivo y la clave defectuosa, en lugar de
hacer creer en una ausencia de hallazgo.
Las supresiones
Una supresión es la única afirmación sobre seguridad que ninguna herramienta
relee — incluida esta, por construcción. # nosec, # noqa: S310,
// eslint-disable … security/…: es una afirmación sobre el código, escrita
una vez, que sobrevive a los llamadores que describía.
Dos veces en una misma auditoría, aquí, era falsa:
supresión | lo que afirmaba | lo que era cierto |
| el esquema está controlado | detenía |
| la URL viene de la configuración | uno de los llamadores la lee en un argumento de herramienta, es decir, del modelo |
Thot las reporta por tanto como clase, en LOW, con el patrón escrito al lado.
El hallazgo no dice «esta línea es peligrosa»: dice «nadie ha
releído la razón por la que fue excusada». En una pasada --deep,
es un agente el que va a verificar si el patrón sigue en pie.
Para Python, se leen los tokens de comentario reales, no
patrones — una expresión regular no distingue # nosec en un
comentario del mismo texto citado en una docstring, y la docstring de este
módulo cita dos.
Una supresión colocada en una línea que esta auditoría señala no es el mismo objeto: es una afirmación que contradice un hallazgo vivo, escrita por alguien que leyó la misma línea y concluyó otra cosa. Se eleva un nivel y lo dice. Medido en Hermes: 7 de 45 — y tres de las supresiones leídas ese día eran falsas.
Medido: 0 en Thot, 0 en Prime, 45 en Hermes.
Calibración
La precisión importa tanto como la detección. Deliberadamente no se reportan:
subprocess.run(cmd)sinshell=True— ningún shell lee el comando.cursor.execute("… ?", params)— consulta literal, parámetros vinculados.Un valor pasado por
int(),shlex.quote(),os.path.basename(),html.escape()— estas llamadas rompen la cadena de contaminación.Un defecto que ningún punto de entrada alcanza se degrada automáticamente — pero solo si se han encontrado puntos de entrada. Sin ninguno, el alcance es desconocido, no nulo, y nada se entierra sobre esa ignorancia.
payload.get(...)no esrequests.get(...).
Orden de magnitud, medido en Hermes Agent (4 457 archivos Python): 98 s, 365 hallazgos de los cuales 25 high — 3 por encima del umbral por defecto una vez aplicada la memoria.
Qué parámetro llena el argumento
Un helper cuyo un parámetro alcanza un sumidero no vuelve peligroso todo lo que se le pasa. El motor emparejaba sin embargo al llamador con el conjunto de sumideros del callee, sin mirar dónde aterrizaba el argumento:
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éLa posición ahora se lee en el sitio de la llamada, y el nombre para un argumento nombrado. Es lo que el motor JavaScript ya hacía por su lado.
Medido, una vez posible la ejecución: de 83 238 resoluciones solicitadas al
analizar Thot y hermes/tools, el 90 % designa un parámetro único y el 9,8 %
no designa ninguno; el 0,1 % restante queda abierto (f(*rest), f(**options)) y
mantiene la respuesta amplia. El callee ofrecía 2,68 parámetros de media, así que
casi dos tercios del espacio de búsqueda desaparecen — el análisis de
hermes/tools pasa de 11,4 s a 9,5 s.
En cuanto al número de findings, en cambio: ningún cambio. Thot, prime y
hermes/tools devuelven exactamente los mismos 151 candidatos antes y después, de
los cuales cero inventados — la propiedad de seguridad se cumple sobre el terreno. Cuatro
caminos de tinte se acortan, nada más. La forma corregida arriba es real
y el test lo demuestra, pero no se produce en ninguno de los tres árboles
medidos: este correctivo compra velocidad y caminos más justos, no
menos ruido.
Dos formas no dicen nada: helper(*args) despliega un número desconocido de
valores, y helper(**options) no nombra ninguno. Ahí, el motor mantiene la
respuesta amplia que daba antes — el conjunto retenido sigue siendo incluso
en el de antes, por lo que afinar solo puede quitar un finding, nunca
inventar uno.
El receptor se omite basándose en la firma del callee, no en la sintaxis
de la llamada: Runner().go(x) se resuelve en un go sin punto, y Cls.m(obj, x)
igual que obj.m(x) son ambos un atributo. Como un método casi siempre se
llama enlazado, es self al frente de los parámetros lo que decide — la llamada no
enlazada es la única forma leída un escalón demasiado corto.
Lo que el grafo no puede seguir
Un defecto alcanzado por un camino que el análisis no resuelve — un handler guardado en una tabla de dispatch, una vista decorada, una llamada sobre una variable cuyo tipo es desconocido — no es un defecto inalcanzable. Thot distingue ambos:
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 nomEn los tres casos el alcance es desconocido, no nulo, y el finding conserva una penalización leve en lugar de ser enterrado. En Hermes: mismos 365 findings, pero 60 suben un escalón. Una función que nadie llama y que nadie menciona sigue, correctamente, devaluada — si no, el filtro dejaría de ser un filtro.
Límites
El tinte a través de los archivos está reservado a Python: JavaScript y TypeScript están indexados, se siguen dentro de un cuerpo de función y hasta un helper del mismo archivo, y se detienen ahí por falta de un grafo de llamadas resuelto — la tabla de arriba lo dice línea por línea. Las reglas por patrón, en cambio, se aplican en todas partes.
Sin --deep, cada finding es PLAUSIBLE: detectado estáticamente, aún no
probado por ejecución. Con --deep, un finding confirmed ha sobrevivido
a una refutación adversaria — aún no es una prueba de ejecución, que
vendrá con el repro. Y la ausencia de findings no es prueba de ausencia
de defectos: dispatch dinámico, reflexión y metaprogramación escapan al
análisis.
La búsqueda de texto completo en las sesiones deja de ser de costo constante en los SQLite cuyo fts5 no sabe remontar los rowid: el motor ordena ahí el conjunto de coincidencias en lugar de cortar en veinte. Medido en el que incluye CPython 3.12 — 6 588 instrucciones contra 27 443 cuando el corpus se multiplica por cinco, donde un motor capaz se mantiene en 2 215 y luego 2 698. Un test lo verifica y nombra la versión cuando no lo permite.
Desarrollo
cd Thot
uv run pytest -qInstalado en modo editable: el código fuente es la fuente de verdad de inmediato. En cambio, si
pyproject.toml cambia (nueva dependencia), hay que relanzar
uv tool install --editable --from . thot --force.
El núcleo determinista (codemap, taint, scope, scoring, store,
report) no depende de ningún agente y no toca la red — un test lo
verifica y hace fallar la suite si eso cambia.
Spec y planes: docs/superpowers/.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Remote MCP for A2A dependency inspector MCP, structured receipts, audit logs, and reviewer-ready evi
Static MCP manifest and tool-policy security preflight with signed input-redacted receipts
A paid remote MCP for CodeG, built to return verdicts, receipts, usage logs, and audit-ready JSON.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/nobodyohm-web/thot'
If you have feedback or need assistance with the MCP directory API, please join our Discord server