Guide de contribution
Ce document couvre le flow de contribution par PR, les règles de versioning, le système de trust tier, le DCO, et les points de friction à connaître avant de soumettre une fiche.
Sommaire
- Flow PR
- Versioning des fiches
- Trust tier
- DCO — Developer Certificate of Origin
- Anti-patterns relevés par le linter
- Champs experts :
mitre_attacketthreat_model - Rappel des invariants clés
1. Flow PR
fork → branche → fiche → validate + lint + render → PR → revue mainteneur → merge
Étapes détaillées
a. Forker et créer une branche
git clone https://github.com/your-org/neuralwall-rules-kit.git
cd neuralwall-rules-kit
git checkout -b feat/mon-scenario
b. Créer la fiche depuis le gabarit
cp templates/fiche.template.yaml profiles/<categorie>/<id>.yaml
Convention d'id : snake_case, descriptif, format recommandé <verbe>_<sujet> ou
<service>_<direction> (ex. expose_https_public, saas_outbound_access). L'id
est une clé primaire stable : ne pas le modifier après merge.
c. Éditer la fiche
- Remplir tous les champs obligatoires (voir
docs/format-spec.md). - Prose uniquement en locale-map
{fr: "...", en: "..."}— jamais de texte brut. - Enums strictement issus de
taxonomy/taxonomy.yaml. - Aucune marque propriétaire (vendor-agnostic, invariant 3).
d. Valider localement avant de pousser
# Validation JSON Schema (structure)
python scripts/validate.py profiles/<categorie>/<id>.yaml
# Linter sécurité (anti-patterns)
python scripts/lint.py profiles/<categorie>/<id>.yaml
# Prévisualiser les trois représentations
python scripts/render.py profiles/<categorie>/<id>.yaml --locale fr,en --out build/
Les trois commandes doivent passer sans erreur. Le répertoire build/ est ignoré par
Git (ne rien commiter depuis ce dossier).
Si vous modifiez
taxonomy/taxonomy.yaml, régénérez le schéma et committez-le :python scripts/gen_schema.py # met à jour schema/fiche.schema.json git add schema/fiche.schema.json taxonomy/taxonomy.yaml git commit -s -m "chore(schema): regenerer les enums depuis la taxonomie"La CI bloquera toute PR où le schéma n'est pas en phase avec la taxonomie (
python scripts/gen_schema.py --checkdoit sortir avec code 0).
e. Commiter avec sign-off DCO
git add profiles/<categorie>/<id>.yaml
git commit -s -m "feat(profiles): ajouter fiche <id>"
# L'option -s ajoute automatiquement la ligne Signed-off-by
f. Ouvrir la PR
- Titre :
feat(profiles): <description courte> - Description : scénario couvert, sources utilisées pour
mitre_attack, justification dutrust_tiersi différent decommunity.
g. Revue par un mainteneur
Le mainteneur vérifie : conformité schéma, cohérence threat model, absence de marque,
qualité du rationale. Il peut promouvoir le trust_tier à reviewed après merge.
2. Versioning des fiches
Deux versions coexistent dans chaque fiche — elles ne se confondent pas (invariant 7).
| Champ | Rôle | Géré par |
|---|---|---|
schema_version |
Version du contrat d'ingestion (JSON Schema). Identique pour toutes les fiches compatibles. | Mainteneurs core |
version |
Version sémantique du contenu de cette fiche. | Auteur de la fiche |
Règles de bump de version (ADR-0002 §4)
| Type de changement | Bump | Exemples |
|---|---|---|
| Clarification de prose, correction typo, amélioration du rationale | patch (1.0.0 → 1.0.1) |
Reformuler une description, corriger une faute |
| Ajout d'un contrôle (nouvelle règle, nouveau profil de sécurité) | minor (1.0.0 → 1.1.0) |
Ajouter sandboxing, ajouter une règle de dépendance DNS |
| Changement d'intent ou modification structurante | major (1.0.0 → 2.0.0) |
Changer l'action d'une règle, modifier les zones, changer le scénario couvert |
Un bump de
schema_versionn'est décidé que par les mainteneurs core, il signale une évolution du contrat d'ingestion incompatible avec les versions précédentes.
3. Trust tier
Chaque fiche porte un trust_tier obligatoire (ADR-0002 §3, invariant 6). Il est
pondéré par NeuralWall à l'ingestion : une fiche community n'a pas le même poids
dans le RAG qu'une fiche verified. C'est le principal rempart contre l'empoisonnement
de la base de connaissance.
| Valeur | Signification | Qui l'attribue |
|---|---|---|
community |
Contribution non revue. Défaut pour toute PR entrante. | Automatique à la soumission |
reviewed |
Revue explicite par un mainteneur du portail. | Mainteneur (après merge ou via PR de mise à jour) |
verified |
Validée par un éditeur ou une source autoritaire (ex. CERT, éditeur de firewall). | Mainteneurs core uniquement |
Comment le tier évolue
- Toute fiche mergée commence à
community. - Un mainteneur qui a relu en profondeur ouvre un commit de mise à jour changeant
trust_tier: community→trust_tier: reviewedavec un bump patch deversion. verifiedest réservé aux fiches issues d'une source institutionnelle vérifiée ; le mainteneur core crée un commit documentant la source.
Pourquoi ce tier est critique
NeuralWall ingère les fiches pour alimenter un RAG de génération de règles. Une fiche
community mal rédigée (mauvaise action, profils manquants) pourrait conduire à la
suggestion de règles faibles. Le tier permet à NeuralWall de pondérer la confiance et
d'éviter qu'une fiche non revue influence autant qu'une fiche validée par un expert.
4. DCO — Developer Certificate of Origin
Ce projet n'utilise pas de CLA (Contributor License Agreement). À la place, il suit le Developer Certificate of Origin 1.1, mécanisme léger et standard qui atteste que vous avez le droit de soumettre votre contribution sous la licence du projet (CC-BY-4.0).
Comment signer
Ajoutez l'option -s à chaque git commit :
git commit -s -m "votre message de commit"
Git ajoute automatiquement en fin de message :
Signed-off-by: Prénom Nom <email@exemple.org>
Cette ligne est votre attestation du DCO. L'email doit correspondre à l'identité Git configurée. Aucune signature électronique ni procédure externe n'est requise.
Texte du Developer Certificate of Origin 1.1
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
Attribution CC-BY-4.0
En signant le DCO, vous attestez que votre contribution peut être distribuée sous
CC-BY-4.0. Veillez à renseigner le champ authors de votre fiche avec votre nom (et
optionnellement email/org) pour que l'attribution soit correctement propagée dans les
rendus.
5. Anti-patterns relevés par le linter
Le script python scripts/lint.py détecte les configurations risquées. Ces règles sont
minimales en Phase 1 et extensibles.
a. allow sans security_profiles
# ANTI-PATTERN — le linter ECHOUE sur ceci :
action: allow
# security_profiles absent
# CORRECT — posture next-gen (invariant CLAUDE.md) :
action: allow
security_profiles:
antivirus: { action: default }
ips: { action: block, min_severity: medium }
Une règle allow sans aucun profil de sécurité laisse passer le trafic sans inspection.
Si l'omission est intentionnelle et justifiée (ex. flux de management ultra-contrôlé),
le linter l'accepte si un rationale explicite est présent sur la règle.
b. decryption: mode: none sur du sortant à risque
# ANTI-PATTERN — flux sortant vers internet sans déchiffrement :
direction: outbound
zones:
source: [trust]
destination: [internet]
decryption:
mode: none
# Sans rationale : le linter AVERTIT
# CORRECT — soit déchiffrer, soit justifier :
decryption:
mode: ssl_forward_proxy
# OU :
decryption:
mode: none
rationale:
fr: "Trafic vers service avec cert-pinning (client lourd) — déchiffrement impossible."
en: "Traffic to cert-pinned service (thick client) — decryption not feasible."
Sans déchiffrement, les profils AV/IPS/sandboxing voient un flux chiffré opaque et ne peuvent pas inspecter le contenu.
c. application: unknown non justifié
# ANTI-PATTERN :
application:
app_id: unknown
category: unknown
# Sans rationale : le linter AVERTIT
# CORRECT — toujours justifier un unknown :
application:
app_id: unknown
category: unknown
rationale:
fr: "Trafic legacy non identifiable L7 — port restreint, périmètre isolé."
en: "Legacy traffic not identifiable at L7 — restricted port, isolated perimeter."
d. url_filtering riche sans déchiffrement (LNT-004)
allow_list/block_list (filtrage par chemin d'URL) et credential_phishing n'opèrent
qu'avec decryption.mode: ssl_forward_proxy : sans déchiffrement, le firewall ne voit que
le domaine (SNI), pas l'URL complète ni les soumissions de formulaire (taxonomie §7.1 + §8).
# ANTI-PATTERN — protection dégradée (le linter le relève) :
action: allow
security_profiles:
url_filtering:
credential_phishing: block # exige le déchiffrement pour être effectif
decryption:
mode: none
# CORRECT — activer le déchiffrement sortant :
decryption:
mode: ssl_forward_proxy
# OU justifier explicitement le mode none via rationale (cert-pinning, etc.).
Posture next-gen : identifier l'application, pas le port
Identifiez toujours l'application au niveau L7 (app_id). Le champ service.ports
est un complément pour la cohérence, pas le critère de matching. Un contrôle par port
seul est un fallback à justifier explicitement dans rationale.
6. Champs experts : mitre_attack et threat_model
Ces deux champs sont auteurés par un expert humain (ADR-0002 §2, invariant 5). La carte RAG les rend ; elle ne les déduit pas des autres champs. C'est ce qui garantit le « zéro drift » : la connaissance experte est explicite, pas implicite.
mitre_attack[]
Tableau des techniques MITRE ATT&CK couvertes ou atténuées par le scénario.
mitre_attack:
- technique_id: "T1071" # Format : T\d{4} ou T\d{4}.\d{3}
name: "Application Layer Protocol"
tactic: "command-and-control"
- technique_id: "T1071.001" # Sous-technique
name: "Web Protocols"
tactic: "command-and-control"
Gabarit de recherche : consultez attack.mitre.org pour trouver les techniques pertinentes à votre scénario. Raisonnez en termes de flux atténué : quelle technique l'attaquant ne peut plus exécuter grâce à ces règles ?
Exemples courants par scénario :
| Scénario | Techniques pertinentes |
|---|---|
| Ingress HTTPS public | T1190 (Exploit Public-Facing App), T1071.001 (Web Protocols) |
| Sortant SaaS | T1048 (Exfiltration Over Alt Protocol), T1567 (Exfiltration to Cloud Storage) |
| DNS | T1071.004 (DNS), T1568 (Dynamic Resolution) |
threat_model
Modèle de menace structuré au niveau fiche.
threat_model:
summary:
fr: "Ce scénario expose un serveur web en DMZ. Sans inspection TLS inbound,
un attaquant peut exploiter des vulnérabilités applicatives masquées dans
le flux chiffré. Les profils IPS et sandboxing atténuent ce risque."
en: "This scenario exposes a web server in the DMZ. Without inbound TLS inspection,
an attacker can exploit application vulnerabilities hidden in encrypted traffic.
IPS and sandboxing profiles mitigate this risk."
attacker_goal:
fr: "Compromettre le serveur web exposé pour accéder au réseau interne."
en: "Compromise the exposed web server to gain access to the internal network."
key_controls:
- ssl_inbound_inspection # Identifiants libres, conventions : clés canoniques de taxonomie
- ips
- sandboxing
Conseils pour threat_model :
summary: décrivez la surface d'attaque principale et comment les règles la réduisent.attacker_goal: formulez en termes d'objectif attaquant (pas de contrôle).key_controls: listez les contrôles qui font réellement la différence — renvoyez aux clés canoniques desecurity_profilesou àdecryption.mode.
La friction documentée ici est intentionnelle (ADR-0002) : un threat model vide ou générique sera signalé lors de la revue mainteneur.
7. Rappel des invariants clés
Ces invariants dérivent de CLAUDE.md et des ADR. Les violer entraîne un refus de PR.
| # | Invariant | Conséquence si violé |
|---|---|---|
| 1 | Source unique. Une fiche = un fichier YAML. Pas de prose parallèle. | Drift garantis — refus immédiat |
| 2 | Structure neutre linguistiquement. Enums/clés en snake_case anglais, jamais traduits. |
Schéma invalide, validate échoue |
| 3 | Vendor-agnostic strict. Aucune marque dans les fiches. | Refus PR |
| 4 | Enums ⊆ taxonomie. Toute valeur doit exister dans taxonomy/taxonomy.yaml. |
validate échoue |
| 5 | MITRE & threat_model auteurés. Ne pas laisser vides ni remplir mécaniquement. | Refus revue mainteneur |
| 6 | trust_tier obligatoire. |
validate échoue |
| 7 | schema_version ≠ version. Ne pas les confondre ni les synchroniser. |
Erreur d'ingestion NeuralWall |
| 8 | Rendus non commités. Le dossier build/ n'appartient pas au repo. |
CI rejette le commit |
| 9 | Actions alignées OpenC2. allow/deny/drop/reset uniquement. |
validate échoue |
| 10 | DCO sign-off obligatoire. git commit -s sur chaque commit. |
PR bloquée |
Bascule CMS (ADR-0004) — la base devient la source
Depuis l'ADR-0004, la source de vérité d'une fiche est la base de données (table
fiche, colonne content JSON validée au schéma à l'écriture). Le YAML de profiles/
et les rendus deviennent des exports reproductibles.
Tant que la migration de bascule (seed) n'a pas été appliquée en production, les YAML de
profiles/restent la source de fait : continuez à contribuer par PR + DCO comme ci-dessus. La bascule est atomique à l'étape « seed » (cf. plan ADR-0004 §2-5).
Outils CMS (backend api/) :
| Outil | Rôle |
|---|---|
Endpoints POST/GET/PUT /fiches |
Cycle de vie éditorial. Toute écriture valide + linte le content avant persistance (rejet 422 si schéma invalide ou finding lint de sévérité high). |
python scripts/seed_fiches.py |
Import one-shot des profiles/*/fiche.yaml → table fiche (idempotent ; nécessite alembic upgrade head au préalable). |
python scripts/export_fiches.py [--check] |
Export inverse DB → profiles/<id>/fiche.yaml (canonique, déterministe). --check = garde-fou CI : exit 1 si l'export YAML diverge de la base (anti-drift). |
Le content_hash (SHA-256 du JSON canonique) garantit la détection de drift entre la base
et les exports.