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
61 changes: 61 additions & 0 deletions .github/workflows/kb-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: kb-sync

# Garde-fou de cohérence de la KB. Le JSON sous active/ et archived/ est la
# source canonique ; index.json, index.md et le miroir md/ en sont dérivés.
#
# Sur push, le workflow ne se contente pas de vérifier : il régénère les fichiers
# dérivés et committe le résultat, pour qu'un contributeur puisse modifier une
# fiche sans rien lancer en local.
#
# Sur pull request, il se limite à vérifier : une PR venant d'un fork ne permet
# pas de pousser sur sa branche source.

on:
push:
pull_request:

permissions:
contents: write

jobs:
sync:
# Les pushes uniquement : sur une PR, le job check ci-dessous prend le relais.
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
# Nécessaire pour pouvoir committer et pousser sur la branche courante.
persist-credentials: true

# Aucune dépendance à installer : tools/kb.py n'utilise que la stdlib
# Python 3, déjà présente sur le runner.
- name: Régénère les index et le miroir markdown
run: python3 tools/kb.py sync

- name: Committe le résultat si quelque chose a changé
run: |
if git diff --quiet; then
echo "Rien à régénérer : la KB est déjà cohérente."
exit 0
fi
echo "Fichiers dérivés régénérés :"
git diff --name-only
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git add -A
git commit -m 'chore(kb): régénère les index et le miroir markdown [skip ci]'
# Un push fait avec GITHUB_TOKEN ne redéclenche pas les workflows : pas
# de boucle. Le [skip ci] ci-dessus est une ceinture supplémentaire.
git push origin HEAD:"${GITHUB_REF_NAME}"

check:
# Sur PR (typiquement depuis un fork), on vérifie sans écrire.
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v5
- name: Vérifie que JSON et Markdown sont synchronisés
run: python3 tools/kb.py check
108 changes: 100 additions & 8 deletions KB-CONVENTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,35 @@ partagé entre l'état local de ma.xi.me et ce repo dès le départ.
## Structure

```
index.json ← index léger, un objet par fiche sans "content" (seul fichier chargé systématiquement)
active/<theme>/<id>.json ← une fiche = attributs + "content", chargée à la demande par thème
active/<theme>/<id>.json ← SOURCE CANONIQUE : une fiche = attributs + "content"
archived/ ← fiches obsolètes, jamais chargées sauf demande explicite
index.json ← index machine généré, un objet par fiche sans "content"
md/active/<theme>/<id>.md← miroir généré, lisible par un humain
index.md ← catalogue généré, lisible par un humain
tools/kb.py ← convertisseur JSON <-> Markdown
```

**Une seule source de vérité : le JSON.** Le Markdown existe parce qu'une fiche
JSON, dont tout le corps est une chaîne échappée, est illisible sur GitHub et
donne des diffs de PR non relisables. C'est une projection régénérable, pas une
deuxième base à tenir à jour à la main.

`index.json`, `index.md` et tout `md/` sont **entièrement dérivés** des fiches :
ne jamais les éditer directement, ils seront écrasés à la prochaine
régénération.

### Fiches archivées et contrat de chargement

`index.json` liste **toutes** les fiches, actives comme archivées, pour qu'une
fiche archivée reste trouvable sur demande explicite. Un consommateur qui charge
la connaissance courante doit donc **filtrer lui-même** — sur `status` (une
fiche archivée vaut toujours `archived`) ou sur `path` (préfixe `archived/`) —
sous peine de resservir une fiche périmée comme si elle était à jour.

`tools/kb.py validate` garantit que ce filtre est fiable : une fiche dans
`archived/` doit porter `"status": "archived"`, et une fiche marquée `archived`
ne peut pas rester dans `active/`. Dossier et statut ne peuvent pas diverger.

## Format de fiche (obligatoire)

Fiche JSON avec attributs courts/contrôlés et un seul champ texte libre (`content`) :
Expand All @@ -30,10 +54,9 @@ Fiche JSON avec attributs courts/contrôlés et un seul champ texte libre (`cont
"status": "draft | active | suspect | obsolete | archived",
"confidence": "fact | hypothesis | opinion",
"audience": "generic | project | secret",
"source": "URL / PR / discussion (libre)",
"source": ["URL / PR / discussion (libre)"],
"validated": "YYYY-MM-DD",
"created": "YYYY-MM-DD",
"ttl_days": 90,
"links": ["autres-ids-liés"],
"content": "Corps de la fiche, Markdown en texte libre"
}
Expand All @@ -42,14 +65,83 @@ Fiche JSON avec attributs courts/contrôlés et un seul champ texte libre (`cont
Seuls `title`, `source` et `content` sont exemptés de la contrainte de
concision (texte libre par nature).

## Format Markdown (miroir)

La même fiche, côté `md/` : les attributs deviennent le frontmatter, `content`
devient le corps.

```markdown
---
id: slug-kebab-case
type: reference
title: Titre lisible
theme: thème court
tags:
- mots-clés
- courts
scope: global
status: active
confidence: fact
audience: generic
source:
- https://exemple.invalid/page
validated: 2026-08-09
created: 2026-08-09
links:
- autre-id-lié
---

# Titre lisible

Corps de la fiche, Markdown en texte libre.
```

Le frontmatter est un sous-ensemble YAML volontairement restreint au schéma
réel — scalaires texte et séquences de textes — pour se parser sans dépendance.
Une valeur n'est mise entre guillemets doubles (échappement JSON) que si elle
le nécessite : vide, espace en tête ou en fin, `: ` ou ` #` à l'intérieur, ou
premier caractère qui est un indicateur YAML.

L'aller-retour est **sans perte** : reconstruire le JSON depuis le Markdown
redonne exactement le JSON de départ, octet pour octet.

## Outillage — `tools/kb.py`

Python 3, bibliothèque standard seule, rien à installer.

```bash
python3 tools/kb.py sync # JSON -> Markdown + index (sens par défaut)
python3 tools/kb.py sync --from md # Markdown -> JSON + index
python3 tools/kb.py check # vérifie sans écrire, sort en 1 si écart
python3 tools/kb.py validate # contrôles de schéma seuls
```

`sync` recanonicalise aussi les fiches JSON (indentation 2, ordre des clés fixe,
`content` terminé par exactement un saut de ligne) : c'est ce qui rend la
comparaison de `check` fiable.

`validate` vérifie la présence et le type des 14 attributs, les valeurs
contrôlées, `id` = nom de fichier, `theme` = dossier parent, le format des
dates, l'unicité des `id` et le fait que chaque `links` pointe vers une fiche
existante. Les champs retirés du schéma (`ttl_days`) sont rejetés explicitement,
pour qu'ils ne reviennent pas par copier-coller d'une ancienne fiche.

Le workflow `.github/workflows/kb-sync.yml` lance `sync` à chaque push et
committe lui-même les fichiers dérivés s'ils ont bougé ; sur pull request depuis
un fork, où pousser est impossible, il se rabat sur `check`.

## Conventions

- Nommage : `active/<thème>/<id>.json`, `id` = nom de fichier sans extension.
- Nommage : `active/<thème>/<id>.json`, `id` = nom de fichier sans extension,
`theme` = nom du dossier parent.
- Une fiche = un sujet précis. Pas de fourre-tout.
- Statut `suspect` si la fiche n'a pas été revalidée dans le délai `ttl_days`.
- Statut `suspect` posé explicitement lors d'une revue, quand la fiche n'est plus
sûre ; `validated` porte la date de dernière vérification.
- Archiver (déplacer dans `archived/`) plutôt que supprimer.
- Mettre à jour `index.json` (sans `content`, plus champ `path`) à chaque
ajout, archivage ou changement d'attribut.
- Après tout ajout, archivage ou modification — d'un côté ou de l'autre —
lancer `python3 tools/kb.py sync` (ou `sync --from md`) et committer les deux
côtés. Les index ne se mettent plus à jour à la main. Si c'est oublié, le
workflow le fait au push.

## Ajout comme submodule dans un repo consommateur

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
],
"validated": "2026-07-25",
"created": "2026-07-25",
"ttl_days": 180,
"links": [
"windows-server-2022-plus-hardening-source-baselines",
"ad-ds-tiered-administration-hardening",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
],
"validated": "2026-07-25",
"created": "2026-07-25",
"ttl_days": 180,
"links": [
"ad-ds-security-hardening-study-synthesis",
"ad-ds-domain-controller-security-hardening",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
],
"validated": "2026-07-25",
"created": "2026-07-25",
"ttl_days": 180,
"links": [
"ad-ds-domain-forest-gpo-security-baseline",
"ad-ds-tiered-administration-hardening",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
],
"validated": "2026-07-25",
"created": "2026-07-25",
"ttl_days": 180,
"links": [
"ad-ds-security-hardening-study-synthesis",
"ad-ds-threat-driven-control-map",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
],
"validated": "2026-07-25",
"created": "2026-07-25",
"ttl_days": 180,
"links": [
"windows-server-2022-plus-hardening-source-baselines",
"ad-ds-domain-forest-gpo-security-baseline",
Expand Down
1 change: 0 additions & 1 deletion active/ad-ds-security/ad-ds-threat-driven-control-map.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
],
"validated": "2026-07-25",
"created": "2026-07-25",
"ttl_days": 180,
"links": [
"ad-ds-domain-controller-security-hardening",
"ad-ds-domain-forest-gpo-security-baseline",
Expand Down
1 change: 0 additions & 1 deletion active/ad-ds/reference.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
],
"validated": "2026-07-16",
"created": "2026-06-16",
"ttl_days": 180,
"links": [
"unattended-deployment"
],
Expand Down
3 changes: 1 addition & 2 deletions active/ad-ds/unattended-deployment.json

Large diffs are not rendered by default.

21 changes: 16 additions & 5 deletions active/agent-tooling/agent-skills-cross-tool-integration.json

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions active/aws/ecs-fargate-task-isolation.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@
],
"validated": "2026-07-18",
"created": "2026-07-18",
"ttl_days": 180,
"links": [
"zero-standing-access"
],
"content": "# ECS — launch type EC2 vs Fargate, isolation\n\n## EC2 launch type\n\nLes tasks ECS s'exécutent comme des conteneurs sur des instances EC2 gérées par le client, en partageant le **même noyau hôte**. La documentation AWS elle-même est explicite : les conteneurs ne sont pas une frontière d'isolation de sécurité garantie — un conteneur compromis peut potentiellement atteindre des ressources d'autres tasks colocalisées sur le même hôte (évasion de conteneur, attaque via le noyau partagé).\n\n## Fargate launch type\n\nChaque task tourne dans sa **propre micro-VM** dédiée — frontière d'isolation matérielle réelle entre tasks, même si elles appartiennent à des services différents sur le même compte. Compromis : moins de contrôle sur l'instance sous-jacente (pas d'accès SSH à l'hôte, pas de choix du type d'instance EC2), en échange d'une isolation plus forte par défaut.\n\n## gMSA sur Fargate\n\nSupport depuis **mars 2024**, mais **limité aux conteneurs Linux**, via le mode **domainless gMSA** et le daemon `credentials-fetcher` packagé par AWS. Pas de support équivalent documenté pour des conteneurs Windows sur Fargate — vérifier ce point avant de bâtir une architecture qui en dépendrait.\n\nEn mode domainless, le conteneur n'est **pas** joint au domaine. `credentials-fetcher` récupère un identifiant AD **statique** (utilisateur + mot de passe + nom de domaine) stocké dans un secret AWS Secrets Manager, référencé par le fichier CredSpec de la task — ce n'est donc **pas une chaîne entièrement secretless** : il existe un credential AD permanent au bootstrap, protégé par IAM (accès au secret scoped au rôle d'exécution de la task), pas par une rotation JIT. Le conteneur applicatif, lui, ne voit jamais ce mot de passe — `credentials-fetcher` le consomme et expose un ticket Kerberos au conteneur.\n\n## Quand privilégier Fargate\n\nPertinent quand la charge de travail est une **passerelle privilégiée** vers un système sensible (ex. LDAP/AD) où la compromission d'un voisin de conteneur serait un risque inacceptable — le coût de l'isolation micro-VM se justifie par l'enjeu, pas par défaut pour toute charge de travail."
"content": "# ECS — launch type EC2 vs Fargate, isolation\n\n## EC2 launch type\n\nLes tasks ECS s'exécutent comme des conteneurs sur des instances EC2 gérées par le client, en partageant le **même noyau hôte**. La documentation AWS elle-même est explicite : les conteneurs ne sont pas une frontière d'isolation de sécurité garantie — un conteneur compromis peut potentiellement atteindre des ressources d'autres tasks colocalisées sur le même hôte (évasion de conteneur, attaque via le noyau partagé).\n\n## Fargate launch type\n\nChaque task tourne dans sa **propre micro-VM** dédiée — frontière d'isolation matérielle réelle entre tasks, même si elles appartiennent à des services différents sur le même compte. Compromis : moins de contrôle sur l'instance sous-jacente (pas d'accès SSH à l'hôte, pas de choix du type d'instance EC2), en échange d'une isolation plus forte par défaut.\n\n## gMSA sur Fargate\n\nSupport depuis **mars 2024**, mais **limité aux conteneurs Linux**, via le mode **domainless gMSA** et le daemon `credentials-fetcher` packagé par AWS. Pas de support équivalent documenté pour des conteneurs Windows sur Fargate — vérifier ce point avant de bâtir une architecture qui en dépendrait.\n\nEn mode domainless, le conteneur n'est **pas** joint au domaine. `credentials-fetcher` récupère un identifiant AD **statique** (utilisateur + mot de passe + nom de domaine) stocké dans un secret AWS Secrets Manager, référencé par le fichier CredSpec de la task — ce n'est donc **pas une chaîne entièrement secretless** : il existe un credential AD permanent au bootstrap, protégé par IAM (accès au secret scoped au rôle d'exécution de la task), pas par une rotation JIT. Le conteneur applicatif, lui, ne voit jamais ce mot de passe — `credentials-fetcher` le consomme et expose un ticket Kerberos au conteneur.\n\n## Quand privilégier Fargate\n\nPertinent quand la charge de travail est une **passerelle privilégiée** vers un système sensible (ex. LDAP/AD) où la compromission d'un voisin de conteneur serait un risque inacceptable — le coût de l'isolation micro-VM se justifie par l'enjeu, pas par défaut pour toute charge de travail.\n"
}
Loading