Nucleo propio: oye con whisper.cpp, piensa con un modelo de Ollama, habla con Piper, y hace RAG sobre los apuntes del usuario. 100% local, sin cuentas ni claves. Escrito bajo una restriccion dura, 4 GB de VRAM: el cerebro y whisper comparten tarjeta y solo caben porque estan dimensionados para ello. El RAG usa embeddings estaticos con busqueda hibrida; la voz clonada se sirve de una cache de frases. Incluye instalador (install.sh), requisitos, y documentacion del stack, del manejo de root y de las acciones. Los apuntes indexados y el diario NO se incluyen: son privados y el .gitignore los bloquea.
110 lines
5.4 KiB
Markdown
110 lines
5.4 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
./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. `SLEEP` pasó 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-base` empató 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_BASE` en `reindexa.py`
|
|
reconstruye 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.
|