instalado.py lee los .desktop y los paquetes para saber que hay en el equipo y para que sirve cada cosa. indexa.py lo engancha, y con el los docs markdown de las herramientas clonadas en CODERS. Las dos fuentes son personales: se quedan en el indice de la maquina y no entran en el publico. busca_cli.py expone el mismo motor que usa el cerebro (saber.busca) para consultar el indice desde la terminal, con --n y --json. reentrena_noche.sh encadena las fases del reentrenamiento largo y las deja corriendo horas; cada fase es reanudable por su cuenta. eval del indice vivo: hit@1 43/57 (75%), hit@5 55/57 (96%), sim 0.909. |
||
|---|---|---|
| .. | ||
| correr_noche.sh | ||
| cosecha.py | ||
| eval_set.jsonl | ||
| evalua.py | ||
| experimento_embedder.py | ||
| README.md | ||
| reentrena_noche.sh | ||
| reindexa.py | ||
| sintetiza.py | ||
| sintetiza_catalogo.py | ||
Enriquecer el RAG a nivel de pentesting profesional
El RAG del naranja ya funcionaba, pero se dejaba fuera lo mejor que hay en el
disco. Medido antes de empezar: de las 72 notas del OSCP_Vault —la
metodologia OSCP escrita a mano, con comandos reales— había 0 en el índice.
indexa.py las descartaba por dos motivos a la vez: el nombre (SQL injection.md no lleva "notas" ni "guia") y la profundidad (viven a 5 niveles).
Este pipeline las rescata, multiplica su recuperabilidad con preguntas generadas, y mide que el resultado es mejor antes de tocar nada.
Correrlo
./correr_noche.sh # las cuatro fases, y decide si promociona
./correr_noche.sh --sin-promo # igual, pero deja el vivo intacto
Es reanudable: cada fase se salta si ya está hecha, así que si se corta, relanzarlo continúa. La fase larga (síntesis) es reanudable fragmento a fragmento.
Las cuatro fases
| Script | Qué hace | Coste | |
|---|---|---|---|
| 1 | cosecha.py |
rescata la metodología de COFRE que faltaba | segundos, CPU |
| 2 | sintetiza.py |
preguntas en castellano por cada fragmento | horas, modelo local |
| 3 | reindexa.py |
une todo y calcula vectores → saber.jsonl.nuevo |
minutos, CPU |
| 4 | evalua.py |
mide recuperación vivo vs candidato | segundos |
1 · Cosecha
Donde indexa.py es prudente (solo ficheros que parecen documentación, 3
niveles), esto es agresivo con el oro: las carpetas de metodología (OSCP vaults,
apuntes, cheatsheets) enteras, sin límite de profundidad. Fuera de ahí pone un
tope por herramienta: si un repo tiene más de 25 documentos, es una base de
datos (exploitdb son 29.925 ficheros), no unos apuntes, y solo se coge su
README. Así el oro no se ahoga en ruido.
2 · Síntesis — indexación multi-representación
El punto flaco de los embeddings estáticos: "cómo saco una shell reversa" no se
parece vectorialmente a bash -i >& /dev/tcp/.... La solución es embeber la
pregunta y devolver el fragmento. Por cada fragmento, el modelo local escribe
las preguntas que responde; se indexa la pregunta, pero lo que se le muestra al
cerebro es el fragmento literal.
Clave: el modelo solo escribe preguntas, nunca reescribe comandos. Si inventa una pregunta rara, esa entrada recupera peor y ya está; no corrompe una respuesta. Y el fragmento crudo sigue en el índice por su lado.
3 · Reindexado
Une el índice vivo (comandos Linux, glosario, fichas), la cosecha y la síntesis;
deduplica por prefijo; calcula los vectores con el mismo model2vec de siempre.
Escribe a saber.jsonl.nuevo. No toca el vivo. busca.py no cambia.
4 · Evaluación
eval_set.jsonl son ~45 preguntas de pentesting con las palabras que un
fragmento correcto debe contener. Mide hit@1, hit@5 y similitud media, del
índice vivo contra el candidato. Determinista y reproducible, sin juez-LLM.
La decisión, y por qué es segura
El índice vivo solo se sustituye si la evaluación dice que el candidato es
mejor, y antes se guarda saber.jsonl.antes-<fecha>. Si empeora, se queda el
vivo y el candidato espera revisión. Siempre reversible con un cp.
Ficheros que genera (en ../../datos/)
cosecha.jsonl la metodología rescatada, sin vectores
sintesis.jsonl las preguntas generadas (+ .hecho, el marcador)
saber.jsonl.nuevo el índice candidato, con vectores
informe_rag.txt los números de la evaluación
noche_rag.log el registro de la noche
saber.jsonl.antes-* copia del índice anterior, si se promocionó
Iteraciones posteriores a la primera noche
-
Troceado que no tira comandos cortos (
cosecha.py). El troceado por encabezado descartaba toda sección de menos de 60 caracteres, y eso perdía el oro:## Detección time-based+' AND SLEEP(5)-- -son 50. Ahora una sección corta se pega a la siguiente y cada fragmento lleva delante el título de la nota.SLEEPpasó de 0 a 13 apariciones en el índice; hit@1 80→82 %. El salto en la métrica es pequeño porque las preguntas que quedan son cross-lingual, y de eso no salva el troceado. -
experimento_embedder.py— mide, sin tocar nada, si un transformer (e5, bge) supera al model2vec estático. Resultado (16 ago): NO compensa.intfloat/multilingual-e5-baseempató con el estático (hit@1 86 %, hit@5 95 % los dos): arregla las 2 preguntas que el estático fallaba, pero rompe otras 2 distintas. A cambio pediría ~50-150 ms por consulta en CPU y una dependencia de torch en cada búsqueda. Medido antes de cambiar → el estático se queda. El trabajo de recuperación lo hace la búsqueda híbrida (coseno + bono por palabras), no el embedder, y por eso subir el embedder no mueve la aguja. Reproducible:./experimento_embedder.py. -
Re-síntesis limpia sobre el troceado nuevo (
SABER_BASEenreindexa.pyreconstruye desde el índice original en vez de apilar, para no inflar). Da a los comandos rescatados (SLEEP y demás) su gancho-pregunta en castellano, que es lo que de verdad ataca las consultas cross-lingual —no el embedder—.
Nota sobre la tarjeta
La síntesis usa el modelo local por 127.0.0.1:11434. Fija qwen3.5:4b-jarvis
(el naranja, que no inventa comandos). Si dejas el TUI verde abierto, ollama
tiene que cambiar de modelo en cada petición y la noche va más lenta: para la
síntesis más rápida, cierra el verde.