Aller au contenu

Changelog

Source de vérité

Le changelog du projet vit dans CHANGELOG.md à la racine du dépôt — c'est la seule version maintenue. Cette page le rend simplement consultable depuis la documentation publiée ; elle ne le duplique pas.

Changelog

Toutes les évolutions notables du projet Kirexo sont consignées ici.

Le format suit Keep a Changelog et le projet adhère, autant que possible, au Versionnement Sémantique.

Tant qu'aucune version 1.0.0 n'a été publiée, les évolutions sont versionnées par étape de cadrage (cf. Étapes/étape<n>.md). Une version 0.X.0 correspond à la clôture de l'étape X.

0.3.0 — 2026-05-19

Correctifs rétrospective étape 3 — 2026-05-18

  • Fusion FirewallWhitelistStoreFirewallWhitelist : une seule classe src/DevContainer/FirewallWhitelist.php combine désormais la logique pure statique (isValidDomain, parseDomainLines, isAlreadyCovered + constantes DOMAIN_REGEX, DEFAULT_BASELINE_PATH, DEFAULT_LOCAL_PATH) et les méthodes d'instance d'I/O fichier (readBaseline, readLocal, readLocalRejected, isInBaseline, isInLocal, appendLocal, removeFromLocal). La méthode parseLocal est renommée parseDomainLines pour aligner la sémantique avec le helper bash firewall_parse_domain_lines (.devcontainer/init-firewall.lib.sh).
  • Extraction de logique pure dans ToolsUpgrader : selectFrankenphpCandidate() (sélection de tag candidat pour l'image base FrankenPHP, comparaison de tuples (franken-major, php-major, php-minor) + exclusion régression PHP) et buildDockerfileUrls() (reconstruction des URLs des RUN curl … à partir des ARG *_VERSION). Permet la couverture unitaire de fetch_frankenphp_drift() et de la sous-étape host:check HEAD HTTP sans réseau.
  • Section « Ce que l'isolation ne protège pas » ajoutée dans docs/explanation/devcontainer.md entre « Comment ça marche — flow réseau » et « Pourquoi NE PAS monter /var/run/docker.sock ». Deux limites assumées documentées : exfiltration via l'API Anthropic (les credentials lus dans ${HOME}/.claude/.credentials.json peuvent partir dans le contenu d'une requête vers api.anthropic.com légitimement whitelisté — le RO empêche la persistance entre sessions, pas l'exfiltration en session) et contournement potentiel via DNS-over-HTTPS (préférer des FQDNs précis, pas des suffixes génériques de CDN).
  • Contrôle négatif documentaire : 203.0.113.1 (IP RFC 5737 TEST-NET-3, garantie non routable) remplace example.com dans le tutoriel Ouvrir Kirexo dans un Dev Container PHPStorm. Reste proche du modèle de menace (filtrage par socket, pas seulement par résolution DNS) et évite qu'un domaine de test finisse par accident dans la whitelist.
  • Références obsolètes à init-firewall.sh pour l'ajout de domaine corrigées : .devcontainer/AGENTS.md et .devcontainer/whitelist.local.txt.dist pointent désormais vers baseline-domains.txt (source de vérité versionnée de la baseline) et castor devcontainer:whitelist-add. La whitelist n'est plus inlinée dans init-firewall.sh.
  • castor devcontainer:check-tools / upgrade-tools étendus au drift de l'image base FrankenPHP : interrogation de Docker Hub (hub.docker.com/v2/repositories/dunglas/frankenphp/tags/), warning non bloquant dans upgrade-tools (le bump cible une instruction FROM, pas un ARG — donc manuel), compté dans le exit 2 de --check. Couvre indirectement les paquets apt et install-php-extensions livrés via la base.
  • castor host:check étendu de 3 à 5 sous-étapes : ajout de hadolint sur le Dockerfile (docker run --rm -i hadolint/hadolint:latest hadolint --failure-threshold error - < Dockerfile, seules les erreurs sont bloquantes ; warnings et info remontés mais tolérés) et de HEAD HTTP en parallèle sur les URLs construites par ToolsUpgrader::buildDockerfileUrls() à partir des ARG *_VERSION (Castor, glab, tarball Node, SHASUMS256.txt). Détecte une release upstream supprimée/renommée avant un rebuild qui rejetterait silencieusement un fichier corrompu via sha256sum -c. Coût observé : ~0.8 s en cache, ~3.5 s au premier appel (pull de l'image hadolint).
  • Documentation Diátaxis mise à jour :

Ajouté

  • CHANGELOG.md racine au format Keep a Changelog, exposé dans la doc Diátaxis en complément de la nav existante.
  • Dev Container dérivé de dunglas/symfony-docker :
    • .devcontainer/devcontainer.json, compose.devcontainer.yaml, post-create.sh, post-start.sh.
    • Stack complète accessible depuis l'IDE (PHPStorm 2024.1+ ou VS Code) sans installer PHP/Node/Composer côté hôte.
    • Stratégie d'isolation sans socket Docker montécastor docker:*, cert:trust, doctor, docs:build refusent de tourner depuis l'intérieur (cf. assert_outside_container() dans castor.php).
  • Pare-feu interne (init-firewall.sh) restreignant le trafic sortant à une whitelist de domaines :
    • Policies OUTPUT/INPUT/FORWARD DROP posées immédiatement après le flush.
    • dnsmasq + ipset allowed-domains alimentés à la volée à chaque résolution DNS.
    • Plage CIDR intra-stack unique 172.30.0.0/24 (réseau Docker custom kirexo) au lieu des trois RFC1918.
    • Override local whitelist.local.txt (gitignored) concaténé à la baseline si présent.
    • Smoke-tests post-reload : contrôle négatif bloquant sur 203.0.113.1 (RFC 5737), contrôles positifs non bloquants sur gitlab.com / symfony.com / packagist.org / registry.npmjs.org, résolution intra-stack bloquante.
  • Mode bypassPermissions Claude Code activé par post-create.sh via création de .claude/settings.local.json, avec opt-out :
    • One-shot par variable d'environnement KIREXO_CLAUDE_BYPASS=0.
    • Persistant par marqueur fichier .devcontainer/no-claude-bypass (gitignored).
  • Embarquement direct de Node + Claude Code dans le stage frankenphp_dev du Dockerfile (et non via les features du devcontainer) pour contourner un bug du pipeline JetBrains qui re-cible le stage frankenphp_dev sur un Dockerfile temporaire.
  • Vérification d'intégrité par SHA-256 sur les binaires Castor, glab et Node (ARG *_VERSION + *_SHA256). Claude Code repose sur le dist.integrity SRI sha512 du registre npm.
  • autocomplete Castor system-wide dans le devcontainer via /etc/bash.bashrc (sourcing de bash-completion + eval "$(castor completion bash)").
  • Logique pure testable extraite dans src/DevContainer/ :
    • FirewallWhitelist — validation FQDN, parsing baseline, dédoublonnage.
    • ToolsUpgrader — extraction et substitution d'ARG dans le Dockerfile et la doc.
  • Nouvelles cibles Castor (cf. docs/reference/commandes-castor.md) :
    • castor devcontainer:upgrade-tools [--check|--apply] — résout les latest (Castor via GitHub API, glab via GitLab API, Node majeure LTS via nodejs/Release/schedule.json, Node patch via nodejs.org/dist/index.json, Claude Code via npm registry), recalcule les SHA-256, met à jour le Dockerfile et la doc.
    • castor devcontainer:firewall-reload — wrapper sur sudo init-firewall.sh, refuse l'hôte.
    • castor devcontainer:whitelist-add <domain> / whitelist-remove <domain> — édite whitelist.local.txt puis recharge le pare-feu, avec validation FQDN et dédoublonnage.
    • castor devcontainer:doctor — pendant interne de castor doctor (vérifie l'amorçage, dnsmasq, l'ipset, FrankenPHP sur 127.0.0.1, la résolution intra-stack, les migrations à jour).
    • castor devcontainer:frankenphp-restart — relance le supervisor FrankenPHP après bail-out ou kill manuel.
    • castor lint:shellshellcheck sur les scripts .devcontainer/*.sh et .claude/hooks/*.sh, intégrée à castor lint.
  • Supervisor FrankenPHP (frankenphp-supervisor.sh) avec backoff exponentiel borné (INITIAL_DELAY=2, MAX_DELAY=30) et bail-out après 5 crashs consécutifs sans stabilisation (>60s).
  • Marqueurs d'amorçage .devcontainer/.bootstrap.{ok,failed} écrits par post-create.sh et consultés en première ligne par castor devcontainer:doctor.
  • Tests unitaires :
    • FirewallWhitelistTest — validation FQDN, parsing baseline et locale, dédoublonnage.
    • ToolsUpgraderTest — extraction et substitution d'ARG, calcul du diff de versions.
    • FirewallRegexConsistencyTest — garde-fou contre le drift entre la regex bash (DOMAIN_VALIDATION_REGEX) et la regex PHP (FirewallWhitelist::DOMAIN_REGEX) : égalité textuelle + dataset croisé preg_match[[ =~ ]].
  • Documentation Diátaxis :
  • .devcontainer/whitelist.local.txt.dist versionné comme modèle pour le fichier d'override local.

Modifié

  • assert_outside_container() (castor.php) accepte désormais une suggestion contextuelle d'alternative ; castor doctor et castor docs:build lancés depuis l'intérieur du devcontainer affichent une commande utilisable directement (castor devcontainer:doctor et curl -sIf http://localhost:8000/ respectivement) en plus du refus.
  • Dockerfile (stage frankenphp_dev) : ajout de sudo, iptables, ipset, dnsmasq, jq, iproute2, ca-certificates, shellcheck, bash-completion. Création d'un user app aligné sur l'UID/GID hôte.
  • frankenphp/Caddyfile : ajout de skip_install_trust au bloc global pour empêcher Caddy de tenter d'installer sa CA racine dans le trust store du conteneur (qui exigerait sudo).
  • compose.override.yaml : définition d'un réseau Docker custom kirexo (172.30.0.0/24) + build args USER_ID/GROUP_ID.

Sécurité

  • bypassPermissions Claude Code rendu acceptable par l'addition de deux barrières structurelles : isolation filesystem (le conteneur ne voit que /app) + isolation réseau (pare-feu iptables sur whitelist). Sans ces deux barrières, ne pas reproduire ce paramétrage.
  • GitHub volontairement exclu de la whitelist : le projet est hébergé sur GitLab, et les binaires GitHub indispensables (Castor, glab) sont récupérés au build de l'image via le réseau hôte, pas au runtime depuis le conteneur.
  • Durcissements post-audit (4 mécanismes ajoutés en seconde passe, cf. Référence) :
    • Surcharge RO ciblée sur ${HOME}/.claude/.credentials.json, ${HOME}/.claude/settings.json et ${HOME}/.claude/CLAUDE.md — un Claude éventuellement compromis dans le conteneur ne peut plus réécrire ces fichiers (exfiltration via la prochaine session côté hôte impossible). L'historique de conversations (projects/, sessions/, history.jsonl) reste en RW pour rester persisté côté hôte.
    • Bind-mount RO sur init-firewall.sh, init-firewall.lib.sh, baseline-domains.txt et firewall-healthcheck.sh — un sous-processus malveillant ne peut plus réécrire les scripts puis attendre le prochain reload pour s'exécuter en root via le sudoers NOPASSWD. Pour modifier ces fichiers, redémarrer le devcontainer.
    • security_opt: ["no-new-privileges:true"] — empêche un binaire setuid/setgid d'élever ses droits dans le conteneur.
    • Healthcheck enrichi d'une 4e condition sudo -n /app/.devcontainer/firewall-healthcheck.sh (wrapper read-only qui vérifie iptables -S OUTPUT | grep -q '^-P OUTPUT DROP'). Détecte un iptables -F manuel oublié qui laissait jusqu'ici le pare-feu silencieusement ouvert avec dnsmasq encore vert.

Ajouté (post-audit)

  • Tests bats dans tests/Bash/firewall/ (43 tests, 3 fichiers) — couvrent les fonctions pures firewall_is_valid_domain, firewall_parse_domain_lines, firewall_is_already_covered. Premier lot de tests shell du projet ; convention bats documentée dans tests/Bash/firewall/README.md.
  • .devcontainer/init-firewall.lib.sh — fonctions pures extraites de init-firewall.sh pour permettre les tests bats sans NET_ADMIN. La regex FIREWALL_DOMAIN_REGEX y est définie en readonly, partagée avec App\DevContainer\FirewallWhitelist::DOMAIN_REGEX côté PHP.
  • .devcontainer/firewall-healthcheck.sh — wrapper sudo read-only utilisé par le healthcheck Docker pour valider que la policy OUTPUT DROP est toujours posée.
  • Cible Castor test:bash + intégration dans castor test et castor all (entre test:unit et test:e2e). Lance bats --recursive tests/Bash/ dans le conteneur.
  • Pare-feu activé dans post-create.sh (avant castor install) — la phase d'amorçage Composer/importmap/Tailwind est maintenant filtrée par la whitelist au lieu de tourner avec un réseau ouvert.
  • Mermaid activé dans mkdocs (pymdownx.superfences + custom_fences) — diagrammes du flow réseau (DNS → dnsmasq → ipset → iptables, cas rejet, ordre d'exécution) ajoutés dans docs/explanation/devcontainer.md.
  • bats ajouté au stage frankenphp_dev du Dockerfile (paquet Debian, v1.x).
  • Auto-relance de FrankenPHP via le healthcheck Docker du service php (devcontainer) — rattrape le cas où PhpStorm zappe le postStartCommand en s'attachant à un conteneur déjà démarré. Logique déportée dans .devcontainer/php-healthcheck.sh (bind-mount RO, deny-list Claude), avec deux garde-fous (pgrep frankenphp-supervisor.sh + lockfile mtime RELAUNCH_MIN_INTERVAL=30s), détachement via setsid -f et audit dans var/log/devcontainer/frankenphp.log. Cf. Référence — Auto-relance de FrankenPHP.

Modifié (post-audit)

  • castor.php allégé (1602 → 868 lignes) : extraction des cibles devcontainer:* + helpers associés dans castor/devcontainer.php, importé via Castor\import(). Inventaire des cibles identique (46), comportement inchangé.
  • init-firewall.sh refactoré pour sourcer init-firewall.lib.sh — suppression des deux boucles inlinées de parsing baseline/locale, validation FQDN déléguée à firewall_is_valid_domain. Sémantique inchangée.
  • Dockerfile (stage frankenphp_dev) : sudoers étendu à un second script (firewall-healthcheck.sh) en NOPASSWD — auparavant seul init-firewall.sh était autorisé.
  • compose.devcontainer.yaml : ajout security_opt, 3 bind-mounts RO ${HOME}/.claude/*, 5 bind-mounts RO .devcontainer/* (dont php-healthcheck.sh), healthcheck déporté dans php-healthcheck.sh (4 conditions enchaînées + auto-relance FrankenPHP).
  • .devcontainer/AGENTS.md : pointer explicite vers /CLAUDE.md comme source de vérité primaire, rappel du quota 5 fichiers PHP et de la délégation obligatoire aux sous-agents.

Défaut connu (à corriger en suivi)

  • FIREWALL_DOMAIN_REGEX accepte les IPs littérales : la regex actuelle laisse passer 1.2.3.4 comme FQDN valide (labels purement numériques autorisés). La doc dit le contraire. Découvert pendant l'écriture des tests bats — le test verrouille le comportement actuel avec un commentaire pointant l'inversion à faire le jour où on durcit la regex (côté lib et côté FirewallWhitelist::DOMAIN_REGEX PHP, sinon FirewallRegexConsistencyTest cassera).

0.2.0 — Étape 2 — Cadrage, outillage et documentation

Ajouté

  • castor.php à la racine — task runner Castor, source unique des cibles de qualité et de développement (cs:fix, phpstan, lint, test:unit, test:e2e, qa, all, …).
  • Documentation Diátaxis initiale publiée via mkdocs-material (service docs du compose).
  • mkdocs.yml à la racine + image custom mkdocs/Dockerfile (plugins git-revision-date-localized et social).
  • Sous-agents Claude Code dans .claude/agents/ (reviewer, test-writer, doc-writer) avec hooks de délégation et de comptage de fichiers PHP de production.

0.1.0 — Étape 1 — Bootstrap

Ajouté

  • Bootstrap du projet à partir de dunglas/symfony-docker (FrankenPHP + Symfony + Docker Compose).
  • Services Postgres, Redis, RabbitMQ, Typesense, Gotenberg, Mailpit ajoutés au compose.
  • CLAUDE.md initial — cadrage technique, patterns d'architecture (CQRS, plugins isolés, Voters, Live Components, DTOs immuables, Repository pattern), règles de développement.