Conversation
) Suite de #1. Le verrou read_only (#176) protège les LECTEURS PURS ; gardien (AUDIT.md) et phare-* (tmp/audit/*) ont read-only-mode ET audit-output — « lecture seule SAUF ces chemins » — mais gardaient Write+Bash complet : sur workspace: shared + -p ., ils pouvaient écrire N'IMPORTE quel fichier source, pas seulement leur livrable. L'autre moitié du trou de #1. Chantier #10. Sous --dangerously-skip-permissions l'allowlist d'outils est indicative ; deux mécanismes verrouillent : 1. HOOK PreToolUse (scripts/audit_output_guard.sh), émis par behaviors/audit-output.yaml, assemblé/câblé par la chaîne existante. Il autorise Write/Edit/NotebookEdit UNIQUEMENT sur les chemins d'audit déclarés (params.paths), refuse ailleurs. Résistant à la traversée : résout le plus long préfixe existant (pwd -P → .. et symlinks résolus), rejette tout .. restant, compare des chemins ABSOLUS. Le REFUS passe par un JSON de décision sur stdout + exit 0 (le wrapper promptweave `|| exit 1` masquerait un exit 2) — format vérifié contre un vrai claude. 2. Bash retiré côté agent-loop (disallowedForAuditOutput = Bash + nested), car le matcher PreToolUse ne cible qu'Edit|Write|NotebookEdit : sans ça, `bash -c 'echo > src/x'` contournerait le path-scope. Write/Edit RESTENT (le hook les scope). Câblé via presetIsAuditOutput -> agent.audit_output -> AgentLoopConfig.auditOutput, aux deux sites d'envoi (send + interrupt). Acceptance #177 prouvée bout-en-bout (vrai claude, câblage de prod) : gardien écrit AUDIT.md (passe, non-régression #34) mais src/pwn.ts est REFUSÉ. Tests : 13 cas du guard (autorise AUDIT.md/tmp-audit-absent ; refuse src/traversée/ absolu/préfixe-trompeur), disallow audit-output (Bash oui, Write non), wiring (gardien audit_output=true, émet le hook avec ses chemins). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ne (revue #177) Revue adverse sécurité (12 findings). Les 2 critiques rendaient le guard INERTE en prod ; le test d'acceptation initial était invalide (guard en ISOLATION, pas la chaîne assemblée). - CRITIQUE stdin drainé : dans le hook pre-tool-use assemblé, activity-tracking (order 50) fait `INPUT=$(cat)` et vide le pipe avant le guard (order 60), qui lisait EOF -> autorisait tout. Corrigé EN AMONT dans promptweave >= 0.5.2 (assembleHooks bufferise stdin et le redonne à chaque hook). ^0.5.1 couvre 0.5.2. - CRITIQUE fail-open jq : sans jq (défaut Windows/Git-Bash), le guard autorisait tout. Désormais FAIL-CLOSED : jq absent OU stdin vide -> deny littéral (sans jq). - HAUT symlink : la feuille cible n'était pas résolue ; une cible symlink est désormais refusée (une écriture la suivrait hors scope). Test d'acceptation REFAIT via la VRAIE chaîne assemblée (audit_output_chain.test.ts) : AUDIT.md autorisé, src/x.ts refusé — ce test ÉCHOUE sur promptweave 0.5.1 (stdin drainé -> fail-closed refuse AUDIT.md), donc il verrouille la dépendance 0.5.2. Plus 3 cas fail-closed dans le .test.sh du guard. Reste en suivi (findings mineurs, #<suivi>) : chemin d'audit à espace (word-split ; aucun preset concerné), casse NTFS (faux-refus, direction sûre), gap `essaim solo` (pas de skip-permissions ni hook ; l'issue scopait solo en non-régression). REQUIS pour activer en prod : publier @swoofer/promptweave 0.5.2 puis `pnpm install`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…y non pollué (re-revue #177) 2e revue sécurité : une FUITE HAUTE résiduelle. Le hook pre-tool-use assemblé enchaîne check-interrupt (order 10), activity-tracking (50), puis le guard (60), partageant UN stdout. Sur interrupt en attente, check_interrupt émet une bannière MULTI-LIGNES (blank lines + détails de thread) AVANT le deny JSON du guard. Reproduit contre un vrai claude : le stdout n'est plus du JSON valide -> le parse échoue -> le deny est perdu -> l'écriture hors scope PASSE. La mitigation « guard en dernier » ne couvrait que le texte APRÈS le guard, pas celui d'avant. - check-interrupt est DÉPRÉCIÉ (les interrupts passent par le push MQTT de l'agent-loop) : on le retire des 6 presets audit-output (gardien, phare-*). Reste alors activity-tracking (stdout SILENCIEUX -> /dev/null) puis le guard : le deny JSON est le stdout UNIQUE et propre. Vérifié contre un vrai claude + parse JSON. - Tests : la chaîne assemblée rend un deny PARSABLE (JSON.parse) ; le hook pre-tool-use de gardien ne contient plus check_interrupt. Ferme les 3 confirmés de la re-revue (tous la même pollution de stdout). Les 3 critiques du 1er tour (stdin-drain, fail-open jq, symlink) sont confirmés clos. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e revue #177) Revue de faisabilité (workflow) : MultiEdit était un vecteur d'écriture NON couvert — absent du matcher PreToolUse (Edit|Write|NotebookEdit), du case du hook audit_output_guard.sh, ET du disallow audit-output. Sous --dangerously-skip-permissions l'allowlist (CODE_TOOLS, sans MultiEdit) est du théâtre : le modèle pouvait appeler MultiEdit pour écrire hors des chemins d'audit. Ajouté au tool-lock DISALLOWED_FOR_AUDIT_OUTPUT (redondant avec Edit, que le hook path-scope — le retirer ne coûte rien). Assertion ajoutée. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nerte sur Linux (#177) CI ubuntu : « audit_output_guard.sh: Permission denied ». Le wrapper de hook généré par promptweave invoque le script DIRECTEMENT (\"$BCE_SCRIPTS_DIR/x.sh\"), pas via `bash`. Commité en 100644 (sans bit exec), il n'est pas exécutable sur POSIX → le hook de décision ne produit AUCUN deny JSON → l'écriture hors scope serait AUTORISÉE en silence. L'enforcement #177 était donc inerte sur Linux (non détecté sur Windows/Git-Bash, qui ignore le bit exec). Mode passé à 100755. Note : les autres scripts/*.sh sont aussi 100644 (hooks non-décisionnels → échec avalé par `|| true`, no-op silencieux sur Linux) — bug pré-existant traité à part. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(pas d'exécution) CI : le chain-test échouait sur les DEUX plateformes en exécutant le wrapper bash multi-script assemblé via spawnSync — non portable : - ubuntu : le test concaténait stdout+stderr puis JSON.parse ; pre_track_activity.sh (100644, pré-existant) émettait « Permission denied » sur stderr → parse cassé. - windows : Git-Bash livrait un stdin vide au guard à travers le wrapper. - Sa prémisse était fausse (il prétendait que 0.5.1 draine stdin / exige 0.5.2 ; or 0.5.1 a déjà le buffer __BCE_HOOK_STDIN). Le comportement allow/deny du guard reste couvert, portablement, par tests/audit_output_guard.test.sh (exécute le guard directement, vert sur ubuntu ET windows). Ce test asserte désormais la COMPOSITION du wrapper — ce qui garantit la délivrance du deny : buffer stdin redonné AU guard (anti-drain), guard invoqué `|| true` (deny via stdout, pas exit code), check-interrupt absent. Portable. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Le compteur (P1)
gardientente d'écriresrc/x.tsETAUDIT.md⇒ AUDIT.md passe, src/x.ts refusé ;essaim solo gardienproduit AUDIT.md sur disque (non-régression #34), 0 autre fichier modifié. Closes #177.Le problème (moitié restante de #1)
gardien(écrit AUDIT.md) etphare-*(écrivent tmp/audit/*) ontread-only-modeETaudit-output→ « lecture seule SAUF ces chemins ». Ils gardaient Write+Bash complet : surworkspace: shared+-p ., ils pouvaient écrire n'importe quel source de l'utilisateur.--allowedToolsest par-outil, pas par-chemin, et--dangerously-skip-permissionsignore settings.json → le seul mécanisme de path-scope est un hook PreToolUse.La solution
scripts/audit_output_guard.shémis parbehaviors/audit-output.yamlavecparams.pathsen args : autorise Write/Edit/NotebookEdit uniquement sur les chemins d'audit, refuse ailleurs. Deny via JSON stdout + exit 0 (le wrapper promptweave|| exit 1avalerait un exit 2). Fail-closed (jq absent / stdin vide / chemin non résoluble), traversal-safe (resolve_physical + refus symlink).presetIsAuditOutput→audit_output→ agent-loop) : Bash + MultiEdit + nested agents retirés viadisallowedTools— les vecteurs d'écriture que le hook ne couvre pas (matcher = Edit|Write|NotebookEdit). MultiEdit fermé après revue (bypass sous skip-permissions).Note de faisabilité
Vérifié : livrable sur promptweave 0.5.1 (le buffer stdin y est déjà, assemble.js:97 ; l'état vulnérable était 0.5.0). Pas de dépendance à 0.5.2.
Tests falsifiables
tests/audit_output_guard.test.sh(bridgé vitest, skip si jq absent) : allow AUDIT.md / deny src/x.ts / deny traversal / fail-closed.tests/unit/audit_output_chain.test.ts: la vraie chaîne assemblée émet le hook avec les chemins.tests/unit/read-only-enforcement.test.ts: wiringaudit_output, tool-lock contient Bash/MultiEdit/nested et PAS Write/Edit.Hook vérifié à la main : deny src/x.ts + traversal + stdin vide, allow AUDIT.md — tous via JSON stdout+exit 0. 922 tests verts, build OK.
🤖 Generated with Claude Code