Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions .github/workflows/agfast-quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: AGFast Quality

on:
push:
branches:
- main
- "fase-*"
pull_request:
branches:
- main
workflow_dispatch:
schedule:
- cron: "0 8 * * 1"

jobs:
build-and-test:
name: Build, tests and evaluation
runs-on: ubuntu-latest

steps:
- name: Descargar repositorio
uses: actions/checkout@v4

- name: Instalar dependencias base
run: |
sudo apt-get update
sudo apt-get install -y build-essential make python3 libssl-dev

- name: Compilar proyecto
run: |
make clean
make

- name: Ejecutar pruebas principales
run: |
make test-fastpath
make test-regression
make test-streaming
make test-guardsketch
make test-unit

- name: Ejecutar evaluacion experimental reproducible
env:
AGFAST_EVAL_EVENTS: "10000"
AGFAST_EVAL_PIDS: "300"
AGFAST_EVAL_DIR: "/tmp/agfast_ci_evaluacion"
run: |
bash scripts/run_evaluation.sh

- name: Limpiar artefactos locales
run: |
make clean
git status --short

valgrind:
name: Valgrind optional
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' || github.event_name == 'schedule'

steps:
- name: Descargar repositorio
uses: actions/checkout@v4

- name: Instalar dependencias con Valgrind
run: |
sudo apt-get update
sudo apt-get install -y build-essential make python3 libssl-dev valgrind

- name: Compilar proyecto
run: |
make clean
make

- name: Ejecutar Valgrind
run: |
make test-valgrind

- name: Limpiar artefactos locales
run: |
make clean
git status --short
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
### AgentGuard-C y AgentGuard FastPath

[![AGFast Quality](https://github.com/kapumota/AgentGuard-FastPath/actions/workflows/agfast-quality.yml/badge.svg)](https://github.com/kapumota/AgentGuard-FastPath/actions/workflows/agfast-quality.yml)

[![CI](https://github.com/kapumota/AgentGuard-FastPath/actions/workflows/ci.yml/badge.svg)](https://github.com/kapumota/AgentGuard-FastPath/actions/workflows/ci.yml)

AgentGuard FastPath es un software de análisis defensivo de eventos de seguridad escrito en C. Procesa telemetría en formato JSONL o CSV, aplica una política configurable y produce métricas, alertas, relaciones proceso-archivo-red, líneas de tiempo y puntajes de riesgo.

El repositorio contiene dos componentes:
Expand Down
105 changes: 105 additions & 0 deletions docs/CI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
### CI/CD visible

#### Objetivo

La Fase 9.2 agrega validación automática con GitHub Actions.

El objetivo es que cada push y cada pull request ejecuten compilación, pruebas y evaluación mínima antes de fusionar cambios a `main`.

#### Archivo principal

```text
.github/workflows/ci.yml
```

#### Eventos del workflow

El workflow se ejecuta en:

- push a `main`;
- push a ramas `fase-*`;
- pull request hacia `main`;
- ejecución manual con `workflow_dispatch`;
- ejecución programada semanal.

#### Job principal

El job principal ejecuta:

```bash
make clean
make
make test-fastpath
make test-regression
make test-streaming
make test-guardsketch
make test-unit
bash scripts/run_evaluation.sh
make clean
```

#### Evaluación en CI

La evaluación experimental usa un dataset reducido para evitar tiempos excesivos en GitHub Actions.

Variables usadas:

```text
AGFAST_EVAL_EVENTS=10000
AGFAST_EVAL_PIDS=300
AGFAST_EVAL_DIR=/tmp/agfast_ci_evaluacion
```

Esto mantiene el flujo reproducible sin generar archivos pesados dentro del repositorio.

#### Job opcional de Valgrind

Valgrind se ejecuta en un job separado.

Ese job corre solo en:

- ejecución manual;
- ejecución programada semanal.

Motivo:

- Valgrind puede ser más lento;
- no debe bloquear cada push pequeño;
- sigue disponible para validación profunda.

Comando ejecutado:

```bash
make test-valgrind
```

#### Badge en README

El README incluye un badge de estado:

```text
CI
```

Ese badge permite verificar rápidamente si el proyecto compila y pasa pruebas.

#### Criterio de aceptación

La Fase 9.2 se considera correcta si:

- GitHub Actions aparece en la pestaña Actions;
- el workflow `CI` se ejecuta en PR;
- el job principal pasa;
- el badge aparece en README;
- Valgrind queda disponible como validación manual o programada;
- la ruta normal del proyecto no cambia.

#### Alcance

Esta fase no modifica código fuente.

Esta fase no modifica pruebas existentes.

Esta fase no agrega dependencias runtime al proyecto.

Esta fase solo agrega automatización de calidad.
85 changes: 76 additions & 9 deletions scripts/run_evaluation.sh
Original file line number Diff line number Diff line change
Expand Up @@ -250,19 +250,57 @@ run_timed() {
local label="$1"
shift
local out_file="$EVAL_DIR/time_${label}.txt"
local status_file="$EVAL_DIR/status_${label}.txt"
local status=0

set +e
/usr/bin/time -f "%e %M" -o "$out_file" "$@"
status=$?
set -e

echo "$status" > "$status_file"
return "$status"
}

accept_status() {
local label="$1"
local status="$2"
shift 2

for allowed in "$@"; do
if [ "$status" -eq "$allowed" ]; then
echo "$label retorno codigo $status aceptado para esta evaluacion"
return 0
fi
done

echo "$label retorno codigo $status no esperado" >&2
return "$status"
}

echo "Ejecutando agfast sobre dataset de evaluacion"
run_timed agfast ./bin/agfast analyze "$EVENTS_FILE" \
if run_timed agfast ./bin/agfast analyze "$EVENTS_FILE" \
--policy "$POLICY_FILE" \
--risk \
--report "$REPORT_JSON" \
--html "$REPORT_HTML" \
--alerts-csv "$ALERTS_CSV"
--alerts-csv "$ALERTS_CSV"; then
echo "agfast finalizo sin codigo de alerta"
else
status=$?
# En evaluacion, codigo 2 se acepta como hallazgo de alertas/riesgo.
# No representa fallo del binario si los reportes fueron generados.
accept_status "agfast analyze" "$status" 2
fi

echo "Evaluando robustez con entradas corruptas usando tail"
run_timed corrupt_tail ./bin/agfast tail "$CORRUPT_FILE" --policy "$POLICY_FILE" > "$TAIL_OUT"
if run_timed corrupt_tail ./bin/agfast tail "$CORRUPT_FILE" --policy "$POLICY_FILE" > "$TAIL_OUT"; then
echo "tail con entradas corruptas finalizo sin codigo de alerta"
else
status=$?
# Se acepta codigo 2 si tail detecta alertas durante la evaluacion.
accept_status "agfast tail" "$status" 2
fi

echo "Ejecutando simulacion de ranking de sketches"
python3 experiments/sketch_rank_simulation.py > "$SKETCH_SIM_OUT"
Expand Down Expand Up @@ -332,19 +370,41 @@ def read_time(label):
p = out_dir / ("time_%s.txt" % label)
if not p.exists():
return {"seconds": None, "max_rss_kb": None}
raw = p.read_text(encoding="utf-8").strip().split()
if len(raw) >= 2:
return {"seconds": float(raw[0]), "max_rss_kb": int(raw[1])}

# /usr/bin/time puede agregar una linea como:
# Command exited with non-zero status 2
# Por eso se busca la ultima linea con dos valores numericos.
lines = p.read_text(encoding="utf-8", errors="ignore").splitlines()
for line in reversed(lines):
raw = line.strip().split()
if len(raw) >= 2:
try:
return {
"seconds": float(raw[0]),
"max_rss_kb": int(float(raw[1])),
}
except ValueError:
continue

return {"seconds": None, "max_rss_kb": None}

def read_status(label):
p = out_dir / ("status_%s.txt" % label)
if not p.exists():
return None
raw = p.read_text(encoding="utf-8").strip()
return int(raw) if raw.isdigit() else None

report_size = report_path.stat().st_size if report_path.exists() else 0
alerts_size = alerts_path.stat().st_size if alerts_path.exists() else 0
tail_text = tail_path.read_text(encoding="utf-8", errors="ignore") if tail_path.exists() else ""
robust_tail = "Eventos procesados en tail" in tail_text

summary = {
"agfast": read_time("agfast"),
"agfast_exit_code": read_status("agfast"),
"corrupt_tail": read_time("corrupt_tail"),
"corrupt_tail_exit_code": read_status("corrupt_tail"),
"precision_alerts_vs_labels": precision,
"recall_alerts_vs_labels": recall,
"true_positives": tp,
Expand Down Expand Up @@ -381,6 +441,13 @@ md.append("| tail con entradas corruptas | {seconds} | {rss} |".format(
rss=summary["corrupt_tail"]["max_rss_kb"],
))
md.append("")
md.append("#### Códigos de salida")
md.append("")
md.append("| Componente | Código | Interpretación |")
md.append("|---|---:|---|")
md.append("| agfast analyze | %s | 0 normal, 2 alertas detectadas aceptadas en evaluación |" % summary.get("agfast_exit_code"))
md.append("| tail con entradas corruptas | %s | 0 normal, 2 alertas detectadas aceptadas en evaluación |" % summary.get("corrupt_tail_exit_code"))
md.append("")
md.append("#### Calidad de detección")
md.append("")
md.append("| Métrica | Valor |")
Expand All @@ -397,14 +464,14 @@ md.append("#### Tamaño de reportes")
md.append("")
md.append("| Archivo | Bytes |")
md.append("|---|---:|")
md.append("| reporte JSON | %d |" % report_size)
md.append("| alertas CSV | %d |" % alerts_size)
md.append("| reporte JSON | %d |" % summary["report_size_bytes"])
md.append("| alertas CSV | %d |" % summary["alerts_size_bytes"])
md.append("")
md.append("#### Robustez")
md.append("")
md.append("| Prueba | Resultado |")
md.append("|---|---|")
md.append("| tail con líneas corruptas | %s |" % ("ok" if robust_tail else "fallo"))
md.append("| tail con líneas corruptas | %s |" % ("ok" if summary["robust_tail_with_corrupt_lines"] else "fallo"))
md.append("| GuardSketch userspace | %s |" % guardsketch_status)
md.append("")
md.append("#### Archivos generados")
Expand Down
Loading