FFFF
Skip to content

Latest commit

 

History

188 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

🔨 Forge

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 — vue d'ensemble, catalogue de modules, findings mappés ATT&CK, lancement de campagne gouverné

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.

Sûreté d'abord — la gate à 4 couches (forge/roe.py)

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).

Install

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 module

Sans installation, tout est aussi accessible via python3 -m forge.cli <commande>.

Quickstart

🚀 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.jsonl

Copier scope.example.jsonscope.json et renseigner in_scope avec autorisation écrite. scope.json, *.key, *.jsonl sont gitignorés (secrets / état d'engagement).

Démo instantanée — console peuplée en 1 commande (hors-ligne)

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.

Console Rust (console/ — le store + la boucle purple)

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).

Quickstart console (≈5 min)

# 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/ingest

En conteneur : la console bind 127.0.0.1:7100 par défaut (FORGE_CONSOLE_ADDR). Pour l'exposer, fixer FORGE_CONSOLE_ADDR=0.0.0.0:7100, ajouter le nom d'hôte public au host-guard anti-rebinding via FORGE_CONSOLE_HOST, et définir FORGE_CONSOLE_PASS_HASH (auth argon2id) — ne jamais exposer le mode dev localhost-ouvert hors de la machine.

Connecteurs (outils de pentest standards, pilotés par Forge)

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.

Architecture (en bref)

  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 Module ne fait que dry()→PoC et fire()→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) : voir docs/DETECTION.md et ARCHITECTURE.md.

État (v0.0.1 — 1513 tests Python + 418 tests Rust passent, zéro réseau) — P1 + P2 complets

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 --offlinetest 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 doctor indique 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 si cryptography absent. 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é.

Déploiement en production (self-deploy)

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.

Documentation

➡️ 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.mdce qu'est Forge, la valeur, le fonctionnement standalone.
  • docs/ARCHITECTURE.mdcomment c'est construit (moteur Python + console Rust + SPA + gouvernance).
  • docs/CONFIGURATION.mdréférence complète des variables d'env et des clés settings.
  • docs/CLI.md · docs/HTTP_API.md — références CLI & API HTTP.
  • docs/GETTING_STARTED.mddé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.mdsource de détection = plugin configurable (brancher n'importe quelle infra BLUE sans code : Plume/CrowdSec/FortiGate/pfSense/OPNsense/Elastic/fichier/exec) + modèle DetectionSource et mapping MITRE.
  • docs/PURPLE_PREREQS.md — prérequis du préréglage Plume pour câbler la boucle purple (le moat) — un cas particulier de DETECTION.md.
  • docs/DEPLOYMENT.mdrunbook self-deploy bout-en-bout : build/run (mini·full · Docker · compose · natif/systemd · image encryption SQLCipher), 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 build guatx-core, liveness /health. Inclut l'empreinte mesurée + matrice (Docker / k8s / host / venv).
  • docs/PLATFORMS.mdmatrice de support OS (Linux primaire · macOS pleinement supporté · Windows best-effort) : ce qui marche partout vs la seule capacité Unix-only (kill de sous-arbre setsid/killpg du 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.

License & model

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.

About

Purple-team engine: authorised offensive campaigns with fail-closed rules of engagement and a tamper-evident ledger. Python stdlib core + Rust console. The red counterpart to Plume.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

0