Montages Défenseurs en lecture-écriture alors que la doc dit lecture seule #36
Labels
No labels
autopilot:pending-human
source:analyste
source:defenseur
source:human
source:medic
status:approved
status:blocked
status:in-progress
status:needs-clarification
status:needs-fix
status:ready
status:review
status:triage
type:bug
type:feature
type:infra
type:refactor
type:schema
type:security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: maximus/vps-health-api#36
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Constat (sonde du 2026-09-30, agent-defenseurs#131)
docker inspectdu conteneuru8000gsg044wsk0oo0w884ok-232803574172:Le
CLAUDE.mddu dépôt décrit ces montages Défenseurs en lecture seule : c'est faux. LeMASTERPLAN.mdd'agent-defenseurs dit « lecture-écriture » : c'est lui qui a raison.Analyse (/analyze)
Prémisse vérifiée
Sonde refaite après le déploiement de
523d90b, sur le conteneuru8000gsg044wsk0oo0w884ok-004930023045: les trois montages sont enRW=true(Mode="rw"). Le compose généré par Coolify les écrit sans mode, sous la forme'/data/defenseurs:/data/defenseurs'.Nuance absente du constat : aucune écriture n'est possible aujourd'hui. Le conteneur tourne en uid 1000 (
node, sans groupe supplémentaire), et tout le contenu Défenseurs appartient àdefenseur(uid 1001), en 755 ou 775 pour les dossiers et 664 pour les fichiers. Dans le conteneur,test -wéchoue sur les six chemins Défenseurs et réussit sur/data/hosts. Côté code,index.jsne fait que lire les chemins Défenseurs ; toutes ses écritures visentHOSTS_DIR(lignes 408-416 et 453).Le
:roajoute donc une seconde couche de défense, il ne ferme pas une exposition active. Il compte si un attaquant obtient root dans le conteneur : le montage rw lui ouvrirait alorsfindings-state.jsonet les rapports, que le Sergent relit sous l'utilisateurdefenseur, membre du groupedockersur l'hôte.Historique
Personne n'a choisi le rw. Le
:roa été tenté deux fois parcustom_docker_run_options(le 2026-04-28, puis le 2026-07-15 en #10), et Coolify l'a ignoré en silence les deux fois. Les montages ont ensuite été posés sans mode :/data/defenseurspar unINSERTSQL danslocal_persistent_volumes,reportspar l'UI. La mention « Lecture seule » duCLAUDE.mddécrit l'usage de l'API, pas le mode du montage.Faisabilité
La table
local_persistent_volumesde Coolify beta.463 n'a pas de colonne lecture seule. Mais Coolify recopie la source et la destination telles quelles dans le compose : un suffixe:rodans la destination doit produire'/data/defenseurs:/data/defenseurs:ro', que Docker lit comme un montage en lecture seule. Ce suffixe n'a jamais été essayé, et la validation de l'UI reste inconnue (la lecture du code Coolify en production a été refusée par le classifieur du mode auto).Décisions (Max, via /analyze)
:rodans la destination des deux montages Défenseurs, par l'UI Coolify. Repli si Coolify refuse ou ignore le suffixe : documenter l'état réel (rw + droits Unix) et consigner l'impossibilité danscoolify-ops.md.node:fs, dans la même PR que la doc.docker inspect.USER node) ; passer l'app en build pack Docker Compose (re-provisionnement disproportionné pour une seconde couche).Travail à faire
/data/defenseurs:roet/data/defenseurs/reports:ro,/data/hostsinchangé. Redéployer.docker inspect, les quatre routes avec contenu, puis le battement suivant du cronhost-agent.node:fs, posé avant unrequirefrais (même montage que l'espiontimingSafeEqual,__tests__/auth.test.js:178-188). Démarrage, quatre routes de lecture, puisPOST /hosts/<id>en témoin positif : chaque chemin écrit doit être sousHOSTS_DIR. Rouge prouvé en ajoutant une écriture sousREPORTS_DIR. Un test parchmod 555ne prouverait rien, la CI tournant en root.la-compagnie-maximus/docs/coolify-ops.md:356-361(procédure lecture seule),defenseurs/MASTERPLAN.md:169,~/.claude/skills/diag-auth-drift/SKILL.md:284.Fichiers concernés
CLAUDE.md:44-46: « Lecture seule pour cette API ». Donner le mode réel et la façon de l'obtenir.CLAUDE.md:81: « contrairement aux montages/data/defenseurs». Droits Unix aujourd'hui,:roensuite.CLAUDE.md:86: gotcha des volumes Coolify. Ajouter la technique:ro, ou son échec.README.md:143: même ambiguïté..env.example:7-8: « parent /data/defenseurs/ is already mounted » est faux,reportsest un montage imbriqué distinct depuis/home/defenseur/defenseurs/reports..env.example:13: même ambiguïté queCLAUDE.md:81.__tests__/: nouveau test d'écriture.Pièges
renameatomique, et un bind de fichier reste accroché à l'ancien inode.reportsdans un parent en lecture seule. Il existe côté hôte (/data/defenseurs/reports, vrai dossier root en 755 créé le 2026-07-16) : ne pas le supprimer, même s'il paraît vide.:rone vaut que pour la vue du conteneur ; le Sergent continue d'écrire côté hôte.no_data: vérifier le contenu des routes, pas seulement le code HTTP.Critères d'acceptation
docker inspectdu nouveau conteneur :/data/defenseurset/data/defenseurs/reportsenRW=false,/data/hostsenRW=true./defenseurs,/defenseurs/state,/defenseurs/findings?project=<p>et/reports/scans?date=<dernier scan>répondent 200 avec du contenu réel, pasno_data.host-agentreçoit204.npm testvert ; rouge de l'espion prouvé par mutation.Complexité
Simple : un réglage Coolify, de la doc, un test. Moyenne si Coolify refuse le suffixe.
Hors périmètre
defenseurs/MASTERPLAN.md:112,:469et:546affirment qu'un push Forgejo déclenche un rebuild Coolify automatique. C'est faux (source_id=null, déploiement manuel) ; à corriger dans agent-defenseurs.Changement Coolify et vérification (2026-09-30 UTC)
Changement (Max, UI Coolify)
vps-health-defenseurs: destination/data/defenseursremplacée par/data/defenseurs:ro.reports: le Directory Mount n'est pas modifiable dans l'UI. Il a été supprimé sans cocher la suppression du dossier sur le serveur, puis recréé en Volume Mount (Source Path/home/defenseur/defenseurs/reports, destination/data/defenseurs/reports:ro)./data/hostsinchangé.~/backups/defenseurs-reports/reports-20260930T012915Z.tgzsur le VPS (1947 fichiers, identique au disque). Restauration si besoin :sudo tar xzpf <archive> -C /home/defenseur/defenseurs.finishedà 01:33:39 UTC, commit523d90b, conteneuru8000gsg044wsk0oo0w884ok-013300798249.Vérification
docker inspect:Vue du noyau dans le conteneur (
/proc/mounts) :/data/defenseurs ro,/data/defenseurs/reports ro,/data/hosts rw.Compose généré par Coolify :
Coolify beta.463 recopie donc tel quel le suffixe
:rode la destination dans le compose : c'est la technique à documenter.Routes de lecture, appelées depuis le conteneur avec son propre token (aucune ne répond
no_data) :/defenseursstatus.jsoncomplet (agents,summary,topFindings)/defenseurs/statefindings-state.jsoncomplet/defenseurs/findings?project=agentsdefenseur-agentsdu 2026-09-30 00:09 UTC, 8 constats/reports/scans?date=2026-09-30Dossier des rapports intact après la suppression du Directory Mount : 1947 fichiers. Le point de montage
/data/defenseurs/reports(root, 755) est toujours présent côté hôte.Ingestion des postes : le battement
host-agentde 01:35:02 UTC, premier après le redéploiement, a reçu204.Reste à faire
Doc du dépôt, espion sur les écritures
node:fset docs hors dépôt, par/fix-issue 36.