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
FirewallWhitelistStore→FirewallWhitelist: une seule classesrc/DevContainer/FirewallWhitelist.phpcombine désormais la logique pure statique (isValidDomain,parseDomainLines,isAlreadyCovered+ constantesDOMAIN_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éthodeparseLocalest renomméeparseDomainLinespour aligner la sémantique avec le helper bashfirewall_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) etbuildDockerfileUrls()(reconstruction des URLs desRUN curl …à partir desARG *_VERSION). Permet la couverture unitaire defetch_frankenphp_drift()et de la sous-étapehost:checkHEAD HTTP sans réseau. - Section « Ce que l'isolation ne protège pas » ajoutée dans
docs/explanation/devcontainer.mdentre « 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.jsonpeuvent partir dans le contenu d'une requête versapi.anthropic.comlé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) remplaceexample.comdans 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.shpour l'ajout de domaine corrigées :.devcontainer/AGENTS.mdet.devcontainer/whitelist.local.txt.distpointent désormais versbaseline-domains.txt(source de vérité versionnée de la baseline) etcastor devcontainer:whitelist-add. La whitelist n'est plus inlinée dansinit-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 dansupgrade-tools(le bump cible une instructionFROM, pas unARG— donc manuel), compté dans leexit 2de--check. Couvre indirectement les paquets apt etinstall-php-extensionslivrés via la base.castor host:checkétendu de 3 à 5 sous-étapes : ajout de hadolint sur leDockerfile(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 parToolsUpgrader::buildDockerfileUrls()à partir desARG *_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 viasha256sum -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 :
docs/reference/devcontainer.md:parseLocal()→parseDomainLines()dans la lignebaseline-domains.txt, nouvelle section « Surveillance de l'image base FrankenPHP » sous l'outillage.docs/reference/commandes-castor.md: descriptions dehost:check(5 sous-étapes),check-toolsetupgrade-toolsenrichies du volet image base FrankenPHP.docs/explanation/devcontainer.md: section « Ce que l'isolation ne protège pas ».
Ajouté¶
CHANGELOG.mdracine 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:buildrefusent de tourner depuis l'intérieur (cf.assert_outside_container()danscastor.php).
- Pare-feu interne (
init-firewall.sh) restreignant le trafic sortant à une whitelist de domaines :- Policies
OUTPUT/INPUT/FORWARD DROPposées immédiatement après le flush. dnsmasq+ipset allowed-domainsalimentés à la volée à chaque résolution DNS.- Plage CIDR intra-stack unique
172.30.0.0/24(réseau Docker customkirexo) 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 surgitlab.com/symfony.com/packagist.org/registry.npmjs.org, résolution intra-stack bloquante.
- Policies
- Mode
bypassPermissionsClaude Code activé parpost-create.shvia 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).
- One-shot par variable d'environnement
- Embarquement direct de Node + Claude Code dans le stage
frankenphp_devduDockerfile(et non via lesfeaturesdu devcontainer) pour contourner un bug du pipeline JetBrains qui re-cible le stagefrankenphp_devsur 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 ledist.integritySRI sha512 du registre npm. autocomplete Castorsystem-wide dans le devcontainer via/etc/bash.bashrc(sourcing debash-completion+eval "$(castor completion bash)").- Logique pure testable extraite dans
src/DevContainer/:FirewallWhitelist— validation FQDN, parsing baseline, dédoublonnage.ToolsUpgrader— extraction et substitution d'ARGdans leDockerfileet la doc.
- Nouvelles cibles Castor (cf.
docs/reference/commandes-castor.md) :castor devcontainer:upgrade-tools [--check|--apply]— résout leslatest(Castor via GitHub API, glab via GitLab API, Node majeure LTS vianodejs/Release/schedule.json, Node patch vianodejs.org/dist/index.json, Claude Code via npm registry), recalcule les SHA-256, met à jour leDockerfileet la doc.castor devcontainer:firewall-reload— wrapper sursudo init-firewall.sh, refuse l'hôte.castor devcontainer:whitelist-add <domain>/whitelist-remove <domain>— éditewhitelist.local.txtpuis recharge le pare-feu, avec validation FQDN et dédoublonnage.castor devcontainer:doctor— pendant interne decastor doctor(vérifie l'amorçage, dnsmasq, l'ipset, FrankenPHP sur127.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:shell—shellchecksur les scripts.devcontainer/*.shet.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 parpost-create.shet consultés en première ligne parcastor 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 :
- Tutoriel : Ouvrir Kirexo dans un Dev Container PHPStorm.
- Guides : Whitelister un domaine, Debugger le pare-feu, Activer l'autocomplétion Castor sur l'hôte.
- Référence : Dev Container.
- Explication : Choix d'isolation du Dev Container.
.devcontainer/whitelist.local.txt.distversionné comme modèle pour le fichier d'override local.
Modifié¶
assert_outside_container()(castor.php) accepte désormais une suggestion contextuelle d'alternative ;castor doctoretcastor docs:buildlancés depuis l'intérieur du devcontainer affichent une commande utilisable directement (castor devcontainer:doctoretcurl -sIf http://localhost:8000/respectivement) en plus du refus.Dockerfile(stagefrankenphp_dev) : ajout desudo,iptables,ipset,dnsmasq,jq,iproute2,ca-certificates,shellcheck,bash-completion. Création d'un userappaligné sur l'UID/GID hôte.frankenphp/Caddyfile: ajout deskip_install_trustau bloc global pour empêcher Caddy de tenter d'installer sa CA racine dans le trust store du conteneur (qui exigeraitsudo).compose.override.yaml: définition d'un réseau Docker customkirexo(172.30.0.0/24) + build argsUSER_ID/GROUP_ID.
Sécurité¶
bypassPermissionsClaude 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.jsonet${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.txtetfirewall-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érifieiptables -S OUTPUT | grep -q '^-P OUTPUT DROP'). Détecte uniptables -Fmanuel oublié qui laissait jusqu'ici le pare-feu silencieusement ouvert avec dnsmasq encore vert.
- Surcharge RO ciblée sur
Ajouté (post-audit)¶
- Tests bats dans
tests/Bash/firewall/(43 tests, 3 fichiers) — couvrent les fonctions puresfirewall_is_valid_domain,firewall_parse_domain_lines,firewall_is_already_covered. Premier lot de tests shell du projet ; convention bats documentée danstests/Bash/firewall/README.md. .devcontainer/init-firewall.lib.sh— fonctions pures extraites deinit-firewall.shpour permettre les tests bats sans NET_ADMIN. La regexFIREWALL_DOMAIN_REGEXy est définie enreadonly, partagée avecApp\DevContainer\FirewallWhitelist::DOMAIN_REGEXcôté PHP..devcontainer/firewall-healthcheck.sh— wrapper sudo read-only utilisé par le healthcheck Docker pour valider que la policyOUTPUT DROPest toujours posée.- Cible Castor
test:bash+ intégration danscastor testetcastor all(entretest:unitettest:e2e). Lancebats --recursive tests/Bash/dans le conteneur. - Pare-feu activé dans
post-create.sh(avantcastor 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 dansdocs/explanation/devcontainer.md. batsajouté au stagefrankenphp_devdu Dockerfile (paquet Debian, v1.x).- Auto-relance de FrankenPHP via le healthcheck Docker du service
php(devcontainer) — rattrape le cas où PhpStorm zappe lepostStartCommanden 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 mtimeRELAUNCH_MIN_INTERVAL=30s), détachement viasetsid -fet audit dansvar/log/devcontainer/frankenphp.log. Cf. Référence — Auto-relance de FrankenPHP.
Modifié (post-audit)¶
castor.phpallégé (1602 → 868 lignes) : extraction des ciblesdevcontainer:*+ helpers associés danscastor/devcontainer.php, importé viaCastor\import(). Inventaire des cibles identique (46), comportement inchangé.init-firewall.shrefactoré pour sourcerinit-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(stagefrankenphp_dev) : sudoers étendu à un second script (firewall-healthcheck.sh) en NOPASSWD — auparavant seulinit-firewall.shétait autorisé.compose.devcontainer.yaml: ajoutsecurity_opt, 3 bind-mounts RO${HOME}/.claude/*, 5 bind-mounts RO.devcontainer/*(dontphp-healthcheck.sh), healthcheck déporté dansphp-healthcheck.sh(4 conditions enchaînées + auto-relance FrankenPHP)..devcontainer/AGENTS.md: pointer explicite vers/CLAUDE.mdcomme 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_REGEXaccepte les IPs littérales : la regex actuelle laisse passer1.2.3.4comme 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_REGEXPHP, sinonFirewallRegexConsistencyTestcassera).
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(servicedocsdu compose). mkdocs.ymlà la racine + image custommkdocs/Dockerfile(pluginsgit-revision-date-localizedetsocial).- 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.mdinitial — cadrage technique, patterns d'architecture (CQRS, plugins isolés, Voters, Live Components, DTOs immuables, Repository pattern), règles de développement.