The heavyweight red-team engine. — l'antithèse offensive de Plume, le SOC blue-team. Python · stdlib-only core · sûreté d'abord (ROE fail-closed + ledger tamper-evident).
· by GuatX · usage autorisé uniquement ·
License: AGPL-3.0-or-later — open core · Enterprise edition commerciale → COMMUNITY_VS_ENTERPRISE.md
Console Forge — dashboard · catalogue de modules (les exploit/destructif sont gatés par les ROE) · findings mappés ATT&CK · lancement gouverné (hors-scope = VETO dur, chaque tir au ledger). Le registre compte 77 modules — compte MESURÉ par python3 -m forge.cli modules --json, pas lu sur cette capture.
Plume observe (la plume qui consigne, bleu). Forge frappe — et trempe les défenses de Plume (la boucle purple-team). Forge orchestre des modules d'attaque (recon → enum → exploit) où chaque action passe par une gate ROE fail-closed et est tracée dans un ledger d'engagement append-time, tamper-evident. Par défaut, Forge est INERTE : rien ne peut tirer tant que l'opérateur n'a pas armé chaque couche consciemment.
⚠️ Cadre autorisé uniquement (bug bounty in-scope, pentest sous contrat, CTF, infra propre). La gate ROE + le scope-guard + le ledger sont là pour imposer ET prouver l'autorisation. Passer un WAF/Cloudflare ≠ une faille : c'est un enabler d'accès, pas un bug.
📚 Documentation produit complète →
docs/— vue d'ensemble, architecture, installation, wizard de 1er déploiement, référence de configuration (env + settings), administration, concepts, référence CLI & API HTTP, standalone, modèle de sécurité, désinstallation, dépannage.
Toutes doivent être franchies pour qu'une action LIVE parte. Sinon : DRY_RUN (simulation) ou
VETO (refus dur).
| Couche | Question | Échec → |
|---|---|---|
| 1 — armé ? | engagement explicitement armé (--arm) ? |
DRY_RUN |
| 2 — scope ? | cible ∈ in_scope et ∉ out_scope ? (fail-closed : in_scope vide = rien) |
VETO |
| 3 — capacité ? | exploit/destructif ⇒ allow_exploit/allow_destructive explicites ? |
VETO |
| 4 — approuvé ? | action approuvée (--approve), ou mode auto ? |
DRY_RUN |
VETO (couche 2/3) n'est jamais simulé ni tiré. Toute erreur d'évaluation ⇒ VETO (fail-closed).
pip install -e . # met l'exécutable `forge` sur le PATH (forge = forge.cli:main)
forge doctor # diagnostic : modules opérationnels + outil/service attendu par moduleSans installation, tout est aussi accessible via python3 -m forge.cli <commande>.
🚀 Nouveau ? Le parcours opérateur bout-en-bout 100 % hors-ligne (seed + mock-Plume, aucun service externe) est dans
docs/GETTING_STARTED.md— install → scope → campagne/make demo→ console → matrice purple → rapport → vérif intégrité, avec un point de contrôle « ce que tu dois voir » à chaque étape.
# démonstration bout-en-bout — AUCUNE cible réelle, AUCUN I/O réseau
forge demo # ou: python3 -m forge.cli demo
# suite complète (stdlib, zéro réseau) : Python unittest + cargo test de la console
make test # = python3 -m unittest discover -s tests -t . + (cd console && cargo test)
python3 -m unittest discover -s tests -t . # Python seul (1513 tests — compte MESURÉ : `Ran 1513 tests` / `OK (skipped=1)`)
# vérifier l'appartenance d'une cible
forge scope-check app.exemple.test --scope scope.json
# planifier (montre le verdict ROE de chaque action, ne tire rien)
forge plan --scope scope.json --actions actions.json
# exécuter — INERTE par défaut ; il faut armer ET approuver pour tirer
forge run --scope scope.json --actions actions.json \
--ledger engagements/e1.jsonl --report rapport.md # tout en DRY_RUN
forge run --scope scope.json --actions actions.json \
--arm --approve demo.fingerprint:app.test --ledger engagements/e1.jsonl
# intégrité de l'engagement
forge ledger verify --ledger engagements/e1.jsonlCopier scope.example.json → scope.json et renseigner in_scope avec autorisation écrite.
scope.json, *.key, *.jsonl sont gitignorés (secrets / état d'engagement).
Un engagement de référence synthétique est fourni dans
examples/reference-engagement/ (hôtes .example réservés, IP de
doc — aucune cible réelle, aucun SOC réel). Il peuple immédiatement les onglets
Findings / Coverage / Purple / Runs :
make demo # amorce la base démo + lance la console peuplée -> http://127.0.0.1:7100
make demo-purple # idem + stub mock-Plume (DEMO FIXTURE) -> matrice détecté/raté/MTTD (7 tirés, 4 détectés, 3 ratés)make demo lance forge seed-demo --dir examples/reference-engagement, qui ingère les
fixtures directement dans SQLite (idempotent, sans réseau, ne touche que la campagne démo
acme-lab). Le write-up commercial correspondant :
examples/reference-engagement/REFERENCE_ENGAGEMENT.md.
Fork minimal de la colonne Plume (axum + rusqlite, binaire unique) : store du modèle ROUGE
(finding/runrecord), API, et le point de jonction purple (POST /api/ingest reçoit les
findings + run-records ATT&CK du moteur Python ; Plume corrèle ensuite par champ mitre).
cd console && cargo build --release # compile offline depuis le cache cargo
# (optionnel) activer l'auth opérateur : hash argon2id du mot de passe, jamais en clair
HASH=$(./target/release/forge hashpw 'mon-mot-de-passe')
FORGE_CONSOLE_TOKEN=$(openssl rand -hex 16) \
FORGE_CONSOLE_USER=forge FORGE_CONSOLE_PASS_HASH="$HASH" \
./target/release/forge # http://127.0.0.1:7100 (sans PASS_HASH = dev localhost ouvert)
# côté moteur : expédier une campagne vers la console
python3 -m forge.cli campaign --scope scope.json --targets t.json --campaign op1 \
--console http://127.0.0.1:7100 --console-token "$FORGE_CONSOLE_TOKEN"Auth/RBAC (modèle auth_guard/host_guard de Plume) : /health ouvert ; toutes les autres
routes derrière (a) host-guard anti-DNS-rebinding (Host en allowlist → sinon 421) et (b)
auth-guard si FORGE_CONSOLE_PASS_HASH est défini — Basic (opérateur=viewer, lecture) ou
Bearer token (agent/admin=écriture). Sans hash → mode dev localhost ouvert (les écritures
restent gatées par le token). Mot de passe argon2id via forge hashpw '...'.
Endpoints : GET /health · POST /api/ingest (token) · GET /api/findings · GET /api/runrecords ·
GET /api/coverage (rollup ATT&CK) · GET /api/query?q=... (GXQL) · /api/panels (GET liste ·
POST créer [token] · DELETE [token] · GET /api/panels/:id/data) · GET / (console opérateur dark,
vanilla-JS : barre de recherche GXQL + dashboard de panels table/bar/stat).
Dedup au niveau store (UNIQUE(campaign,target,title)). Bind 127.0.0.1 ; auth/RBAC complète = durcissement.
GXQL (langage de requête type-SPL, porté de Plume) — GuatX Query Language, anciennement
« SOQL » : même langage, même syntaxe, seul le nom change ; le drapeau CLI --soql, la clé JSON
soql et le module guatx_core::soql restent inchangés. Interroge finding/runrecord,
compilé en SQL read-only (champs en allowlist, valeurs en params liés, un seul SELECT, LIMIT
plafonné, connexion SQLITE_OPEN_READ_ONLY). Exemples :
search severity=HIGH | fields target,title,mitre
search | stats count by severity | sort -count
search title~Origine
runs | stats count by mitre
Un champ hors allowlist → 400 (anti-injection). Le SQL compilé est renvoyé (transparence).
# 1) build du binaire (offline, depuis le cache cargo)
cd console && cargo build --release && cd ..
# 2) lancer la console (dev : localhost ouvert, écritures gatées par token)
FORGE_CONSOLE_TOKEN=$(openssl rand -hex 16) ./console/target/release/forge &
# -> http://127.0.0.1:7100 (UI opérateur dark + API)
# 3) peupler le catalogue de modules côté UI
forge modules --json # liste les 77 modules du registre (kind, mitre, dispo)
# 4) ingérer une campagne de démonstration (zéro réseau, finding synthétique)
FORGE_CONSOLE_URL=http://127.0.0.1:7100 FORGE_CONSOLE_TOKEN=$FORGE_CONSOLE_TOKEN \
python3 demo_ingest.py # FIRE demo.fingerprint -> POST /api/ingestEn conteneur : la console bind
127.0.0.1:7100par défaut (FORGE_CONSOLE_ADDR). Pour l'exposer, fixerFORGE_CONSOLE_ADDR=0.0.0.0:7100, ajouter le nom d'hôte public au host-guard anti-rebinding viaFORGE_CONSOLE_HOST, et définirFORGE_CONSOLE_PASS_HASH(auth argon2id) — ne jamais exposer le mode dev localhost-ouvert hors de la machine.
Forge n'ajoute pas de capacité offensive propre : deux connecteurs pilotent des outils de pentest standards que l'opérateur exécute déjà lui-même, et mappent leurs résultats en Findings derrière la même gate ROE. Tous deux sondent leur service à fire-time (jamais au catalogue) et s'auto-neutralisent si le service est injoignable.
| Module | Outil piloté | Variables d'env (défauts) |
|---|---|---|
msf.module |
msfrpcd (RPC msgpack) — lance le module MSF choisi par l'opérateur ; aucun payload généré par Forge | MSF_RPC_HOST (127.0.0.1) · MSF_RPC_PORT (55553) · MSF_RPC_USER (msf) · MSF_RPC_PASS · MSF_RPC_SSL (true) · MSF_RPC_TOKEN (token permanent, optionnel) |
burp.scan |
REST API Burp Suite Pro/Enterprise — lance un scan in-scope, rapatrie les issues | BURP_API_URL (http://127.0.0.1:1337) · BURP_API_KEY |
Gouvernance : msf.module déclare exploit=True (fail-safe → l'engine exige allow_exploit) ;
burp.scan reste exploit=False mais émet reported_by_tool (jamais vulnerable — comme nuclei,
pas de sur-classement sans preuve d'exploitabilité). forge doctor indique lesquels sont joignables.
cerveau (planner heuristique, seam orchestrateur LLM)
│ propose des Actions (kind, target, exploit?, destructive?)
▼
Engine ──► gate ROE ──► VETO | DRY_RUN | FIRE ──► Ledger (append-time, hash-chain + HMAC)
│ │
│ ▼ (FIRE seulement)
└──► module.fire() ──► Findings ──► report.py (+ section anti-masquage)
▲
modules = OUTILS AUTONOMES orchestrés (recon, oracles à preuve, évasion navigateur, connecteurs)
- Cœur =
roe.py(gate) +ledger.py(preuve) +engine.py+schema.py. Pur-stdlib. - Modules restent indépendants (le
Modulene fait quedry()→PoC etfire()→findings). - Boucle purple : chaque finding porte un champ
mitre(ATT&CK) = clé de jointure pour valider la détection côté défense (BAS). La source de détection est un plugin configurable (Plume n'est qu'un préréglage — CrowdSec, FortiGate, pfSense/OPNsense, Elastic/OpenSearch, fichier, exec se câblent sans code) : voirdocs/DETECTION.mdetARCHITECTURE.md.
D'où viennent ces deux chiffres. Python :
python3 -m unittest discover -s tests -t .→Ran 1513 tests/OK (skipped=1). Rust :cd console && cargo test --offline→test result: ok. 418 passed; 0 failed, features par défaut. Le dépôt porte 435 annotations#[test]/#[tokio::test]: l'écart de 17 est le compte des tests gardés derrière une feature non activée par défaut (store-postgres,encryption) — 18 gardés, moins 1 en#[cfg(not(feature = "encryption"))]qui, lui, tourne. 432 n'est donc PAS un compte de tests exécutés ; ne le publiez pas comme tel.
| Couche | État |
|---|---|
| Gate ROE fail-closed (4 couches) | ✅ construit + testé (10 tests) |
| Ledger append-time tamper-evident (Ed25519 asymétrique + verify externe, repli HMAC) | ✅ construit + testé (8 tests) |
| Engine + report anti-masquage + CLI | ✅ construit, demo bout-en-bout OK |
| Planner coverage-safe (FLOOR sur classes payantes) | ✅ construit + self-test |
Cerveau (interface Brain + HeuristicBrain) |
✅ — seam pour l'orchestrateur Claude |
| Runner (binaire local ou docker, sans install) | ✅ construit + testé |
| Graphe d'engagement (world-model hosts→services→findings) | ✅ construit + testé |
Handlers : recon.httpx/recon.nmap/web.nuclei/access_control.idor/origin.find |
✅ — gatés, auto-neutralisés si l'outil manque |
Évasion : evasion.xhr/evasion.turnstile/evasion.idor_intercept (browser-automation) |
✅ — atteindre les cibles CF/WAF, auto-off si service injoignable |
Connecteurs : msf.module (msfrpcd) / burp.scan (REST API Burp) |
✅ — pilotent des outils standards, sondés à fire-time, auto-off si service injoignable |
Mémoire : store JSONL + dedup (forge/memory.py) |
✅ — backend à embeddings optionnel à brancher |
Boucle purple : run-records ATT&CK + forge campaign |
✅ construit + testé |
Console Rust (console/, fork de la colonne Plume) |
✅ — compile offline, ingest+coverage+PWA, intégration Python↔Rust prouvée |
GXQL finding/runrecord (GET /api/query, read-only, anti-injection) + barre de recherche UI |
✅ porté de Plume, testé en live |
| Dashboards query-driven (panels GXQL sauvegardés, viz table/bar/stat, écriture gatée par token) | ✅ testé en live |
| Auth/RBAC console (argon2id Basic=viewer · Bearer=admin · host-guard anti-rebinding) | ✅ porté de Plume, 10/10 en live |
Ledger Ed25519 (signature asymétrique à l'append + verify_external par clé publique) |
✅ testé |
Ancrage hors-host (anchor.py : interface Anchor + témoin co-signataire + reconcile) |
✅ testé (détecte une réécriture re-signée localement) |
Mémoire sémantique (JaccardMemory floue stdlib + bridge FAISS embeddings optionnel) |
✅ Jaccard testé, FAISS dégrade proprement |
Cœur partagé guatx-core (crate Rust public neutre, repo séparé guatxlabs/core ; console en dépend via git-dep publique épinglée) |
✅ extrait en repo public (épinglé v0.2.1), console rebâtie ; suite propre au cœur : 161 tests (compte MESURÉ dans guatxlabs/core : 155 unitaires + 5 parité + 1 doctest) |
| Wizard 1er déploiement (self-deploy : provisionne admin/crypto/source de détection/politique opérateur depuis le navigateur, auto-désactivant, zéro défaut codé en dur) | ✅ — GET /api/setup/state · POST /api/setup |
RBAC admin & gouvernance des connecteurs (comptes /api/users, viewer/opérateur/admin ; msf/burp sondés à fire-time, exploit fail-safe, jamais de sur-classement) |
✅ — testé en live |
| Source de détection infra-agnostique (plugin configurable dans l'UI : Plume/CrowdSec/FortiGate/pfSense/OPNsense/Elastic/fichier/exec, secret write-only) | ✅ — cf. docs/DETECTION.md |
Sauvegarde/restore chiffrées + migration (archive toujours chiffrée argon2id+XChaCha20, scheduler + offsite, migrate DB+ledger+clé .ed25519) |
✅ — /api/backup(/policy) · /api/restore · forge migrate |
Chiffrement AU REPOS SQLCipher (image opt-in --features encryption, PRAGMA key au boot) |
✅ opt-in — capabilities.sqlcipher exposé au wizard |
Migration Plume vers guatx-core + signeur témoin distant (HTTP) |
⏳ à la demande |
Modules — le registre compte 77 modules (compte MESURÉ sur cet arbre :
python3 -m forge.cli modules --json → 77 entrées ; 11 marqués exploit, 1 destructive, 26 techniques
ATT&CK distinctes). La table ci-dessous en montre 14 ; docs/MODULES.md en décrit
32 avec dépendances et statut. La source de vérité est la commande, pas ces tables :
| kind | exploit | ATT&CK | description |
|---|---|---|---|
access_control.idor |
✅ | T1190 | Oracle différentiel IDOR/BOLA à PREUVE sur 2 comptes (CWE-639). |
auth.takeover |
✅ | T1212 | Oracle ATO/auth-bypass à PREUVE (whoami = identité victime, CWE-287/640). |
burp.scan |
— | T1595.002 | Pilote la REST API de Burp Suite : scan in-scope → issues → Findings. |
cors.credentials |
✅ | T1539 | Oracle CORS-credentials à PREUVE (ACAO reflète l'origine + ACAC=true, CWE-942). |
demo.fingerprint |
— | — | Démonstration du pipeline (plan→ROE→dry/fire→finding→ledger), zéro I/O. |
evasion.idor_intercept |
✅ | T1190 | Arme l'interception IDOR en vol (browser intercept-modify, CWE-639). |
evasion.turnstile |
— | T1556 | Franchit le Turnstile interactif (vision-click-os) — enabler d'accès. |
evasion.xhr |
— | T1190 | Observation des requêtes XHR via la session browser (bypass WAF). |
msf.module |
✅ | T1210 | Pilote msfrpcd : lance le module MSF choisi par l'opérateur. |
origin.find |
— | T1590.005 | IP d'origine derrière CDN/WAF (subfinder→DNS→drop-CF→vérif Host). |
recon.httpx |
— | T1595 | Fingerprint HTTP (status, titre, techno). |
recon.nmap |
— | T1046 | Découverte des services exposés (nmap -sV, top 1000). |
ssrf.callback |
✅ | T1190 | Oracle SSRF à PREUVE (callback unique reçu côté collecteur, CWE-918). |
web.nuclei |
— | T1595.002 | Scan de vulnérabilités par templates nuclei (medium/high/critical). |
Aucun module ne tire rien sans verdict
FIRE(in-scope + armé + approuvé + capacité autorisée). Tous les tests sont hermétiques (aucun outil n'est exécuté contre une cible).forge doctorindique quels modules sont opérationnels sur la machine courante.
Ledger : signature Ed25519 à l'append par défaut (asymétrique → un tiers vérifie avec la SEULE clé publique via
verify_external(pubkey), sans pouvoir forger), repli HMAC sicryptographyabsent. Caveat custody restant : la clé privée est encore locale ; l'ancrage hors-host (clé privée sur un signeur distant / co-signataire / transparency log) est la dernière étape — l'architecture asymétrique le permet déjà (seule la clé publique circule). Documenté, pas caché.
Runbook complet : docs/DEPLOYMENT.md. En bref — le contexte de build est la
racine de ce dépôt (la console résout guatx-core via une git-dep publique épinglée au tag v0.2.1,
aucun crate sibling requis ; console/Cargo.lock est committé pour des builds reproductibles) :
cp scope.example.json scope.json # INERTE tant que in_scope vide ; éditer AVEC AUTORISATION
docker compose up -d --build # console SEULE (loopback :7100, healthcheck GET /health)Ouvrir http://127.0.0.1:7100 → le wizard 1er boot provisionne l'admin (RBAC argon2id), la crypto,
la source de détection de TON infra (FortiGate/pfSense/CrowdSec/Elastic/… — docs/DETECTION.md,
plugin sans code) et la politique opérateur — rien de codé en dur. Services optionnels
(browser/msf/burp) derrière des profils (--profile browser), aucun depends_on dur → un up nu =
console seule. Chiffrement au repos (image SQLCipher opt-in) et sauvegardes chiffrées programmées
(offsite) : docs/MIGRATION.md · docs/BACKUP.md.
➡️ Sommaire complet et navigable : docs/README.md — l'ensemble de la
documentation produit (vue d'ensemble, architecture, installation, configuration, administration,
concepts, CLI, API HTTP, sécurité, dépannage). Les pages phares :
docs/OVERVIEW.md— ce qu'est Forge, la valeur, le fonctionnement standalone.docs/ARCHITECTURE.md— comment c'est construit (moteur Python + console Rust + SPA + gouvernance).docs/CONFIGURATION.md— référence complète des variables d'env et des cléssettings.docs/CLI.md·docs/HTTP_API.md— références CLI & API HTTP.docs/GETTING_STARTED.md— démarrage bout-en-bout hors-ligne (seed + mock-Plume) : install → scope → console → purple → rapport → intégrité.docs/PLAN.md— positionnement, red/blue/purple, roadmap séquencée et statut des blockers.docs/DETECTION.md— source de détection = plugin configurable (brancher n'importe quelle infra BLUE sans code : Plume/CrowdSec/FortiGate/pfSense/OPNsense/Elastic/fichier/exec) + modèleDetectionSourceet mapping MITRE.docs/PURPLE_PREREQS.md— prérequis du préréglage Plume pour câbler la boucle purple (le moat) — un cas particulier deDETECTION.md.docs/DEPLOYMENT.md— runbook self-deploy bout-en-bout : build/run (mini·full · Docker · compose · natif/systemd · imageencryptionSQLCipher), wizard 1er boot (admin · crypto · source de détection · politique opérateur — rien de codé en dur), migration & sauvegardes chiffrées (schedule/offsite), contexte de buildguatx-core, liveness/health. Inclut l'empreinte mesurée + matrice (Docker / k8s / host / venv).docs/PLATFORMS.md— matrice de support OS (Linux primaire · macOS pleinement supporté · Windows best-effort) : ce qui marche partout vs la seule capacité Unix-only (kill de sous-arbresetsid/killpgdu run C2), et la résolution des répertoires config/data/temp par OS.docs/MIGRATION.md— reprendre un install existant (DB + ledger + clé.ed25519) vers Docker/autre cible ; option chiffrement au repos SQLCipher.docs/BACKUP.md— sauvegarde/restauration toujours chiffrées (argon2id + XChaCha20-Poly1305), programmation + expédition offsite.
Forge suit un modèle open-core.
- Community edition — AGPL-3.0-or-later : le cœur complet de gouvernance est open, gratuit et auto-hébergeable. Scope-guard ROE fail-closed, ledger d'autorisation Ed25519 tamper-evident, oracles orientés-preuve, registre extensible de techniques + toutes les classes de techniques, run C2-light gouverné, console (UI + wizard + RBAC admin/operator/viewer), connecteurs/orchestration (nuclei/msf/ burp/…), détection infra-agnostique, backup/restore chiffré et boucle purple — tout ce qu'il faut pour faire tourner Forge en solo ou en petite équipe. Comme c'est de l'AGPL, tout déploiement en réseau doit offrir la source correspondante à ses utilisateurs.
- Enterprise edition — licence commerciale distincte : la couche échelle / équipe / conformité — multi-tenant/MSSP (isolation crypto par tenant), SSO/SCIM, RBAC composable avancé & grants par engagement, HA/clustering/store distribué (Postgres), conformité (preuves SOC2/ISO, rétention legal-hold/WORM, clés KMS/HSM) 402E , connecteurs premium, et support/SLA.
Principe : le cœur gouvernance + audit cryptographique reste OUVERT et vérifiable — c'est toute la
crédibilité du produit. Seule la couche scale/équipe/conformité est commerciale, construite en
modules séparables. Détail de la frontière : COMMUNITY_VS_ENTERPRISE.md.
Usage autorisé / éthique uniquement. La licence AGPL couvre le code ; elle n'autorise aucune action offensive hors d'un périmètre explicitement autorisé (bug bounty in-scope, pentest sous contrat, CTF, infra propre). La gate ROE + le scope-guard + le ledger sont là pour imposer ET prouver cette autorisation.