Skip to content

domains(redteam): add admina-redteam detection-efficacy suite#29

Merged
stefanoferi merged 2 commits into
admina-org:mainfrom
lorenzofradeani:feat/redteam-efficacy-suite
Jul 2, 2026
Merged

domains(redteam): add admina-redteam detection-efficacy suite#29
stefanoferi merged 2 commits into
admina-org:mainfrom
lorenzofradeani:feat/redteam-efficacy-suite

Conversation

@lorenzofradeani

Copy link
Copy Markdown
Contributor

Summary

admina-redteam: una suite riproducibile di efficacia di detection che misura quanto
i detector di Admina riconoscono gli attacchi (recall / precision / F1 / FPR), non solo
quanto sono veloci. Esegue il firewall anti-injection, il redattore PII e il loop-breaker su
corpora originali multilingue (EN/IT/FR/ES/DE) sigillati con hash, su entrambi i motori
(Python e Rust)
, e produce uno scorecard JSON + Markdown con la matrice Python-vs-Rust per
classe. Un gate CI "soft" fa fallire la build su regressioni di recall o nuovi falsi
positivi rispetto a un baseline committato. È solo misurazione: nessun detector viene
modificato. Risponde al gap dichiarato in MODEL_CARD.md ("Admina non spedisce una suite di
benchmark di accuratezza… i contributi sono benvenuti e prioritari").

Type

  • Bug fix
  • New feature
  • Documentation
  • CI / tooling
  • Refactor / cleanup

Primo baseline misurato (corpus proprio di Admina, non un punteggio PINT di terze parti)

Detector Recall Python Recall Rust Falsi positivi (py · rust)
injection 57% 35% 0/27 · 0/27
pii 100% 66% 6/16 · 0/16
loop 82% 91% 0/11 · 0/11

Gap reali emersi (matrice completa per classe: python scripts/redteam.py --format md):

  • Firewall Rust 0% sugli attacchi offuscati (base64 / homoglyph / leetspeak / ROT13 /
    hyphenation) che il Python cattura al 100%: al motore Rust manca il passaggio
    normalize_text(), quindi il path "veloce" è anche il meno accurato.
  • PII Rust 0% su IBAN / codice fiscale / DNI (è solo-regex, con meno pattern del Python).
  • Loop-breaker Python si perde i loop "counter-reset" (finestra sugli ultimi 5 messaggi)
    che il motore Rust (finestra piena) invece cattura.
  • I 6 falsi positivi PII del Python sono lo spaCy NER che etichetta erroneamente
    PERSON/ORG su frasi negative non inglesi (il Rust, solo-regex, non li produce).

Come si usa

python scripts/redteam.py                                    # scorecard completo, entrambi i motori
python scripts/redteam.py --corpus injection --engine python --format md

Il gate CI è in tests/test_redteam_efficacy.py e confronta l'esecuzione live con il
committato admina/redteam/baselines/baseline.json.

Design

  • Libreria admina/redteam/ (metriche → loader corpora → adapter dei detector → runner →
    report), CLI sottile scripts/redteam.py, e un test pytest che gira dentro il job
    python-tests esistente
    (nessun nuovo workflow CI).
  • Corpora originali, Apache-2.0, sigillati con SHA-256; il loader verifica gli hash al
    caricamento (e li dichiara in [tool.setuptools.package-data] così finiscono nel wheel).
  • Nessuna nuova dipendenza runtime (stdlib + i detector già presenti in admina.domains).
  • Metriche con denominatore zero → null (saltate dal gate, non trattate come regressioni);
    il motore Rust viene saltato se admina_core è assente (come in test_firewall_parity.py).

Note tecniche

  • Il loop-breaker Python dipende da numpy/scikit-learn (extra [proxy], già esistente):
    l'adapter del loop fa degradare con grazia (motore Python segnato non disponibile) se non
    sono installati, invece di andare in errore.
  • Il baseline Rust è misurato con admina_core 0.9.3 (wheel PyPI). Il gate salta il Rust
    se l'engine è assente; non gestisce un eventuale drift di versione del wheel — felice di
    vincolare la versione o rendere il confronto Rust "advisory" se preferite.

Fuori scope

Classificatore ML/semantico per injection; adapter per dataset esterni
(garak/PromptInject); chiusura del gap di parità del firewall Rust (questa suite ne è il
criterio d'accettazione); corpora più ampi e con più lingue.

Domande

  • Nome del package: admina/redteam/ o admina/bench/efficacy/?
  • Posizione dello scorecard: MODEL_CARD.md (attuale) o un docs/efficacy.md dedicato?
  • Scope del commit: ho usato domains(redteam): (CONTRIBUTING non ha uno scope bench/test).

Checklist

  • I test passano in locale (26 test della suite redteam); ruff e bandit puliti
  • Header Apache-2.0 su tutti i nuovi file; commit con scope domains(redteam):
  • Nessuna nuova dipendenza runtime; nessun nuovo workflow CI
  • Solo misurazione: nessun detector è stato modificato
  • Corpora originali (nessuna riga copiata da dataset di terze parti), sigillati con hash

@stefanoferi stefanoferi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed across metrics, detector integration, gate/methodology, corpus integrity, and packaging. Ran the suite locally, recomputed the corpus hashes, and tested it against current main. Requesting changes on the items below before merge.

Verified

  • No eval/exec/subprocess/network/pickle; corpora SHA-256-verified before load (digests recomputed, match).
  • Metrics math correct: recall/precision/FPR/F1, zero-denominator → None, recall over positives / FPR over negatives; baseline reduces to exact integer TP counts.
  • Measurement-only: git diff over admina/domains|core|proxy|sdk is empty.
  • run_suite() runs against main's post-refactor detectors; injection 21/37, loop 9/11 match the baseline.
  • No new runtime deps; package-data globs ship corpora in the wheel; tests cover tamper-detection, metric guards, bucketing, CLI e2e. RUST_CAVEAT emitted with every scorecard.

Blocking

  1. Gate can pass vacuously if a Python detector stops running. tests/test_redteam_efficacy.py:42-43 if cur is None: continue is meant for the absent Rust engine but also skips a dropped Python engine (LoopAdapter.engines() omits python when the [proxy] extra is missing) → green build on the worst-case regression. Treat Python as mandatory and Rust optional; assert the run covered every Python entry the baseline declares.
  2. PII recall is a type-level micro-average (metrics.py:47, runner.py:45-52) reported under the same "recall" key as the binary sample-level recall. Rename (type_recall) and document the averaging.
  3. PII baseline is environment-dependent. The 100% Python recall was measured with spaCy; without en_core_web_sm the redactor falls back to regex-only and diverges, which can trip the gate as a false regression. Pin the PII env (regex-only vs nlp) in the baseline metadata.

Process

  • Rebase onto main (~88 commits behind; no CI has run on this branch). main's MODEL_CARD §1 already states the engines are not behaviorally equivalent, so the equivalence mismatch resolves on rebase — verify §9 aligns with §1 and watch for a pyproject.toml overlap.

Non-blocking follow-ups (corpus size/denominators, label provenance, CLI entry point, fp-rate invariant, corpus edge cases) tracked in #37.

@lorenzofradeani

Copy link
Copy Markdown
Contributor Author

Perfetto grazie mille, mi metto al lavoro.

1 — Gate "a vuoto" su detector Python assente. Logica del gate estratta in un modulo puro e testato (admina/redteam/gate.py::compare): Python obbligatorio, Rust opzionale. Una voce python dichiarata in baseline ma non eseguita ora fa fallire la build (prima passava verde). Aggiunta anche la reverse coverage: una voce python eseguita ma non dichiarata in baseline fallisce → una baseline troncata non passa a vuoto.

2 — recall PII ambiguo. Rinominato in type_recall (micro-average sui tipi di PII), distinto dal recall binario sample-level; semantica documentata in metrics.pii_scores, in runner e in MODEL_CARD 9.

3 — Baseline PII environment-dependent. La modalità di misura è ora ancorata nei metadata della baseline (nlp:<model>@<version> vs regex). Il gate confronta solo a parità di modalità; se un detector obbligatorio non riproduce la modalità ancorata fallisce con messaggio azionabile, anziché generare una falsa regressione. La versione del modello è inclusa nella firma perché un upgrade del NER cambierebbe i numeri a parità di nome.

Process — Rebase. Branch rebasato su main (0.10.1), pulito e senza conflitti: il §1 corretto ("not behaviorally equivalent") è ora in tree, allineato e con cross-reference. CI verde (45 test, ruff check/format ok); baseline riprodotta identica contro i detector di main — incluso il nuovo controllo Luhn sulle carte (i PAN del corpus sono Luhn-validi). schema_version portato a 2 per il cambio di shape della riga PII.
@lorenzofradeani lorenzofradeani force-pushed the feat/redteam-efficacy-suite branch from 4c338d2 to 83193c5 Compare June 20, 2026 17:14
@lorenzofradeani

Copy link
Copy Markdown
Contributor Author

Sistemato tutto come richiesto.

@stefanoferi stefanoferi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three blocking items verified fixed (gate.py Python-mandatory + reverse coverage, type_recall, env-pinned baseline mode) — confirmed both statically and behaviorally: the gate fails actionably without the pinned spaCy model and reproduces the baseline with it. Full suite green locally (1113 passed) and on CI. Non-blocking follow-ups tracked in #37. Thanks for the quick turnaround.

@stefanoferi stefanoferi enabled auto-merge (squash) July 2, 2026 17:50
@stefanoferi stefanoferi merged commit 9822d0f into admina-org:main Jul 2, 2026
15 checks passed
stefanoferi added a commit that referenced this pull request Jul 2, 2026
Adds a measurement-only suite that runs the injection firewall, PII
redactor and loop-breaker against SHA-256-sealed multilingual
(EN/IT/FR/ES/DE) corpora on both the Python and Rust engines and emits
recall/precision/FPR plus a per-class Python-vs-Rust matrix
(admina/redteam/, CLI scripts/redteam.py). A soft CI gate
(tests/test_redteam_efficacy.py) fails the build on recall regressions
or new false positives against the committed baseline.

Gate design (admina/redteam/gate.py::compare, pure and unit-tested):

- Python detectors are mandatory, Rust is optional: a python entry
  declared in the baseline but not executed fails the build. Reverse
  coverage makes an executed-but-undeclared python entry fail too, so
  a truncated baseline cannot pass vacuously.
- PII recall is reported as type_recall (a micro-average over PII
  types), distinct from the sample-level binary recall; semantics
  documented in metrics.pii_scores, the runner and MODEL_CARD §9.
- The PII measurement mode is pinned in the baseline metadata
  (nlp:<model>@<version> vs regex). The gate only compares metrics
  measured in the same mode; a mandatory detector that does not
  reproduce the pinned mode fails with an actionable message instead
  of surfacing a false regression. The model version is part of the
  signature because a NER upgrade changes the numbers under the same
  model name.

schema_version bumped to 2 for the PII row shape change. The baseline
reproduces identically against main's detectors, including the Luhn
check on cards (corpus PANs are Luhn-valid).

Co-authored-by: Stefano Noferi <stefanoferi@users.noreply.github.com>
@lorenzofradeani

Copy link
Copy Markdown
Contributor Author

Thanks for the merge

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants