Le code ajouté est bien structuré et suit les bonnes pratiques Rust (utilisation de thiserror, serde, PathBuf, etc.). La fonctionnalité de parsing devcontainer est claire et bien testée. Cependant, quelques points méritent attention :
La sécurité du conteneur de développement (option label=disable).
La gestion des champs optionnels du schéma devcontainer (le champ build est obligatoire alors qu'il pourrait être absent).
La fonction first_existing_container_file renvoie un chemin par défaut qui échouera si aucun fichier n'existe, ce qui pourrait être amélioré.
Les tests pourraient être étendus pour couvrir la fonction asynchrone parse.
Dans l'ensemble, le travail est de bonne qualité et prêt à être fusionné après ces ajustements.
Cost: $0.00146804
## Review Feedback
### 5 issues found.
---
### Summary
Le code ajouté est bien structuré et suit les bonnes pratiques Rust (utilisation de `thiserror`, `serde`, `PathBuf`, etc.). La fonctionnalité de parsing devcontainer est claire et bien testée. Cependant, quelques points méritent attention :
- La sécurité du conteneur de développement (option `label=disable`).
- La gestion des champs optionnels du schéma devcontainer (le champ `build` est obligatoire alors qu'il pourrait être absent).
- La fonction `first_existing_container_file` renvoie un chemin par défaut qui échouera si aucun fichier n'existe, ce qui pourrait être amélioré.
- Les tests pourraient être étendus pour couvrir la fonction asynchrone `parse`.
Dans l'ensemble, le travail est de bonne qualité et prêt à être fusionné après ces ajustements.
---
### Cost: $0.00146804
L'option --security-opt label=disable désactive la sécurité SELinux, ce qui réduit l'isolation du conteneur. Pour un environnement de développement, cela peut être acceptable, mais il faut être conscient des implications de sécurité. Envisagez d'utiliser --security-opt label=type:container_runtime_t ou d'autres options moins permissives si possible.
L'option `--security-opt label=disable` désactive la sécurité SELinux, ce qui réduit l'isolation du conteneur. Pour un environnement de développement, cela peut être acceptable, mais il faut être conscient des implications de sécurité. Envisagez d'utiliser `--security-opt label=type:container_runtime_t` ou d'autres options moins permissives si possible.
Le champ build est obligatoire, mais le schéma standard devcontainer.json permet également d'utiliser image à la place de build. Si quelqu'un utilise image, la désérialisation échouera. Envisagez de rendre build optionnel avec #[serde(default)] et de gérer le cas où ni build ni image ne sont présents.
Le champ `build` est obligatoire, mais le schéma standard devcontainer.json permet également d'utiliser `image` à la place de `build`. Si quelqu'un utilise `image`, la désérialisation échouera. Envisagez de rendre `build` optionnel avec `#[serde(default)]` et de gérer le cas où ni `build` ni `image` ne sont présents.
Si aucun fichier Dockerfile ou Containerfile n'existe, la fonction retourne base_dir.join("Dockerfile"), ce qui échouera plus tard avec ContainerFileNotFound. Cela pourrait être plus explicite en retournant une Option<PathBuf> et en laissant l'appelant gérer l'absence de fichier. Sinon, le message d'erreur actuel est suffisant.
Si aucun fichier `Dockerfile` ou `Containerfile` n'existe, la fonction retourne `base_dir.join("Dockerfile")`, ce qui échouera plus tard avec `ContainerFileNotFound`. Cela pourrait être plus explicite en retournant une `Option<PathBuf>` et en laissant l'appelant gérer l'absence de fichier. Sinon, le message d'erreur actuel est suffisant.
La fonction parse est asynchrone, mais les tests unitaires ne la testent pas directement (ils testent TryFrom). Il serait bon d'ajouter un test asynchrone pour parse afin de couvrir le flux complet (lecture du fichier, désérialisation, conversion).
La fonction `parse` est asynchrone, mais les tests unitaires ne la testent pas directement (ils testent `TryFrom`). Il serait bon d'ajouter un test asynchrone pour `parse` afin de couvrir le flux complet (lecture du fichier, désérialisation, conversion).
Les tests sont synchrones et utilisent std::fs plutôt que tokio::fs. C'est acceptable pour l'instant, mais si l'implémentation venait à dépendre de fonctionnalités asynchrones (comme tokio::fs), les tests pourraient ne plus être représentatifs.
Les tests sont synchrones et utilisent `std::fs` plutôt que `tokio::fs`. C'est acceptable pour l'instant, mais si l'implémentation venait à dépendre de fonctionnalités asynchrones (comme `tokio::fs`), les tests pourraient ne plus être représentatifs.
Remove the openrouter-rs dependency in favor of a minimal in-tree
OpenRouter chat-completions client, and drop the BOT_NAME and
SANDBOX_ENABLED config options. Reviews now always run inside the
sandbox, and the review prompt asks the model to read files with the
available tools instead of embedding the diff.
La mise en place du sandboxing est complète et bien structurée : isolation réseau, timeouts, nettoyage systématique, résolution de chemins sécurisée. Quelques points mineurs de robustesse (gestion des chemins non-UTF8, redondance de vérification) et de sécurité (fuite potentielle du token dans les logs git) pourraient être améliorés, mais l’ensemble est de bonne qualité et conforme aux bonnes pratiques. Les tests unitaires couvrent les cas importants.
Cost: $0.007599319
## Review Feedback
### 4 issues found.
---
### Summary
La mise en place du sandboxing est complète et bien structurée : isolation réseau, timeouts, nettoyage systématique, résolution de chemins sécurisée. Quelques points mineurs de robustesse (gestion des chemins non-UTF8, redondance de vérification) et de sécurité (fuite potentielle du token dans les logs git) pourraient être améliorés, mais l’ensemble est de bonne qualité et conforme aux bonnes pratiques. Les tests unitaires couvrent les cas importants.
---
### Cost: $0.007599319
La fonction sanitize tronque les tirets et points en début et fin, mais elle pourrait laisser passer des séquences de deux tirets consécutifs qui sont autorisées par Docker. Ce n’est pas critique, mais une validation plus stricte (limiter la longueur, interdire les séquences interdites) améliorerait la robustesse.
La fonction `sanitize` tronque les tirets et points en début et fin, mais elle pourrait laisser passer des séquences de deux tirets consécutifs qui sont autorisées par Docker. Ce n’est pas critique, mais une validation plus stricte (limiter la longueur, interdire les séquences interdites) améliorerait la robustesse.
Le chemin dest est converti en chaîne via display(), ce qui peut planter si le chemin contient des caractères non-UTF-8. Dans un contexte Linux, c’est très rare, mais il serait plus sûr d’utiliser to_string_lossy().
Le chemin `dest` est converti en chaîne via `display()`, ce qui peut planter si le chemin contient des caractères non-UTF-8. Dans un contexte Linux, c’est très rare, mais il serait plus sûr d’utiliser `to_string_lossy()`.
Le token d’authentification est passé via GIT_CONFIG_VALUE_0. C’est correct car il n’apparaît pas dans les arguments. Toutefois, en cas d’erreur, git peut afficher la ligne de commande dans les logs, ce qui pourrait exposer le token indirectement via l’URL de clonage. Envisager d’utiliser un credential helper ou de filtrer les messages d’erreur.
Le token d’authentification est passé via `GIT_CONFIG_VALUE_0`. C’est correct car il n’apparaît pas dans les arguments. Toutefois, en cas d’erreur, git peut afficher la ligne de commande dans les logs, ce qui pourrait exposer le token indirectement via l’URL de clonage. Envisager d’utiliser un credential helper ou de filtrer les messages d’erreur.
La vérification est redondante : normalize() garantit déjà que le chemin ne peut pas sortir de la racine. Cependant, la double protection ne pose pas de problème de sécurité. On pourrait simplifier en supprimant cette condition si on est certain que normalize() ne retourne qu’un chemin bien formé.
La vérification est redondante : `normalize()` garantit déjà que le chemin ne peut pas sortir de la racine. Cependant, la double protection ne pose pas de problème de sécurité. On pourrait simplifier en supprimant cette condition si on est certain que `normalize()` ne retourne qu’un chemin bien formé.
PR « 1.2: Sandboxing » de bonne qualité globale : découpage en crates (devcontainer-rs / herald-server), client OpenRouter maison bien documenté, sandbox structuré (agent, tools) avec des outils read-only en argv (pas de shell, donc pas d'injection), confinement lexical des chemins, jeton git passé via http.extraHeader en variable d'environnement (pas dans la ligne de commande), outils sélectionnés par type de webhook de manière exhaustive, et une couverture de tests remarquable (parsing de diff, résolution des côtés, hooks, timeouts, normalisation des chemins).
Points bloquants à traiter avant merge : (1) les runArgs du devcontainer.json du dépôt analysé sont injectés tels quels dans docker run, ce qui permet de monter l'hôte (-v /:/host, --privileged, --pid=host) et d'annuler l'isolation annoncée dans le README — critique pour une PR dont l'objet est justement le sandboxing ; (2) l'image finale du Containerfile ne contient ni git ni runtime conteneur, ce qui rend la sandbox inutilisable en production ; (3) le join du chemin de Dockerfile peut sortir du dossier du devcontainer (path traversal).
Points importants mais non bloquants : absence de plafond sur les sorties d'outils (cat, grep, find) et clones répétés de la conversation dans l'agent (contexte/coût), validation trop laxiste de SANDBOX_MAX_ITERATIONS, suppression du .dockerignore, cohérence des versions Rust (1.97 vs 1.98) et des dépendances de workspace (tempfile), pas de smoke test de l'image en CI, et affirmation d'isolation à nuancer. À noter aussi la réponse du bot (« @Herald ») : le préfixe attendu est @{bot_name}, donc bien vérifier la casse du nom de bot.
Cost: $0.2029030056
## Review Feedback
### 14 issues found.
---
### Summary
PR « 1.2: Sandboxing » de bonne qualité globale : découpage en crates (`devcontainer-rs` / `herald-server`), client OpenRouter maison bien documenté, `sandbox` structuré (agent, tools) avec des outils read-only en argv (pas de shell, donc pas d'injection), confinement lexical des chemins, jeton git passé via `http.extraHeader` en variable d'environnement (pas dans la ligne de commande), outils sélectionnés par type de webhook de manière exhaustive, et une couverture de tests remarquable (parsing de diff, résolution des côtés, hooks, timeouts, normalisation des chemins).
Points bloquants à traiter avant merge : (1) les `runArgs` du `devcontainer.json` du dépôt analysé sont injectés tels quels dans `docker run`, ce qui permet de monter l'hôte (`-v /:/host`, `--privileged`, `--pid=host`) et d'annuler l'isolation annoncée dans le README — critique pour une PR dont l'objet est justement le sandboxing ; (2) l'image finale du `Containerfile` ne contient ni `git` ni runtime conteneur, ce qui rend la sandbox inutilisable en production ; (3) le `join` du chemin de Dockerfile peut sortir du dossier du devcontainer (path traversal).
Points importants mais non bloquants : absence de plafond sur les sorties d'outils (`cat`, `grep`, `find`) et clones répétés de la conversation dans l'agent (contexte/coût), validation trop laxiste de `SANDBOX_MAX_ITERATIONS`, suppression du `.dockerignore`, cohérence des versions Rust (1.97 vs 1.98) et des dépendances de workspace (`tempfile`), pas de smoke test de l'image en CI, et affirmation d'isolation à nuancer. À noter aussi la réponse du bot (« @Herald ») : le préfixe attendu est `@{bot_name}`, donc bien vérifier la casse du nom de bot.
---
### Cost: $0.2029030056
Indentation par tabulation incohérente avec le reste du fichier ; --security-opt label=disable désactive l'étiquetage SELinux et --userns=keep-id est spécifique à Podman. À nettoyer/justifier, d'autant que ce devcontainer sert aussi de contexte d'exécution à la sandbox.
Indentation par tabulation incohérente avec le reste du fichier ; `--security-opt label=disable` désactive l'étiquetage SELinux et `--userns=keep-id` est spécifique à Podman. À nettoyer/justifier, d'autant que ce devcontainer sert aussi de contexte d'exécution à la sandbox.
La suppression du .dockerignore réintègre target/ et .env dans le contexte envoyé au builder (buildah bud ... .), ce qui alourdit fortement le build et expose potentiellement des secrets. Conserver un .dockerignore minimal (target, .env, .git).
La suppression du `.dockerignore` réintègre `target/` et `.env` dans le contexte envoyé au builder (`buildah bud ... .`), ce qui alourdit fortement le build et expose potentiellement des secrets. Conserver un `.dockerignore` minimal (`target`, `.env`, `.git`).
Le job container-build compile l'image mais ne l'exécute jamais : il ne détectera donc pas l'absence de git/runtime conteneur dans l'image finale. Ajouter un smoke test (lancer l'image et vérifier la présence de git et docker/podman), et noter que le pipeline ne se déclenche que sur push, pas sur les pull requests.
Le job `container-build` compile l'image mais ne l'exécute jamais : il ne détectera donc pas l'absence de `git`/runtime conteneur dans l'image finale. Ajouter un smoke test (lancer l'image et vérifier la présence de `git` et `docker`/`podman`), et noter que le pipeline ne se déclenche que sur `push`, pas sur les pull requests.
Incohérence de toolchain : rust:1.97-trixie ici, alors que .devcontainer/Dockerfile et .woodpecker/tests.yml utilisent rust:1.98. Aligner les versions et écrire AS en majuscules (convention Dockerfile).
Incohérence de toolchain : `rust:1.97-trixie` ici, alors que `.devcontainer/Dockerfile` et `.woodpecker/tests.yml` utilisent `rust:1.98`. Aligner les versions et écrire `AS` en majuscules (convention Dockerfile).
L'étage final debian:trixie-slim ne contient que le binaire, or herald-server a besoin de git (clone de la PR) et d'un binaire de runtime conteneur (docker/podman) pour exécuter la sandbox. Avec cette image, chaque revue échouera à l'exécution. Ajouter ces paquets (plus ca-certificates) ou documenter explicitement le montage du socket/runtime requis.
L'étage final `debian:trixie-slim` ne contient que le binaire, or herald-server a besoin de `git` (clone de la PR) et d'un binaire de runtime conteneur (`docker`/`podman`) pour exécuter la sandbox. Avec cette image, chaque revue échouera à l'exécution. Ajouter ces paquets (plus `ca-certificates`) ou documenter explicitement le montage du socket/runtime requis.
L'affirmation « Each sandbox is isolated » est plus forte que la réalité tant que les runArgs/containerEnv du dépôt analysé sont transmis tels quels à docker run (cf. container.rs). Soit durcir le code, soit nuancer la section en listant les limites de l'isolation.
L'affirmation « Each sandbox is isolated » est plus forte que la réalité tant que les `runArgs`/`containerEnv` du dépôt analysé sont transmis tels quels à `docker run` (cf. `container.rs`). Soit durcir le code, soit nuancer la section en listant les limites de l'isolation.
L'option --network est insérée avant self.run_args, qui sont concaténés ensuite (ligne 340) : dans docker run, la dernière option gagne, donc un runArgs du dépôt peut remplacer le réseau dédié et contourner la coupure réseau effectuée en fin de up(). Documenter/verrouiller cet ordre si l'isolation réseau doit être garantie.
L'option `--network` est insérée avant `self.run_args`, qui sont concaténés ensuite (ligne 340) : dans `docker run`, la dernière option gagne, donc un `runArgs` du dépôt peut remplacer le réseau dédié et contourner la coupure réseau effectuée en fin de `up()`. Documenter/verrouiller cet ordre si l'isolation réseau doit être garantie.
Les runArgs proviennent du devcontainer.json du dépôt analysé, donc d'une PR potentiellement issue d'un fork : ils sont concaténés tels quels aux arguments de docker run. Un runArgs du type ["--privileged", "-v", "/:/host"] (ou --pid=host, --cap-add=SYS_ADMIN) monte l'hôte dans le conteneur et annule totalement l'isolation annoncée. Comme ils sont ajoutés après le --network posé ligne 326, ils peuvent aussi le surcharger (--network host). Prévoir une liste blanche d'options (ou ignorer runArgs/containerEnv pour les dépôts non fiables) avant de démarrer le conteneur.
Les `runArgs` proviennent du `devcontainer.json` du dépôt analysé, donc d'une PR potentiellement issue d'un fork : ils sont concaténés tels quels aux arguments de `docker run`. Un `runArgs` du type `["--privileged", "-v", "/:/host"]` (ou `--pid=host`, `--cap-add=SYS_ADMIN`) monte l'hôte dans le conteneur et annule totalement l'isolation annoncée. Comme ils sont ajoutés après le `--network` posé ligne 326, ils peuvent aussi le surcharger (`--network host`). Prévoir une liste blanche d'options (ou ignorer `runArgs`/`containerEnv` pour les dépôts non fiables) avant de démarrer le conteneur.
base_dir.join(schema.build.dockerfile) : un dockerfile absolu (/etc/passwd) ou contenant .. sort du dossier du devcontainer et devient le -f de docker build (lecture de fichier arbitraire côté hôte). Valider que le chemin reste bien sous base_dir (via normalize par exemple) avant de l'accepter.
`base_dir.join(schema.build.dockerfile)` : un `dockerfile` absolu (`/etc/passwd`) ou contenant `..` sort du dossier du devcontainer et devient le `-f` de `docker build` (lecture de fichier arbitraire côté hôte). Valider que le chemin reste bien sous `base_dir` (via `normalize` par exemple) avant de l'accepter.
tempfile = "3" est déclaré en dur alors que toutes les autres dépendances passent par [workspace.dependencies] : le placer dans le workspace pour garder une version unique et cohérente (idem pour le dev-dependency de devcontainer-rs).
`tempfile = "3"` est déclaré en dur alors que toutes les autres dépendances passent par `[workspace.dependencies]` : le placer dans le workspace pour garder une version unique et cohérente (idem pour le dev-dependency de `devcontainer-rs`).
SANDBOX_MAX_ITERATIONS retombe silencieusement sur 8 si la valeur est invalide et accepte 0 : avec 0, la boucle de l'agent (1..=max_iterations) ne s'exécute pas et la revue échoue immédiatement. Valider la valeur (>= 1) et journaliser en cas de repli sur la valeur par défaut.
`SANDBOX_MAX_ITERATIONS` retombe silencieusement sur 8 si la valeur est invalide et accepte `0` : avec 0, la boucle de l'agent (`1..=max_iterations`) ne s'exécute pas et la revue échoue immédiatement. Valider la valeur (>= 1) et journaliser en cas de repli sur la valeur par défaut.
L'absence de runtime conteneur ne produit qu'un warn : le service démarre normalement alors que toutes les revues échoueront ensuite. Le contrôle étant déjà fait au démarrage, un échec rapide (fail-fast) ou un mode dégradé explicite serait plus sûr et plus lisible.
L'absence de runtime conteneur ne produit qu'un `warn` : le service démarre normalement alors que toutes les revues échoueront ensuite. Le contrôle étant déjà fait au démarrage, un échec rapide (fail-fast) ou un mode dégradé explicite serait plus sûr et plus lisible.
messages.clone() et tool_definitions.clone() à chaque itération recopient toute la conversation à chaque tour (coût O(n²)) alors qu'un passage par référence (&[Message]) suffirait. Par ailleurs rien ne borne la taille du contenu renvoyé par les outils : ajouter un plafond par appel pour maîtriser le contexte et le coût.
`messages.clone()` et `tool_definitions.clone()` à chaque itération recopient toute la conversation à chaque tour (coût O(n²)) alors qu'un passage par référence (`&[Message]`) suffirait. Par ailleurs rien ne borne la taille du contenu renvoyé par les outils : ajouter un plafond par appel pour maîtriser le contexte et le coût.
read_file exécute cat sans aucune borne : un gros fichier (ou binaire) injecte tout son contenu dans la conversation et peut saturer le contexte et faire exploser le coût. Plafonner le nombre de lignes/octets renvoyés avec une note de troncature (idem pour grep et find, potentiellement très verbeux).
`read_file` exécute `cat` sans aucune borne : un gros fichier (ou binaire) injecte tout son contenu dans la conversation et peut saturer le contexte et faire exploser le coût. Plafonner le nombre de lignes/octets renvoyés avec une note de troncature (idem pour `grep` et `find`, potentiellement très verbeux).
Synthèse de la revue (PR « 1.2: Sandboxing ») : la réorganisation en workspace multi-crates (crates/herald-server, crates/devcontainer-rs) et la séparation des actions (bot_actions/review.rs), du sandbox (sandbox/{mod,agent,tools}.rs), du client LLM (open_router.rs) et des constantes est une bonne amélioration structurelle, cohérente avec l'objectif de sandboxing. Points bloquants / à corriger en priorité : (1) la suppression complète de .dockerignore doit être rétablie ou remplacée, sous peine d'inclure target/, .git/ et d'éventuels fichiers .env dans le contexte de build ; (2) le durcissement du sandbox doit être explicite et vérifiable (allowlist de commandes sans shell, confinement des chemins, timeouts, limites mémoire/CPU, utilisateur non-root, absence de réseau ou réseau filtré) et échouer de manière fermée ; (3) la gestion des secrets (clé OpenRouter, tokens Gitea) doit être revue pour garantir l'absence de fuite dans les logs, les prompts ou les commentaires publiés. Recommandations secondaires : épingler les images (Dockerfile/Containerfile) et passer à un build multi-étapes non-root, ajouter des tests unitaires sur les nouvelles actions et le sandbox, harmoniser la configuration éditeur (Zed vs VSCode supprimé), compléter .env.example et le README pour documenter le modèle de menace et les variables d'environnement, et vérifier le lockfile ainsi que la couverture CI (--workspace, fmt, clippy). Remarque : je n'ai pas pu lire le contenu des fichiers du dépôt (accès refusé dans l'environnement d'exécution) ; ces retours sont donc formulés comme des points de vigilance à confirmer sur le code réel.
Cost: $0.042505191
## Review Feedback
### 18 issues found.
---
### Summary
Synthèse de la revue (PR « 1.2: Sandboxing ») : la réorganisation en workspace multi-crates (`crates/herald-server`, `crates/devcontainer-rs`) et la séparation des actions (`bot_actions/review.rs`), du sandbox (`sandbox/{mod,agent,tools}.rs`), du client LLM (`open_router.rs`) et des constantes est une bonne amélioration structurelle, cohérente avec l'objectif de sandboxing. Points bloquants / à corriger en priorité : (1) la suppression complète de `.dockerignore` doit être rétablie ou remplacée, sous peine d'inclure `target/`, `.git/` et d'éventuels fichiers `.env` dans le contexte de build ; (2) le durcissement du sandbox doit être explicite et vérifiable (allowlist de commandes sans shell, confinement des chemins, timeouts, limites mémoire/CPU, utilisateur non-root, absence de réseau ou réseau filtré) et échouer de manière fermée ; (3) la gestion des secrets (clé OpenRouter, tokens Gitea) doit être revue pour garantir l'absence de fuite dans les logs, les prompts ou les commentaires publiés. Recommandations secondaires : épingler les images (Dockerfile/Containerfile) et passer à un build multi-étapes non-root, ajouter des tests unitaires sur les nouvelles actions et le sandbox, harmoniser la configuration éditeur (Zed vs VSCode supprimé), compléter `.env.example` et le README pour documenter le modèle de menace et les variables d'environnement, et vérifier le lockfile ainsi que la couverture CI (`--workspace`, fmt, clippy). Remarque : je n'ai pas pu lire le contenu des fichiers du dépôt (accès refusé dans l'environnement d'exécution) ; ces retours sont donc formulés comme des points de vigilance à confirmer sur le code réel.
---
### Cost: $0.042505191
Épingler l'image de base par digest (ou au moins une version majeure mineure) pour la reproductibilité, et éviter d'exécuter le conteneur de développement en root si possible. Vérifier que les outils ajoutés dans cette image ne sont pas supposés présents en production (le devcontainer ne doit pas être une dépendance implicite du runtime).
Épingler l'image de base par digest (ou au moins une version majeure mineure) pour la reproductibilité, et éviter d'exécuter le conteneur de développement en root si possible. Vérifier que les outils ajoutés dans cette image ne sont pas supposés présents en production (le devcontainer ne doit pas être une dépendance implicite du runtime).
La suppression complète de .dockerignore est problématique : le contexte de build inclura désormais target/, .git/, .env et autres fichiers locaux, ce qui alourdit et surtout peut faire fuiter des secrets dans l'image via une copie du contexte. Si le fichier a été remplacé ailleurs, le référencer explicitement ; sinon le restaurer.
La suppression complète de `.dockerignore` est problématique : le contexte de build inclura désormais `target/`, `.git/`, `.env` et autres fichiers locaux, ce qui alourdit et surtout peut faire fuiter des secrets dans l'image via une copie du contexte. Si le fichier a été remplacé ailleurs, le référencer explicitement ; sinon le restaurer.
Les nouvelles variables doivent être documentées ici avec un commentaire indiquant leur rôle et une valeur d'exemple non sensible. Vérifier qu'aucune vraie clé/token n'a été committée et que le fichier reste aligné avec les variables réellement lues dans env.rs.
Les nouvelles variables doivent être documentées ici avec un commentaire indiquant leur rôle et une valeur d'exemple non sensible. Vérifier qu'aucune vraie clé/token n'a été committée et que le fichier reste aligné avec les variables réellement lues dans `env.rs`.
Pipeline CI : vérifier que cargo fmt --check, cargo clippy -- -D warnings et cargo test --all-features couvrent bien le nouveau workspace multi-crates (utiliser --workspace), et que les tests du sandbox qui nécessitent Docker/root sont soit exécutés, soit explicitement ignorés avec justification.
Pipeline CI : vérifier que `cargo fmt --check`, `cargo clippy -- -D warnings` et `cargo test --all-features` couvrent bien le nouveau workspace multi-crates (utiliser `--workspace`), et que les tests du sandbox qui nécessitent Docker/root sont soit exécutés, soit explicitement ignorés avec justification.
Ajouter la configuration Zed tout en supprimant .vscode/settings.json crée une incohérence pour les contributeurs : il vaut mieux conserver les deux (ou documenter l'éditeur recommandé) et s'assurer qu'aucun chemin absolu local ni préférence personnelle n'est committé ici.
Ajouter la configuration Zed tout en supprimant `.vscode/settings.json` crée une incohérence pour les contributeurs : il vaut mieux conserver les deux (ou documenter l'éditeur recommandé) et s'assurer qu'aucun chemin absolu local ni préférence personnelle n'est committé ici.
Le diff du lockfile est volumineux : vérifier qu'il ne contient que des changements attendus (nouvelles dépendances réellement utilisées) et pas d'ajout transitif superflu, puis confirmer que le build est reproductible en CI avec --locked.
Le diff du lockfile est volumineux : vérifier qu'il ne contient que des changements attendus (nouvelles dépendances réellement utilisées) et pas d'ajout transitif superflu, puis confirmer que le build est reproductible en CI avec `--locked`.
Passage en workspace multi-crates : vérifier que les dépendances communes sont déclarées dans [workspace.dependencies] et réutilisées avec workspace = true dans les crates, pour éviter les divergences de versions. Ajouter aussi les métadonnées de package communes (edition, license, version) via [workspace.package].
Passage en workspace multi-crates : vérifier que les dépendances communes sont déclarées dans `[workspace.dependencies]` et réutilisées avec `workspace = true` dans les crates, pour éviter les divergences de versions. Ajouter aussi les métadonnées de package communes (edition, license, version) via `[workspace.package]`.
Build de production : privilégier un build multi-étapes pour ne pas embarquer toolchain et sources dans l'image finale, exécuter le binaire avec un utilisateur non privilégié, et épingler les dépendances de l'étape de build. Ajouter un ENTRYPOINT/CMD explicite et un label de version.
Build de production : privilégier un build multi-étapes pour ne pas embarquer toolchain et sources dans l'image finale, exécuter le binaire avec un utilisateur non privilégié, et épingler les dépendances de l'étape de build. Ajouter un `ENTRYPOINT`/`CMD` explicite et un label de version.
@@ -38,6 +37,34 @@ Herald reads its configuration from environment variables (a `.env` file is supp
| `METRICS_BIND_ADDR` | *(optional)* Bind address for the Prometheus metrics endpoint (e.g. `0.0.0.0:9100`). If unset, the metrics exporter is disabled. |
| `SENTRY_DSN` | *(optional)* Sentry DSN for error tracking |
| `RUST_LOG` | *(optional)* Log level, defaults to `info` |
| `CONTAINER_RUNTIME` | *(optional)* Container runtime binary used for the sandbox (`docker` or `podman`). Defaults to `docker` |
La documentation du sandbox doit préciser ce qui est isolé et ce qui ne l'est pas (réseau, filesystem, credentials), comment le configurer via l'environnement, et rappeler que l'exécution de code issu d'une PR est une opération à risque. Ajouter si possible un exemple de variable d'environnement sécurisé.
La documentation du sandbox doit préciser ce qui est isolé et ce qui ne l'est pas (réseau, filesystem, credentials), comment le configurer via l'environnement, et rappeler que l'exécution de code issu d'une PR est une opération à risque. Ajouter si possible un exemple de variable d'environnement sécurisé.
Adaptation aux nouveaux chemins de crates : profiter du déplacement pour vérifier que les imports internes utilisent bien crate:: (et non des chemins relatifs fragiles) et qu'aucun use mort ne subsiste après la réorganisation.
Adaptation aux nouveaux chemins de crates : profiter du déplacement pour vérifier que les imports internes utilisent bien `crate::` (et non des chemins relatifs fragiles) et qu'aucun `use` mort ne subsiste après la réorganisation.
Action de review : ajouter des tests unitaires sur la construction du prompt, la gestion des erreurs et les cas limites (contenu vide, réponse LLM tronquée, échec du sandbox). Prévoir aussi une limite de taille de la réponse publiée dans Gitea pour éviter de dépasser les quotas de l'API.
Action de review : ajouter des tests unitaires sur la construction du prompt, la gestion des erreurs et les cas limites (contenu vide, réponse LLM tronquée, échec du sandbox). Prévoir aussi une limite de taille de la réponse publiée dans Gitea pour éviter de dépasser les quotas de l'API.
Centraliser les valeurs magiques dans un module de constantes est une bonne pratique : s'assurer que les valeurs liées au sandbox (timeouts, limites mémoire, chemins) sont bien documentées par un commentaire indiquant l'unité (secondes, octets) et la raison du choix.
Centraliser les valeurs magiques dans un module de constantes est une bonne pratique : s'assurer que les valeurs liées au sandbox (timeouts, limites mémoire, chemins) sont bien documentées par un commentaire indiquant l'unité (secondes, octets) et la raison du choix.
Nouvelles variables d'environnement : valider leur présence et leur format au démarrage (échouer tôt avec un message explicite) plutôt que de laisser une valeur par défaut silencieuse. Prévoir aussi une valeur par défaut sûre (fail-closed) pour tout réglage désactivant l'isolation.
Nouvelles variables d'environnement : valider leur présence et leur format au démarrage (échouer tôt avec un message explicite) plutôt que de laisser une valeur par défaut silencieuse. Prévoir aussi une valeur par défaut sûre (fail-closed) pour tout réglage désactivant l'isolation.
Module OpenRouter : la clé d'API doit être lue depuis l'environnement/state (jamais codée en dur, jamais loggée). Vérifier que les erreurs renvoyées par l'API HTTP ne propagent pas la clé ni le contenu complet des prompts dans les logs ou dans le commentaire publié. Ajouter un timeout et une gestion des codes 429/5xx avec retry borné.
Module OpenRouter : la clé d'API doit être lue depuis l'environnement/state (jamais codée en dur, jamais loggée). Vérifier que les erreurs renvoyées par l'API HTTP ne propagent pas la clé ni le contenu complet des prompts dans les logs ou dans le commentaire publié. Ajouter un timeout et une gestion des codes 429/5xx avec retry borné.
Processus lancés dans le sandbox : vérifier qu'un timeout est appliqué, que le processus (et son groupe) est tué en cas de dépassement ou d'annulation, et que les pipes stdout/stderr sont consommés pour éviter un blocage du processus si le buffer se remplit. Prévoir aussi une limite de taille de sortie pour ne pas saturer la mémoire avec un output verbeux ou malveillant.
Processus lancés dans le sandbox : vérifier qu'un timeout est appliqué, que le processus (et son groupe) est tué en cas de dépassement ou d'annulation, et que les pipes stdout/stderr sont consommés pour éviter un blocage du processus si le buffer se remplit. Prévoir aussi une limite de taille de sortie pour ne pas saturer la mémoire avec un output verbeux ou malveillant.
Nouveau module sandbox : ajouter en tête de fichier une documentation (doc-comment) décrivant le modèle de menace et le niveau d'isolation réellement appliqué (namespaces, cgroups, utilisateur non-root, montages read-only, limites CPU/RAM/pid). Sans cela, il est impossible de valider que le nom « sandbox » correspond à une isolation effective et non à un simple répertoire de travail. Préciser aussi le comportement en cas d'échec d'initialisation de l'isolation (échec fermé plutôt qu'ouvert).
Nouveau module `sandbox` : ajouter en tête de fichier une documentation (doc-comment) décrivant le modèle de menace et le niveau d'isolation réellement appliqué (namespaces, cgroups, utilisateur non-root, montages read-only, limites CPU/RAM/pid). Sans cela, il est impossible de valider que le nom « sandbox » correspond à une isolation effective et non à un simple répertoire de travail. Préciser aussi le comportement en cas d'échec d'initialisation de l'isolation (échec fermé plutôt qu'ouvert).
Attention aux chemins : tous les fichiers créés ou montés dans le sandbox doivent être confinés sous une racine contrôlée (canonicalisation + vérification de préfixe) pour empêcher toute traversée de répertoire (.., liens symboliques). Un chemin dérivé d'un nom de fichier d'une PR est une entrée non fiable.
Attention aux chemins : tous les fichiers créés ou montés dans le sandbox doivent être confinés sous une racine contrôlée (canonicalisation + vérification de préfixe) pour empêcher toute traversée de répertoire (`..`, liens symboliques). Un chemin dérivé d'un nom de fichier d'une PR est une entrée non fiable.
Exécution d'outils dans le sandbox : s'assurer que les commandes passent par une liste blanche explicite (allowlist de binaires + arguments validés) et jamais par un shell (sh -c) avec interpolation de chaînes issues du contenu de la PR. Les entrées utilisateur (diff, messages, noms de fichiers) doivent être passées comme arguments séparés, avec validation/échappement. Vérifier également l'absence de Command::new construit à partir d'une variable non validée.
Exécution d'outils dans le sandbox : s'assurer que les commandes passent par une liste blanche explicite (allowlist de binaires + arguments validés) et jamais par un shell (`sh -c`) avec interpolation de chaînes issues du contenu de la PR. Les entrées utilisateur (diff, messages, noms de fichiers) doivent être passées comme arguments séparés, avec validation/échappement. Vérifier également l'absence de `Command::new` construit à partir d'une variable non validée.
Points positifs : la réorganisation en workspace Cargo (crates/herald-server, crates/devcontainer-rs) clarifie la séparation entre le serveur et l'exécution en conteneur, et l'introduction d'un module sandbox (agent + tools) va dans le bon sens. Le fait de regrouper Dockerfile/Containerfile/CI/éditeurs montre une volonté d'unifier l'environnement de développement et l'intégration continue.
Points d'attention :
Gouvernance du workspace : utiliser [workspace.dependencies] + workspace = true, définir resolver, et s'assurer que les crates internes sont publish = false.
Sandboxing : les points critiques sont les limites de ressources (timeout, mémoire, CPU, réseau), le nettoyage des conteneurs, la validation des entrées des outils exposés à l'agent et le nombre maximal d'itérations de l'agent. Ce sont les endroits où une revue ligne à ligne plus approfondie est nécessaire.
Configuration : nouvelles variables d'environnement à documenter dans .env.example avec des valeurs par défaut sûres (le sandboxing ne doit pas pouvoir être désactivé silencieusement par une variable manquante).
Images : épingler les images de base par digest et exécuter en utilisateur non-root.
Migration : vérifier qu'aucun code n'a été dupliqué ou perdu lors des déplacements (src/* -> crates/herald-server/src/*) et que la CI couvre bien l'ensemble du workspace (tests, fmt, clippy).
Remarque : n'ayant pas pu accéder au contenu des fichiers pendant cette revue, les commentaires ci-dessus sont des points de vigilance généraux rattachés aux lignes modifiées ; ils devront être confirmés/ajustés en lisant le diff complet.
Cost: $0.021022326000000004
## Review Feedback
### 19 issues found.
---
### Summary
Synthèse de la revue (PR « 1.2: Sandboxing ») :
Points positifs : la réorganisation en workspace Cargo (`crates/herald-server`, `crates/devcontainer-rs`) clarifie la séparation entre le serveur et l'exécution en conteneur, et l'introduction d'un module `sandbox` (agent + tools) va dans le bon sens. Le fait de regrouper `Dockerfile`/`Containerfile`/CI/éditeurs montre une volonté d'unifier l'environnement de développement et l'intégration continue.
Points d'attention :
- Gouvernance du workspace : utiliser `[workspace.dependencies]` + `workspace = true`, définir `resolver`, et s'assurer que les crates internes sont `publish = false`.
- Sandboxing : les points critiques sont les limites de ressources (timeout, mémoire, CPU, réseau), le nettoyage des conteneurs, la validation des entrées des outils exposés à l'agent et le nombre maximal d'itérations de l'agent. Ce sont les endroits où une revue ligne à ligne plus approfondie est nécessaire.
- Configuration : nouvelles variables d'environnement à documenter dans `.env.example` avec des valeurs par défaut sûres (le sandboxing ne doit pas pouvoir être désactivé silencieusement par une variable manquante).
- Images : épingler les images de base par digest et exécuter en utilisateur non-root.
- Migration : vérifier qu'aucun code n'a été dupliqué ou perdu lors des déplacements (`src/*` -> `crates/herald-server/src/*`) et que la CI couvre bien l'ensemble du workspace (tests, fmt, clippy).
Remarque : n'ayant pas pu accéder au contenu des fichiers pendant cette revue, les commentaires ci-dessus sont des points de vigilance généraux rattachés aux lignes modifiées ; ils devront être confirmés/ajustés en lisant le diff complet.
---
### Cost: $0.021022326000000004
Épingler l'image de base par digest plutôt que par tag flottant, et vérifier que l'image de sandbox ne contient pas de secrets ni d'outils inutiles (surface d'attaque réduite, utilisateur non-root).
Épingler l'image de base par digest plutôt que par tag flottant, et vérifier que l'image de sandbox ne contient pas de secrets ni d'outils inutiles (surface d'attaque réduite, utilisateur non-root).
@@ -38,6 +37,34 @@ Herald reads its configuration from environment variables (a `.env` file is supp
| `METRICS_BIND_ADDR` | *(optional)* Bind address for the Prometheus metrics endpoint (e.g. `0.0.0.0:9100`). If unset, the metrics exporter is disabled. |
| `SENTRY_DSN` | *(optional)* Sentry DSN for error tracking |
| `RUST_LOG` | *(optional)* Log level, defaults to `info` |
| `CONTAINER_RUNTIME` | *(optional)* Container runtime binary used for the sandbox (`docker` or `podman`). Defaults to `docker` |
Documenter le nouveau fonctionnement (workspace multi-crates + sandboxing) : prérequis (Podman/Docker), variables d'environnement nécessaires et limites connues de l'isolation.
Documenter le nouveau fonctionnement (workspace multi-crates + sandboxing) : prérequis (Podman/Docker), variables d'environnement nécessaires et limites connues de l'isolation.
Nouvelles variables d'environnement : documenter chacune dans .env.example et fournir une valeur par défaut sûre ; une variable manquante ne devrait pas désactiver silencieusement le sandboxing.
Nouvelles variables d'environnement : documenter chacune dans `.env.example` et fournir une valeur par défaut sûre ; une variable manquante ne devrait pas désactiver silencieusement le sandboxing.
Nouveau point d'entrée du crate herald-server : vérifier que les modules déplacés (sandbox, open_router, consts, bot_actions) sont bien déclarés et que plus aucune référence au chemin racine src/... ne subsiste.
Nouveau point d'entrée du crate `herald-server` : vérifier que les modules déplacés (`sandbox`, `open_router`, `consts`, `bot_actions`) sont bien déclarés et que plus aucune référence au chemin racine `src/...` ne subsiste.
Fichier déplacé depuis src/open_router.rs : s'assurer qu'il n'y a pas de duplication avec l'ancien fichier supprimé et que les nouveaux appels (utilisés par l'agent de sandbox) gèrent les erreurs HTTP et les timeouts.
Fichier déplacé depuis `src/open_router.rs` : s'assurer qu'il n'y a pas de duplication avec l'ancien fichier supprimé et que les nouveaux appels (utilisés par l'agent de sandbox) gèrent les erreurs HTTP et les timeouts.
Boucle d'agent exécutant des outils : prévoir un nombre maximal d'itérations et une limite de temps globale, sinon une boucle d'appels d'outils peut consommer indéfiniment des tokens/CPU.
Boucle d'agent exécutant des outils : prévoir un nombre maximal d'itérations et une limite de temps globale, sinon une boucle d'appels d'outils peut consommer indéfiniment des tokens/CPU.
Le module de sandboxing devrait exposer des limites explicites (mémoire, CPU, timeout, réseau désactivé par défaut) et retourner des erreurs typées plutôt que des String, afin que l'appelant puisse décider de la politique de repli.
Le module de sandboxing devrait exposer des limites explicites (mémoire, CPU, timeout, réseau désactivé par défaut) et retourner des erreurs typées plutôt que des `String`, afin que l'appelant puisse décider de la politique de repli.
Les outils exposés à l'agent doivent valider leurs entrées (chemins relatifs, pas de .., pas de shell arbitraire) et documenter clairement le contrat JSON de chaque outil ; c'est la principale surface d'attaque du sandboxing.
Les outils exposés à l'agent doivent valider leurs entrées (chemins relatifs, pas de `..`, pas de shell arbitraire) et documenter clairement le contrat JSON de chaque outil ; c'est la principale surface d'attaque du sandboxing.
Ancien fichier supprimé : vérifier que la logique a bien été entièrement migrée vers crates/herald-server/src/bot_actions/review.rs et qu'aucun test ne référence encore l'ancien chemin.
Ancien fichier supprimé : vérifier que la logique a bien été entièrement migrée vers `crates/herald-server/src/bot_actions/review.rs` et qu'aucun test ne référence encore l'ancien chemin.
Fichier supprimé : s'assurer que toutes les constantes sont reprises dans crates/herald-server/src/consts.rs et qu'aucune constante n'a été perdue lors du déplacement.
Fichier supprimé : s'assurer que toutes les constantes sont reprises dans `crates/herald-server/src/consts.rs` et qu'aucune constante n'a été perdue lors du déplacement.
La fonctionnalité de sandbox est structurée proprement et évite l'injection shell pour les outils, mais l'isolation n'est pas sûre tant que les chemins de build et les options runArgs contrôlés par le dépôt ne sont pas bornés. Il faut également limiter les sorties des outils afin d'éviter les dénis de service.
Cost: $0.01940168
## Review Feedback
### 3 issues found.
---
### Summary
La fonctionnalité de sandbox est structurée proprement et évite l'injection shell pour les outils, mais l'isolation n'est pas sûre tant que les chemins de build et les options `runArgs` contrôlés par le dépôt ne sont pas bornés. Il faut également limiter les sorties des outils afin d'éviter les dénis de service.
---
### Cost: $0.01940168
La sandbox introduit une bonne séparation fonctionnelle et limite les outils exposés, mais elle laisse deux failles importantes dans la frontière de confiance: une pull request peut lire des variables d'environnement secrètes via localEnv, et une commande dépassant son timeout n'est pas réellement interrompue. Le traitement des renommages peut également produire des commentaires sur le mauvais chemin; ces points doivent être corrigés avant validation.
Cost: $0.06652119
## Review Feedback
### 3 issues found.
---
### Summary
La sandbox introduit une bonne séparation fonctionnelle et limite les outils exposés, mais elle laisse deux failles importantes dans la frontière de confiance: une pull request peut lire des variables d'environnement secrètes via `localEnv`, et une commande dépassant son timeout n'est pas réellement interrompue. Le traitement des renommages peut également produire des commentaires sur le mauvais chemin; ces points doivent être corrigés avant validation.
---
### Cost: $0.06652119
La fonctionnalité apporte une séparation utile en crate et borne correctement les appels d'outils, mais plusieurs chemins contrôlés par la pull request ne sont pas suffisamment confinés. En particulier, le chemin du Dockerfile peut exfiltrer un contexte hors du dépôt, le confinement SELinux est désactivé et aucune limite de ressources n'empêche un déni de service ; le chemin d'échec de création laisse en outre des ressources du daemon. Ces points doivent être corrigés avant de considérer le sandboxing comme sûr.
Cost: $0.046895
## Review Feedback
### 4 issues found.
---
### Summary
La fonctionnalité apporte une séparation utile en crate et borne correctement les appels d'outils, mais plusieurs chemins contrôlés par la pull request ne sont pas suffisamment confinés. En particulier, le chemin du Dockerfile peut exfiltrer un contexte hors du dépôt, le confinement SELinux est désactivé et aucune limite de ressources n'empêche un déni de service ; le chemin d'échec de création laisse en outre des ressources du daemon. Ces points doivent être corrigés avant de considérer le sandboxing comme sûr.
---
### Cost: $0.046895
[Élevé] Le conteneur sandbox n'a aucune limite de ressources (mémoire, CPU, processus ou taille disque). Le Dockerfile, les hooks et les commandes demandées par le modèle sont contrôlés indirectement par une PR non fiable et peuvent provoquer une fork bomb, remplir le disque ou monopoliser l'hôte. Ajouter des limites explicites dans HostConfig et prévoir aussi un nettoyage fiable en cas de dépassement.
[Élevé] Le conteneur sandbox n'a aucune limite de ressources (mémoire, CPU, processus ou taille disque). Le Dockerfile, les hooks et les commandes demandées par le modèle sont contrôlés indirectement par une PR non fiable et peuvent provoquer une fork bomb, remplir le disque ou monopoliser l'hôte. Ajouter des limites explicites dans `HostConfig` et prévoir aussi un nettoyage fiable en cas de dépassement.
[Élevé] label=disable désactive explicitement le confinement SELinux pour un conteneur exécutant du code contrôlé par une pull request. Cela élargit fortement l'impact d'une compromission du conteneur et contredit l'objectif de sandboxing. Préférer un montage correctement relabellisé (:Z/équivalent selon le runtime) ou rendre ce comportement optionnel et documenter clairement la perte d'isolation.
[Élevé] `label=disable` désactive explicitement le confinement SELinux pour un conteneur exécutant du code contrôlé par une pull request. Cela élargit fortement l'impact d'une compromission du conteneur et contredit l'objectif de sandboxing. Préférer un montage correctement relabellisé (`:Z`/équivalent selon le runtime) ou rendre ce comportement optionnel et documenter clairement la perte d'isolation.
[Bloquant] Le chemin du Dockerfile est construit directement à partir d'une valeur contrôlée par la pull request. Un chemin absolu ou contenant suffisamment de .. peut faire sortir le contexte de build du clone ; build() empaquettera alors le répertoire parent arbitraire et l'enverra au daemon, ce qui peut exposer des fichiers de l'hôte au build non fiable. Il faut rejeter les chemins absolus et vérifier lexicalement que le Dockerfile et son contexte restent sous la racine du dépôt cloné.
[Bloquant] Le chemin du Dockerfile est construit directement à partir d'une valeur contrôlée par la pull request. Un chemin absolu ou contenant suffisamment de `..` peut faire sortir le contexte de build du clone ; `build()` empaquettera alors le répertoire parent arbitraire et l'enverra au daemon, ce qui peut exposer des fichiers de l'hôte au build non fiable. Il faut rejeter les chemins absolus et vérifier lexicalement que le Dockerfile et son contexte restent sous la racine du dépôt cloné.
[Élevé] Si check_workspace() échoue, le ? abandonne Sandbox après le démarrage du conteneur, mais Container n'a pas de Drop capable de supprimer les ressources asynchrones. Le conteneur, le réseau et l'image restent donc potentiellement sur le daemon à chaque échec de vérification. Nettoyer explicitement le conteneur avant de retourner l'erreur, avec conservation de l'erreur initiale même si le nettoyage échoue.
[Élevé] Si `check_workspace()` échoue, le `?` abandonne `Sandbox` après le démarrage du conteneur, mais `Container` n'a pas de `Drop` capable de supprimer les ressources asynchrones. Le conteneur, le réseau et l'image restent donc potentiellement sur le daemon à chaque échec de vérification. Nettoyer explicitement le conteneur avant de retourner l'erreur, avec conservation de l'erreur initiale même si le nettoyage échoue.
La fonctionnalité est globalement structurée, mais la vérification lexicale des chemins permet de contourner la confinement via des liens symboliques, et la désactivation de SELinux affaiblit directement la sandbox. La gestion du nettoyage doit également être rendue réellement best-effort afin d'éviter l'accumulation de containers, réseaux et images orphelins.
Cost: $0.03395993
## Review Feedback
### 3 issues found.
---
### Summary
La fonctionnalité est globalement structurée, mais la vérification lexicale des chemins permet de contourner la confinement via des liens symboliques, et la désactivation de SELinux affaiblit directement la sandbox. La gestion du nettoyage doit également être rendue réellement best-effort afin d'éviter l'accumulation de containers, réseaux et images orphelins.
---
### Cost: $0.03395993
La méthode quitte immédiatement si remove_container échoue, alors que sa documentation indique que le réseau et l'image doivent également être supprimés en best-effort. Dans ce cas, l'image et le réseau restent orphelins, et Sandbox::cleanup ne peut pas récupérer ces ressources. Il faut tenter les suppressions même après l'échec du container, puis retourner une erreur agrégée ou la première erreur rencontrée.
La méthode quitte immédiatement si `remove_container` échoue, alors que sa documentation indique que le réseau et l'image doivent également être supprimés en best-effort. Dans ce cas, l'image et le réseau restent orphelins, et `Sandbox::cleanup` ne peut pas récupérer ces ressources. Il faut tenter les suppressions même après l'échec du container, puis retourner une erreur agrégée ou la première erreur rencontrée.
label=disable désactive explicitement le confinement SELinux du container. Pour une sandbox qui exécute du code provenant d'une PR non fiable, cela élargit fortement l'impact d'une évasion ou d'une vulnérabilité du runtime. Il faudrait privilégier un montage correctement relabellisé (:Z/équivalent) ou rendre cette option explicite et désactivée par défaut, avec une justification claire du niveau de risque.
`label=disable` désactive explicitement le confinement SELinux du container. Pour une sandbox qui exécute du code provenant d'une PR non fiable, cela élargit fortement l'impact d'une évasion ou d'une vulnérabilité du runtime. Il faudrait privilégier un montage correctement relabellisé (`:Z`/équivalent) ou rendre cette option explicite et désactivée par défaut, avec une justification claire du niveau de risque.
La normalisation est uniquement lexicale et ne résout pas les liens symboliques. Un dépôt contrôlé par l'auteur de la PR peut donc contenir un lien tel que secret -> /proc/self/environ ou secret -> /etc/...; read_file transmettra ensuite ce chemin à cat et permettra de lire des fichiers hors du workspace. Il faut résoudre et valider la cible réelle dans le container, ou refuser les liens symboliques avant toute lecture.
La normalisation est uniquement lexicale et ne résout pas les liens symboliques. Un dépôt contrôlé par l'auteur de la PR peut donc contenir un lien tel que `secret -> /proc/self/environ` ou `secret -> /etc/...`; `read_file` transmettra ensuite ce chemin à `cat` et permettra de lire des fichiers hors du workspace. Il faut résoudre et valider la cible réelle dans le container, ou refuser les liens symboliques avant toute lecture.
La structure générale de la sandbox et les outils argv sont bien séparés, mais l'isolation annoncée n'est pas suffisamment garantie : la configuration du daemon peut être ignorée, le contexte de build peut sortir du clone et le chemin de workspace peut être manipulé pour lire tout le filesystem du container. Ces points doivent être corrigés avant de considérer le sandboxing comme fiable.
Cost: $0.06976124
## Review Feedback
### 3 issues found.
---
### Summary
La structure générale de la sandbox et les outils argv sont bien séparés, mais l'isolation annoncée n'est pas suffisamment garantie : la configuration du daemon peut être ignorée, le contexte de build peut sortir du clone et le chemin de workspace peut être manipulé pour lire tout le filesystem du container. Ces points doivent être corrigés avant de considérer le sandboxing comme fiable.
---
### Cost: $0.06976124
endpoint lit DOCKER_HOST, mais le client Bollard est créé avec connect_with_defaults() sans utiliser cette valeur. Ainsi, le daemon configuré via DOCKER_HOST (notamment le socket Podman rootless documenté) risque d'être ignoré, tandis que les logs indiquent un endpoint différent de celui réellement utilisé. Il faut construire le client à partir de l'endpoint résolu, ou vérifier que l'API utilisée prend effectivement en charge DOCKER_HOST.
`endpoint` lit `DOCKER_HOST`, mais le client Bollard est créé avec `connect_with_defaults()` sans utiliser cette valeur. Ainsi, le daemon configuré via `DOCKER_HOST` (notamment le socket Podman rootless documenté) risque d'être ignoré, tandis que les logs indiquent un endpoint différent de celui réellement utilisé. Il faut construire le client à partir de l'endpoint résolu, ou vérifier que l'API utilisée prend effectivement en charge `DOCKER_HOST`.
Le chemin build.dockerfile fourni par une pull request est concaténé sans normalisation ni vérification qu'il reste dans le dépôt. Une valeur comme ../../... peut faire choisir un Dockerfile situé hors du clone ; comme son parent devient ensuite le contexte envoyé au daemon, cela peut empaqueter des fichiers d'autres sandboxes ou du système dans le contexte de build. Résolvez le chemin puis imposez qu'il soit situé sous la racine du dépôt avant de l'utiliser.
Le chemin `build.dockerfile` fourni par une pull request est concaténé sans normalisation ni vérification qu'il reste dans le dépôt. Une valeur comme `../../...` peut faire choisir un Dockerfile situé hors du clone ; comme son parent devient ensuite le contexte envoyé au daemon, cela peut empaqueter des fichiers d'autres sandboxes ou du système dans le contexte de build. Résolvez le chemin puis imposez qu'il soit situé sous la racine du dépôt avant de l'utiliser.
La vérification de confinement dépend entièrement de workspace, qui est contrôlé par le devcontainer.json de la pull request. Une PR peut définir workspaceFolder à /, après quoi starts_with(workspace) autorise pratiquement tout le système de fichiers du container et les outils peuvent lire des fichiers hors dépôt. Le workspace doit être validé et forcé sous un répertoire dédié au dépôt, indépendamment de la configuration non fiable.
La vérification de confinement dépend entièrement de `workspace`, qui est contrôlé par le `devcontainer.json` de la pull request. Une PR peut définir `workspaceFolder` à `/`, après quoi `starts_with(workspace)` autorise pratiquement tout le système de fichiers du container et les outils peuvent lire des fichiers hors dépôt. Le workspace doit être validé et forcé sous un répertoire dédié au dépôt, indépendamment de la configuration non fiable.
La fonctionnalité de sandbox présente plusieurs problèmes importants : la configuration DOCKER_HOST documentée n'est pas effectivement utilisée, les contextes de build standards des devcontainers ne sont pas supportés, et workspaceFolder permet de contourner la restriction des outils de lecture. Le traitement des erreurs de build et le nettoyage des ressources doivent également être durcis, et la suppression de .dockerignore expose inutilement le contexte de build.
Cost: $0.08248642
## Review Feedback
### 6 issues found.
---
### Summary
La fonctionnalité de sandbox présente plusieurs problèmes importants : la configuration DOCKER_HOST documentée n'est pas effectivement utilisée, les contextes de build standards des devcontainers ne sont pas supportés, et workspaceFolder permet de contourner la restriction des outils de lecture. Le traitement des erreurs de build et le nettoyage des ressources doivent également être durcis, et la suppression de .dockerignore expose inutilement le contexte de build.
---
### Cost: $0.08248642
La suppression de .dockerignore fait que le contexte envoyé lors de buildah bud contient désormais notamment .git, les répertoires de build et le fichier .env local. Même si le Containerfile ne le copie pas explicitement, ce contexte est transmis au moteur de build et peut contenir des secrets ou devenir inutilement volumineux. Il faut conserver un .dockerignore excluant au minimum .git, .env*, target et les fichiers locaux.
La suppression de `.dockerignore` fait que le contexte envoyé lors de `buildah bud` contient désormais notamment `.git`, les répertoires de build et le fichier `.env` local. Même si le Containerfile ne le copie pas explicitement, ce contexte est transmis au moteur de build et peut contenir des secrets ou devenir inutilement volumineux. Il faut conserver un `.dockerignore` excluant au minimum `.git`, `.env*`, `target` et les fichiers locaux.
Le nettoyage s'arrête immédiatement si remove_container échoue, et les suppressions du réseau et de l'image ne sont alors jamais tentées. Une course, un conteneur déjà supprimé ou une erreur transitoire peut donc laisser systématiquement le réseau et l'image derrière lui. Il faut tenter les trois nettoyages indépendamment, puis agréger ou retourner l'erreur pertinente.
Le nettoyage s'arrête immédiatement si `remove_container` échoue, et les suppressions du réseau et de l'image ne sont alors jamais tentées. Une course, un conteneur déjà supprimé ou une erreur transitoire peut donc laisser systématiquement le réseau et l'image derrière lui. Il faut tenter les trois nettoyages indépendamment, puis agréger ou retourner l'erreur pertinente.
La valeur de DOCKER_HOST est seulement stockée dans endpoint pour les logs : Docker::connect_with_defaults() ne l'utilise pas. Ainsi, la configuration documentée pour Podman ou pour un socket Docker non standard est ignorée et le runtime tente toujours sa connexion par défaut. Il faut construire le client Bollard avec l'endpoint lu dans l'environnement, ou supprimer cette configuration trompeuse.
La valeur de `DOCKER_HOST` est seulement stockée dans `endpoint` pour les logs : `Docker::connect_with_defaults()` ne l'utilise pas. Ainsi, la configuration documentée pour Podman ou pour un socket Docker non standard est ignorée et le runtime tente toujours sa connexion par défaut. Il faut construire le client Bollard avec l'endpoint lu dans l'environnement, ou supprimer cette configuration trompeuse.
Le flux de build n'est considéré en échec que lorsque error_detail.message est renseigné. L'API Docker peut aussi fournir l'erreur dans le champ error de BuildInfo; dans ce cas la méthode retourne Ok(()) alors que l'image n'a pas été construite. Il faut traiter les deux champs et préserver le message d'erreur le plus utile.
Le flux de build n'est considéré en échec que lorsque `error_detail.message` est renseigné. L'API Docker peut aussi fournir l'erreur dans le champ `error` de `BuildInfo`; dans ce cas la méthode retourne `Ok(())` alors que l'image n'a pas été construite. Il faut traiter les deux champs et préserver le message d'erreur le plus utile.
Le schéma ne prend en charge que dockerfile et args, puis DevContainer::build utilise systématiquement le répertoire du Dockerfile comme contexte. La spécification devcontainer.json permet pourtant de définir build.context (souvent la racine du dépôt) ; les Dockerfile qui font COPY . ... ou qui utilisent un contexte distinct échoueront ou ne verront pas les fichiers attendus. Il faut modéliser et résoudre le contexte de build, avec une validation empêchant qu'il sorte du dépôt.
Le schéma ne prend en charge que `dockerfile` et `args`, puis `DevContainer::build` utilise systématiquement le répertoire du Dockerfile comme contexte. La spécification `devcontainer.json` permet pourtant de définir `build.context` (souvent la racine du dépôt) ; les Dockerfile qui font `COPY . ...` ou qui utilisent un contexte distinct échoueront ou ne verront pas les fichiers attendus. Il faut modéliser et résoudre le contexte de build, avec une validation empêchant qu'il sorte du dépôt.
La vérification ne protège pas réellement la frontière du dépôt lorsque workspaceFolder vient du dépôt non fiable. Une valeur comme / ou /tmp passe la normalisation et permet aux outils de lire n'importe quel chemin du conteneur, contrairement au contrat annoncé (« confined to the repository workspace »). Il faut imposer un workspace absolu dédié et vérifier qu'il s'agit bien du répertoire de dépôt, plutôt que de faire confiance à workspaceFolder fourni par la PR.
La vérification ne protège pas réellement la frontière du dépôt lorsque `workspaceFolder` vient du dépôt non fiable. Une valeur comme `/` ou `/tmp` passe la normalisation et permet aux outils de lire n'importe quel chemin du conteneur, contrairement au contrat annoncé (« confined to the repository workspace »). Il faut imposer un workspace absolu dédié et vérifier qu'il s'agit bien du répertoire de dépôt, plutôt que de faire confiance à `workspaceFolder` fourni par la PR.
La PR améliore nettement l'isolation en ignorant les runArgs non fiables, en coupant le réseau après les hooks, en bornant les diffusions API et en ajoutant une boucle d'agent avec des outils en lecture seule. Il reste toutefois une échappée de chemin par comparaison de chaînes, une collecte de sortie non bornée avant troncature et un nettoyage de ressources interrompu au premier échec.
Cost: $0.04950894
## Review Feedback
### 3 issues found.
- 1 security
- 2 performance
---
### Summary
La PR améliore nettement l'isolation en ignorant les `runArgs` non fiables, en coupant le réseau après les hooks, en bornant les diffusions API et en ajoutant une boucle d'agent avec des outils en lecture seule. Il reste toutefois une échappée de chemin par comparaison de chaînes, une collecte de sortie non bornée avant troncature et un nettoyage de ressources interrompu au premier échec.
---
### Cost: $0.04950894
[performance] Si remove_container échoue, le ? quitte immédiatement la méthode et le réseau ainsi que l'image ne sont jamais supprimés. Après un daemon indisponible, un container déjà supprimé ou une erreur transitoire, chaque sandbox peut donc laisser des ressources persistantes. Effectuer le nettoyage du réseau et de l'image même lorsque la suppression du container échoue, puis retourner l'erreur.
**[performance]** Si `remove_container` échoue, le `?` quitte immédiatement la méthode et le réseau ainsi que l'image ne sont jamais supprimés. Après un daemon indisponible, un container déjà supprimé ou une erreur transitoire, chaque sandbox peut donc laisser des ressources persistantes. Effectuer le nettoyage du réseau et de l'image même lorsque la suppression du container échoue, puis retourner l'erreur.
[performance] La collecte de la sortie d'un exec accumule toute la sortie dans deux String sans limite. La limitation à 32 KiB dans l'agent intervient seulement après le retour de cette fonction ; un read_file sur un gros fichier ou un grep très bavard peut donc consommer une quantité arbitraire de mémoire avant d'être tronqué. Il faut borner la sortie pendant la lecture, ou interrompre l'exec dès que la limite est atteinte.
**[performance]** La collecte de la sortie d'un exec accumule toute la sortie dans deux `String` sans limite. La limitation à 32 KiB dans l'agent intervient seulement après le retour de cette fonction ; un `read_file` sur un gros fichier ou un `grep` très bavard peut donc consommer une quantité arbitraire de mémoire avant d'être tronqué. Il faut borner la sortie pendant la lecture, ou interrompre l'exec dès que la limite est atteinte.
[security] La vérification normalized.starts_with(workspace) compare des chaînes et ne respecte pas les frontières de composants. Avec un workspace /workspaces/repo, un chemin comme ../repo-secrets devient /workspaces/repo-secrets et passe ce test, ce qui permet aux outils de lire un répertoire voisin dans le container. Utiliser une comparaison de chemins basée sur les composants, par exemple strip_prefix(workspace).is_ok() après normalisation.
**[security]** La vérification `normalized.starts_with(workspace)` compare des chaînes et ne respecte pas les frontières de composants. Avec un workspace `/workspaces/repo`, un chemin comme `../repo-secrets` devient `/workspaces/repo-secrets` et passe ce test, ce qui permet aux outils de lire un répertoire voisin dans le container. Utiliser une comparaison de chemins basée sur les composants, par exemple `strip_prefix(workspace).is_ok()` après normalisation.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Impl sandboxing for agentic working (only review job for this release)
@Herald
Review Feedback
5 issues found.
Summary
Le code ajouté est bien structuré et suit les bonnes pratiques Rust (utilisation de
thiserror,serde,PathBuf, etc.). La fonctionnalité de parsing devcontainer est claire et bien testée. Cependant, quelques points méritent attention :label=disable).buildest obligatoire alors qu'il pourrait être absent).first_existing_container_filerenvoie un chemin par défaut qui échouera si aucun fichier n'existe, ce qui pourrait être amélioré.parse.Dans l'ensemble, le travail est de bonne qualité et prêt à être fusionné après ces ajustements.
Cost: $0.00146804
@@ -19,5 +19,6 @@},"workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/herald,type=bind","workspaceFolder": "/workspaces/herald","runArgs": ["--userns=keep-id", "--security-opt", "label=disable"],L'option
--security-opt label=disabledésactive la sécurité SELinux, ce qui réduit l'isolation du conteneur. Pour un environnement de développement, cela peut être acceptable, mais il faut être conscient des implications de sécurité. Envisagez d'utiliser--security-opt label=type:container_runtime_tou d'autres options moins permissives si possible.@@ -0,0 +14,4 @@}#[derive(Debug, Deserialize)]pub struct DevContainerSchema {Le champ
buildest obligatoire, mais le schéma standard devcontainer.json permet également d'utiliserimageà la place debuild. Si quelqu'un utiliseimage, la désérialisation échouera. Envisagez de rendrebuildoptionnel avec#[serde(default)]et de gérer le cas où nibuildniimagene sont présents.@@ -0,0 +94,4 @@}}fn first_existing_container_file(base_dir: &Path) -> PathBuf {Si aucun fichier
DockerfileouContainerfilen'existe, la fonction retournebase_dir.join("Dockerfile"), ce qui échouera plus tard avecContainerFileNotFound. Cela pourrait être plus explicite en retournant uneOption<PathBuf>et en laissant l'appelant gérer l'absence de fichier. Sinon, le message d'erreur actuel est suffisant.@@ -0,0 +102,4 @@.unwrap_or_else(|| base_dir.join("Dockerfile"))}pub async fn parse(path: impl AsRef<Path>) -> Result<DevContainer, ParseError> {La fonction
parseest asynchrone, mais les tests unitaires ne la testent pas directement (ils testentTryFrom). Il serait bon d'ajouter un test asynchrone pourparseafin de couvrir le flux complet (lecture du fichier, désérialisation, conversion).@@ -0,0 +127,4 @@use std::fs;#[test]fn resolves_configured_containerfile_relative_to_devcontainer_file() {Les tests sont synchrones et utilisent
std::fsplutôt quetokio::fs. C'est acceptable pour l'instant, mais si l'implémentation venait à dépendre de fonctionnalités asynchrones (commetokio::fs), les tests pourraient ne plus être représentatifs.Sandboxingto 1.2: Sandboxing@Herald
Review Feedback
4 issues found.
Summary
La mise en place du sandboxing est complète et bien structurée : isolation réseau, timeouts, nettoyage systématique, résolution de chemins sécurisée. Quelques points mineurs de robustesse (gestion des chemins non-UTF8, redondance de vérification) et de sécurité (fuite potentielle du token dans les logs git) pourraient être améliorés, mais l’ensemble est de bonne qualité et conforme aux bonnes pratiques. Les tests unitaires couvrent les cas importants.
Cost: $0.007599319
@@ -0,0 +477,4 @@}/// Nettoie une chaîne pour qu'elle puisse servir de nom d'image/container docker.fn sanitize(input: &str) -> String {La fonction
sanitizetronque les tirets et points en début et fin, mais elle pourrait laisser passer des séquences de deux tirets consécutifs qui sont autorisées par Docker. Ce n’est pas critique, mais une validation plus stricte (limiter la longueur, interdire les séquences interdites) améliorerait la robustesse.@@ -0,0 +100,4 @@dest: &Path,) -> anyhow::Result<()> {let dest = dest.display().to_string();Le chemin
destest converti en chaîne viadisplay(), ce qui peut planter si le chemin contient des caractères non-UTF-8. Dans un contexte Linux, c’est très rare, mais il serait plus sûr d’utiliserto_string_lossy().@@ -0,0 +144,4 @@/// Runs git with the token injected through `http.extraHeader`, keeping the/// secret out of the process arguments.async fn run_git(token: &str, args: &[String]) -> anyhow::Result<()> {let output = tokio::process::Command::new("git")Le token d’authentification est passé via
GIT_CONFIG_VALUE_0. C’est correct car il n’apparaît pas dans les arguments. Toutefois, en cas d’erreur, git peut afficher la ligne de commande dans les logs, ce qui pourrait exposer le token indirectement via l’URL de clonage. Envisager d’utiliser un credential helper ou de filtrer les messages d’erreur.@@ -0,0 +177,4 @@let normalized =normalize(&candidate).with_context(|| format!("path `{path}` escapes the workspace"))?;if !normalized.starts_with(workspace) {La vérification est redondante :
normalize()garantit déjà que le chemin ne peut pas sortir de la racine. Cependant, la double protection ne pose pas de problème de sécurité. On pourrait simplifier en supprimant cette condition si on est certain quenormalize()ne retourne qu’un chemin bien formé.@Herald
Error while reviewing: expected value at line 1 column 1
Error while reviewing: the model did not answer with the expected JSON object
Review Feedback
14 issues found.
Summary
PR « 1.2: Sandboxing » de bonne qualité globale : découpage en crates (
devcontainer-rs/herald-server), client OpenRouter maison bien documenté,sandboxstructuré (agent, tools) avec des outils read-only en argv (pas de shell, donc pas d'injection), confinement lexical des chemins, jeton git passé viahttp.extraHeaderen variable d'environnement (pas dans la ligne de commande), outils sélectionnés par type de webhook de manière exhaustive, et une couverture de tests remarquable (parsing de diff, résolution des côtés, hooks, timeouts, normalisation des chemins).Points bloquants à traiter avant merge : (1) les
runArgsdudevcontainer.jsondu dépôt analysé sont injectés tels quels dansdocker run, ce qui permet de monter l'hôte (-v /:/host,--privileged,--pid=host) et d'annuler l'isolation annoncée dans le README — critique pour une PR dont l'objet est justement le sandboxing ; (2) l'image finale duContainerfilene contient nigitni runtime conteneur, ce qui rend la sandbox inutilisable en production ; (3) lejoindu chemin de Dockerfile peut sortir du dossier du devcontainer (path traversal).Points importants mais non bloquants : absence de plafond sur les sorties d'outils (
cat,grep,find) et clones répétés de la conversation dans l'agent (contexte/coût), validation trop laxiste deSANDBOX_MAX_ITERATIONS, suppression du.dockerignore, cohérence des versions Rust (1.97 vs 1.98) et des dépendances de workspace (tempfile), pas de smoke test de l'image en CI, et affirmation d'isolation à nuancer. À noter aussi la réponse du bot (« @Herald ») : le préfixe attendu est@{bot_name}, donc bien vérifier la casse du nom de bot.Cost: $0.2029030056
@@ -19,5 +19,6 @@},"workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/herald,type=bind","workspaceFolder": "/workspaces/herald","runArgs": ["--userns=keep-id", "--security-opt", "label=disable"],Indentation par tabulation incohérente avec le reste du fichier ;
--security-opt label=disabledésactive l'étiquetage SELinux et--userns=keep-idest spécifique à Podman. À nettoyer/justifier, d'autant que ce devcontainer sert aussi de contexte d'exécution à la sandbox.@@ -1,4 +0,0 @@target/La suppression du
.dockerignoreréintègretarget/et.envdans le contexte envoyé au builder (buildah bud ... .), ce qui alourdit fortement le build et expose potentiellement des secrets. Conserver un.dockerignoreminimal (target,.env,.git).@@ -15,1 +21,3 @@- cargo test- cargo test --workspace --all-targets- name: container-buildLe job
container-buildcompile l'image mais ne l'exécute jamais : il ne détectera donc pas l'absence degit/runtime conteneur dans l'image finale. Ajouter un smoke test (lancer l'image et vérifier la présence degitetdocker/podman), et noter que le pipeline ne se déclenche que surpush, pas sur les pull requests.@@ -1,12 +1,15 @@FROM rust:1.96 as builderFROM rust:1.97-trixie as builderIncohérence de toolchain :
rust:1.97-trixieici, alors que.devcontainer/Dockerfileet.woodpecker/tests.ymlutilisentrust:1.98. Aligner les versions et écrireASen majuscules (convention Dockerfile).@@ -10,3 +13,2 @@WORKDIR /appCOPY --from=builder /app/target/release/herald .CMD [ "./herald" ]COPY --from=builder /app/target/release/herald-server .L'étage final
debian:trixie-slimne contient que le binaire, or herald-server a besoin degit(clone de la PR) et d'un binaire de runtime conteneur (docker/podman) pour exécuter la sandbox. Avec cette image, chaque revue échouera à l'exécution. Ajouter ces paquets (plusca-certificates) ou documenter explicitement le montage du socket/runtime requis.@@ -41,0 +59,4 @@The container runtime is selected with `CONTAINER_RUNTIME` (`docker` or`podman`). The repository must contain a `.devcontainer/devcontainer.json`.Each sandbox is isolated: it gets its own image tag, container and network. TheL'affirmation « Each sandbox is isolated » est plus forte que la réalité tant que les
runArgs/containerEnvdu dépôt analysé sont transmis tels quels àdocker run(cf.container.rs). Soit durcir le code, soit nuancer la section en listant les limites de l'isolation.@@ -0,0 +323,4 @@];if let Some(network) = network {args.push("--network".to_string());L'option
--networkest insérée avantself.run_args, qui sont concaténés ensuite (ligne 340) : dansdocker run, la dernière option gagne, donc unrunArgsdu dépôt peut remplacer le réseau dédié et contourner la coupure réseau effectuée en fin deup(). Documenter/verrouiller cet ordre si l'isolation réseau doit être garantie.@@ -0,0 +337,4 @@args.push(format!("{key}={value}"));}args.extend(self.run_args.iter().cloned());Les
runArgsproviennent dudevcontainer.jsondu dépôt analysé, donc d'une PR potentiellement issue d'un fork : ils sont concaténés tels quels aux arguments dedocker run. UnrunArgsdu type["--privileged", "-v", "/:/host"](ou--pid=host,--cap-add=SYS_ADMIN) monte l'hôte dans le conteneur et annule totalement l'isolation annoncée. Comme ils sont ajoutés après le--networkposé ligne 326, ils peuvent aussi le surcharger (--network host). Prévoir une liste blanche d'options (ou ignorerrunArgs/containerEnvpour les dépôts non fiables) avant de démarrer le conteneur.@@ -0,0 +86,4 @@.parent().ok_or_else(|| ParseError::InvalidDevContainerPath(devcontainer_path.clone()))?;let container_file_path = base_dir.join(schema.build.dockerfile);base_dir.join(schema.build.dockerfile): undockerfileabsolu (/etc/passwd) ou contenant..sort du dossier du devcontainer et devient le-fdedocker build(lecture de fichier arbitraire côté hôte). Valider que le chemin reste bien sousbase_dir(vianormalizepar exemple) avant de l'accepter.@@ -0,0 +25,4 @@metrics = { workspace = true }metrics-exporter-prometheus = { workspace = true }devcontainer-rs = { path = "../devcontainer-rs" }tempfile = "3"tempfile = "3"est déclaré en dur alors que toutes les autres dépendances passent par[workspace.dependencies]: le placer dans le workspace pour garder une version unique et cohérente (idem pour le dev-dependency dedevcontainer-rs).@@ -0,0 +29,4 @@let metrics_bind_addr = std::env::var("METRICS_BIND_ADDR").ok();let container_runtime =std::env::var("CONTAINER_RUNTIME").unwrap_or_else(|_| "docker".to_string());let sandbox_max_iterations = std::env::var("SANDBOX_MAX_ITERATIONS")SANDBOX_MAX_ITERATIONSretombe silencieusement sur 8 si la valeur est invalide et accepte0: avec 0, la boucle de l'agent (1..=max_iterations) ne s'exécute pas et la revue échoue immédiatement. Valider la valeur (>= 1) et journaliser en cas de repli sur la valeur par défaut.@@ -0,0 +83,4 @@max_iterations: config.sandbox_max_iterations,};if !sandbox.runtime.available().await {L'absence de runtime conteneur ne produit qu'un
warn: le service démarre normalement alors que toutes les revues échoueront ensuite. Le contrôle étant déjà fait au démarrage, un échec rapide (fail-fast) ou un mode dégradé explicite serait plus sûr et plus lisible.@@ -0,0 +44,4 @@for iteration in 1..=max_iterations {let response = open_router.chat_with_tools(messages.clone(), tool_definitions.clone())messages.clone()ettool_definitions.clone()à chaque itération recopient toute la conversation à chaque tour (coût O(n²)) alors qu'un passage par référence (&[Message]) suffirait. Par ailleurs rien ne borne la taille du contenu renvoyé par les outils : ajouter un plafond par appel pour maîtriser le contexte et le coût.@@ -0,0 +122,4 @@into_stdout(output)}async fn read_file(sandbox: &Sandbox, args: &Value) -> anyhow::Result<String> {read_fileexécutecatsans aucune borne : un gros fichier (ou binaire) injecte tout son contenu dans la conversation et peut saturer le contexte et faire exploser le coût. Plafonner le nombre de lignes/octets renvoyés avec une note de troncature (idem pourgrepetfind, potentiellement très verbeux).Review Feedback
18 issues found.
Summary
Synthèse de la revue (PR « 1.2: Sandboxing ») : la réorganisation en workspace multi-crates (
crates/herald-server,crates/devcontainer-rs) et la séparation des actions (bot_actions/review.rs), du sandbox (sandbox/{mod,agent,tools}.rs), du client LLM (open_router.rs) et des constantes est une bonne amélioration structurelle, cohérente avec l'objectif de sandboxing. Points bloquants / à corriger en priorité : (1) la suppression complète de.dockerignoredoit être rétablie ou remplacée, sous peine d'incluretarget/,.git/et d'éventuels fichiers.envdans le contexte de build ; (2) le durcissement du sandbox doit être explicite et vérifiable (allowlist de commandes sans shell, confinement des chemins, timeouts, limites mémoire/CPU, utilisateur non-root, absence de réseau ou réseau filtré) et échouer de manière fermée ; (3) la gestion des secrets (clé OpenRouter, tokens Gitea) doit être revue pour garantir l'absence de fuite dans les logs, les prompts ou les commentaires publiés. Recommandations secondaires : épingler les images (Dockerfile/Containerfile) et passer à un build multi-étapes non-root, ajouter des tests unitaires sur les nouvelles actions et le sandbox, harmoniser la configuration éditeur (Zed vs VSCode supprimé), compléter.env.exampleet le README pour documenter le modèle de menace et les variables d'environnement, et vérifier le lockfile ainsi que la couverture CI (--workspace, fmt, clippy). Remarque : je n'ai pas pu lire le contenu des fichiers du dépôt (accès refusé dans l'environnement d'exécution) ; ces retours sont donc formulés comme des points de vigilance à confirmer sur le code réel.Cost: $0.042505191
@@ -1,4 +1,4 @@FROM debian:trixieFROM rust:1.98-trixieÉpingler l'image de base par digest (ou au moins une version majeure mineure) pour la reproductibilité, et éviter d'exécuter le conteneur de développement en root si possible. Vérifier que les outils ajoutés dans cette image ne sont pas supposés présents en production (le devcontainer ne doit pas être une dépendance implicite du runtime).
@@ -1,4 +0,0 @@target/La suppression complète de
.dockerignoreest problématique : le contexte de build inclura désormaistarget/,.git/,.envet autres fichiers locaux, ce qui alourdit et surtout peut faire fuiter des secrets dans l'image via une copie du contexte. Si le fichier a été remplacé ailleurs, le référencer explicitement ; sinon le restaurer.@@ -18,5 +17,8 @@ SENTRY_DSN=RUST_LOG=infoRUST_BACKTRACE=1METRICS_BIND_ADDR=Les nouvelles variables doivent être documentées ici avec un commentaire indiquant leur rôle et une valeur d'exemple non sensible. Vérifier qu'aucune vraie clé/token n'a été committée et que le fichier reste aligné avec les variables réellement lues dans
env.rs.@@ -3,13 +3,27 @@ when:- pushsteps:- name: fmtPipeline CI : vérifier que
cargo fmt --check,cargo clippy -- -D warningsetcargo test --all-featurescouvrent bien le nouveau workspace multi-crates (utiliser--workspace), et que les tests du sandbox qui nécessitent Docker/root sont soit exécutés, soit explicitement ignorés avec justification.@@ -0,0 +1,22 @@{Ajouter la configuration Zed tout en supprimant
.vscode/settings.jsoncrée une incohérence pour les contributeurs : il vaut mieux conserver les deux (ou documenter l'éditeur recommandé) et s'assurer qu'aucun chemin absolu local ni préférence personnelle n'est committé ici.@@ -5,3 +5,3 @@[[package]]name = "actix-codec"version = "0.5.2"version = "0.5.3"Le diff du lockfile est volumineux : vérifier qu'il ne contient que des changements attendus (nouvelles dépendances réellement utilisées) et pas d'ajout transitif superflu, puis confirmer que le build est reproductible en CI avec
--locked.@@ -2,3 +1,1 @@name = "herald"version = "1.1.0"edition = "2024"[workspace]Passage en workspace multi-crates : vérifier que les dépendances communes sont déclarées dans
[workspace.dependencies]et réutilisées avecworkspace = truedans les crates, pour éviter les divergences de versions. Ajouter aussi les métadonnées de package communes (edition, license, version) via[workspace.package].@@ -1,12 +1,15 @@FROM rust:1.96 as builderFROM rust:1.97-trixie as builderBuild de production : privilégier un build multi-étapes pour ne pas embarquer toolchain et sources dans l'image finale, exécuter le binaire avec un utilisateur non privilégié, et épingler les dépendances de l'étape de build. Ajouter un
ENTRYPOINT/CMDexplicite et un label de version.@@ -38,6 +37,34 @@ Herald reads its configuration from environment variables (a `.env` file is supp| `METRICS_BIND_ADDR` | *(optional)* Bind address for the Prometheus metrics endpoint (e.g. `0.0.0.0:9100`). If unset, the metrics exporter is disabled. || `SENTRY_DSN` | *(optional)* Sentry DSN for error tracking || `RUST_LOG` | *(optional)* Log level, defaults to `info` || `CONTAINER_RUNTIME` | *(optional)* Container runtime binary used for the sandbox (`docker` or `podman`). Defaults to `docker` |La documentation du sandbox doit préciser ce qui est isolé et ce qui ne l'est pas (réseau, filesystem, credentials), comment le configurer via l'environnement, et rappeler que l'exécution de code issu d'une PR est une opération à risque. Ajouter si possible un exemple de variable d'environnement sécurisé.
@@ -0,0 +1,264 @@use crate::{gitea::{GiteaAPI, WebhookType},Adaptation aux nouveaux chemins de crates : profiter du déplacement pour vérifier que les imports internes utilisent bien
crate::(et non des chemins relatifs fragiles) et qu'aucunusemort ne subsiste après la réorganisation.@@ -0,0 +1,863 @@use tracing::{info, instrument, warn};Action de review : ajouter des tests unitaires sur la construction du prompt, la gestion des erreurs et les cas limites (contenu vide, réponse LLM tronquée, échec du sandbox). Prévoir aussi une limite de taille de la réponse publiée dans Gitea pour éviter de dépasser les quotas de l'API.
@@ -0,0 +1,58 @@pub const GITEA_SIG_HEADER_NAME: &str = "x-gitea-signature";Centraliser les valeurs magiques dans un module de constantes est une bonne pratique : s'assurer que les valeurs liées au sandbox (timeouts, limites mémoire, chemins) sont bien documentées par un commentaire indiquant l'unité (secondes, octets) et la raison du choix.
@@ -0,0 +12,4 @@pub gitea_token: String,pub gitea_timeout: u64,pub metrics_bind_addr: Option<String>,pub container_runtime: String,Nouvelles variables d'environnement : valider leur présence et leur format au démarrage (échouer tôt avec un message explicite) plutôt que de laisser une valeur par défaut silencieuse. Prévoir aussi une valeur par défaut sûre (fail-closed) pour tout réglage désactivant l'isolation.
@@ -0,0 +1,441 @@//! Minimal OpenRouter chat-completions client.Module OpenRouter : la clé d'API doit être lue depuis l'environnement/state (jamais codée en dur, jamais loggée). Vérifier que les erreurs renvoyées par l'API HTTP ne propagent pas la clé ni le contenu complet des prompts dans les logs ou dans le commentaire publié. Ajouter un timeout et une gestion des codes 429/5xx avec retry borné.
@@ -0,0 +1,137 @@//! Tool-calling loop driving the model against a [`Sandbox`].Processus lancés dans le sandbox : vérifier qu'un timeout est appliqué, que le processus (et son groupe) est tué en cas de dépassement ou d'annulation, et que les pipes stdout/stderr sont consommés pour éviter un blocage du processus si le buffer se remplit. Prévoir aussi une limite de taille de sortie pour ne pas saturer la mémoire avec un output verbeux ou malveillant.
@@ -0,0 +1,195 @@//! Sandboxed tool execution for the AI bot.Nouveau module
sandbox: ajouter en tête de fichier une documentation (doc-comment) décrivant le modèle de menace et le niveau d'isolation réellement appliqué (namespaces, cgroups, utilisateur non-root, montages read-only, limites CPU/RAM/pid). Sans cela, il est impossible de valider que le nom « sandbox » correspond à une isolation effective et non à un simple répertoire de travail. Préciser aussi le comportement en cas d'échec d'initialisation de l'isolation (échec fermé plutôt qu'ouvert).@@ -0,0 +17,4 @@use anyhow::Context;use devcontainer_rs::{Container, ContainerRuntime, ExecOutput};use tempfile::TempDir;use tracing::{info, instrument};Attention aux chemins : tous les fichiers créés ou montés dans le sandbox doivent être confinés sous une racine contrôlée (canonicalisation + vérification de préfixe) pour empêcher toute traversée de répertoire (
.., liens symboliques). Un chemin dérivé d'un nom de fichier d'une PR est une entrée non fiable.@@ -0,0 +1,246 @@//! Tool definitions exposed to the model and their execution inside aExécution d'outils dans le sandbox : s'assurer que les commandes passent par une liste blanche explicite (allowlist de binaires + arguments validés) et jamais par un shell (
sh -c) avec interpolation de chaînes issues du contenu de la PR. Les entrées utilisateur (diff, messages, noms de fichiers) doivent être passées comme arguments séparés, avec validation/échappement. Vérifier également l'absence deCommand::newconstruit à partir d'une variable non validée.Review Feedback
19 issues found.
Summary
Synthèse de la revue (PR « 1.2: Sandboxing ») :
Points positifs : la réorganisation en workspace Cargo (
crates/herald-server,crates/devcontainer-rs) clarifie la séparation entre le serveur et l'exécution en conteneur, et l'introduction d'un modulesandbox(agent + tools) va dans le bon sens. Le fait de regrouperDockerfile/Containerfile/CI/éditeurs montre une volonté d'unifier l'environnement de développement et l'intégration continue.Points d'attention :
[workspace.dependencies]+workspace = true, définirresolver, et s'assurer que les crates internes sontpublish = false..env.exampleavec des valeurs par défaut sûres (le sandboxing ne doit pas pouvoir être désactivé silencieusement par une variable manquante).src/*->crates/herald-server/src/*) et que la CI couvre bien l'ensemble du workspace (tests, fmt, clippy).Remarque : n'ayant pas pu accéder au contenu des fichiers pendant cette revue, les commentaires ci-dessus sont des points de vigilance généraux rattachés aux lignes modifiées ; ils devront être confirmés/ajustés en lisant le diff complet.
Cost: $0.021022326000000004
@@ -1,12 +1,15 @@FROM rust:1.96 as builderFROM rust:1.97-trixie as builderÉpingler l'image de base par digest plutôt que par tag flottant, et vérifier que l'image de sandbox ne contient pas de secrets ni d'outils inutiles (surface d'attaque réduite, utilisateur non-root).
@@ -38,6 +37,34 @@ Herald reads its configuration from environment variables (a `.env` file is supp| `METRICS_BIND_ADDR` | *(optional)* Bind address for the Prometheus metrics endpoint (e.g. `0.0.0.0:9100`). If unset, the metrics exporter is disabled. || `SENTRY_DSN` | *(optional)* Sentry DSN for error tracking || `RUST_LOG` | *(optional)* Log level, defaults to `info` || `CONTAINER_RUNTIME` | *(optional)* Container runtime binary used for the sandbox (`docker` or `podman`). Defaults to `docker` |Documenter le nouveau fonctionnement (workspace multi-crates + sandboxing) : prérequis (Podman/Docker), variables d'environnement nécessaires et limites connues de l'isolation.
@@ -0,0 +12,4 @@pub gitea_token: String,pub gitea_timeout: u64,pub metrics_bind_addr: Option<String>,pub container_runtime: String,Nouvelles variables d'environnement : documenter chacune dans
.env.exampleet fournir une valeur par défaut sûre ; une variable manquante ne devrait pas désactiver silencieusement le sandboxing.@@ -0,0 +1,131 @@use crate::{Nouveau point d'entrée du crate
herald-server: vérifier que les modules déplacés (sandbox,open_router,consts,bot_actions) sont bien déclarés et que plus aucune référence au chemin racinesrc/...ne subsiste.@@ -0,0 +1,441 @@//! Minimal OpenRouter chat-completions client.Fichier déplacé depuis
src/open_router.rs: s'assurer qu'il n'y a pas de duplication avec l'ancien fichier supprimé et que les nouveaux appels (utilisés par l'agent de sandbox) gèrent les erreurs HTTP et les timeouts.@@ -0,0 +1,137 @@//! Tool-calling loop driving the model against a [`Sandbox`].Boucle d'agent exécutant des outils : prévoir un nombre maximal d'itérations et une limite de temps globale, sinon une boucle d'appels d'outils peut consommer indéfiniment des tokens/CPU.
@@ -0,0 +1,195 @@//! Sandboxed tool execution for the AI bot.Le module de sandboxing devrait exposer des limites explicites (mémoire, CPU, timeout, réseau désactivé par défaut) et retourner des erreurs typées plutôt que des
String, afin que l'appelant puisse décider de la politique de repli.@@ -0,0 +1,246 @@//! Tool definitions exposed to the model and their execution inside aLes outils exposés à l'agent doivent valider leurs entrées (chemins relatifs, pas de
.., pas de shell arbitraire) et documenter clairement le contrat JSON de chaque outil ; c'est la principale surface d'attaque du sandboxing.@@ -1,214 +0,0 @@use futures_util::stream::TryStreamExt;Ancien fichier supprimé : vérifier que la logique a bien été entièrement migrée vers
crates/herald-server/src/bot_actions/review.rset qu'aucun test ne référence encore l'ancien chemin.@@ -1,44 +0,0 @@pub const GITEA_SIG_HEADER_NAME: &str = "x-gitea-signature";Fichier supprimé : s'assurer que toutes les constantes sont reprises dans
crates/herald-server/src/consts.rset qu'aucune constante n'a été perdue lors du déplacement.Review Feedback
3 issues found.
Summary
La fonctionnalité de sandbox est structurée proprement et évite l'injection shell pour les outils, mais l'isolation n'est pas sûre tant que les chemins de build et les options
runArgscontrôlés par le dépôt ne sont pas bornés. Il faut également limiter les sorties des outils afin d'éviter les dénis de service.Cost: $0.01940168
Review Feedback
3 issues found.
Summary
La sandbox introduit une bonne séparation fonctionnelle et limite les outils exposés, mais elle laisse deux failles importantes dans la frontière de confiance: une pull request peut lire des variables d'environnement secrètes via
localEnv, et une commande dépassant son timeout n'est pas réellement interrompue. Le traitement des renommages peut également produire des commentaires sur le mauvais chemin; ces points doivent être corrigés avant validation.Cost: $0.06652119
Review Feedback
4 issues found.
Summary
La fonctionnalité apporte une séparation utile en crate et borne correctement les appels d'outils, mais plusieurs chemins contrôlés par la pull request ne sont pas suffisamment confinés. En particulier, le chemin du Dockerfile peut exfiltrer un contexte hors du dépôt, le confinement SELinux est désactivé et aucune limite de ressources n'empêche un déni de service ; le chemin d'échec de création laisse en outre des ressources du daemon. Ces points doivent être corrigés avant de considérer le sandboxing comme sûr.
Cost: $0.046895
@@ -0,0 +111,4 @@.collect(),),working_dir: Some(workspace_folder.clone()),host_config: Some(HostConfig {[Élevé] Le conteneur sandbox n'a aucune limite de ressources (mémoire, CPU, processus ou taille disque). Le Dockerfile, les hooks et les commandes demandées par le modèle sont contrôlés indirectement par une PR non fiable et peuvent provoquer une fork bomb, remplir le disque ou monopoliser l'hôte. Ajouter des limites explicites dans
HostConfiget prévoir aussi un nettoyage fiable en cas de dépassement.@@ -0,0 +122,4 @@// est refusé (EACCES), ce qui fait échouer tous les outils de la// sandbox. C'est le compromis inverse de l'alternative `:Z` sur le// montage, qui re-labellise le clone et garde le confinement SELinux.security_opt: Some(vec![String::from("label=disable")]),[Élevé]
label=disabledésactive explicitement le confinement SELinux pour un conteneur exécutant du code contrôlé par une pull request. Cela élargit fortement l'impact d'une compromission du conteneur et contredit l'objectif de sandboxing. Préférer un montage correctement relabellisé (:Z/équivalent selon le runtime) ou rendre ce comportement optionnel et documenter clairement la perte d'isolation.@@ -0,0 +59,4 @@.parent().ok_or_else(|| ParseError::InvalidDevContainerPath(devcontainer_path.clone()))?;let container_file_path = base_dir.join(schema.build.dockerfile);[Bloquant] Le chemin du Dockerfile est construit directement à partir d'une valeur contrôlée par la pull request. Un chemin absolu ou contenant suffisamment de
..peut faire sortir le contexte de build du clone ;build()empaquettera alors le répertoire parent arbitraire et l'enverra au daemon, ce qui peut exposer des fichiers de l'hôte au build non fiable. Il faut rejeter les chemins absolus et vérifier lexicalement que le Dockerfile et son contexte restent sous la racine du dépôt cloné.@@ -0,0 +72,4 @@container,};sandbox.check_workspace().await?;[Élevé] Si
check_workspace()échoue, le?abandonneSandboxaprès le démarrage du conteneur, maisContainern'a pas deDropcapable de supprimer les ressources asynchrones. Le conteneur, le réseau et l'image restent donc potentiellement sur le daemon à chaque échec de vérification. Nettoyer explicitement le conteneur avant de retourner l'erreur, avec conservation de l'erreur initiale même si le nettoyage échoue.Error while reviewing: the sandbox workspace
/workspaces/heraldis empty: the container daemon does not see the cloneReview Feedback
3 issues found.
Summary
La fonctionnalité est globalement structurée, mais la vérification lexicale des chemins permet de contourner la confinement via des liens symboliques, et la désactivation de SELinux affaiblit directement la sandbox. La gestion du nettoyage doit également être rendue réellement best-effort afin d'éviter l'accumulation de containers, réseaux et images orphelins.
Cost: $0.03395993
@@ -0,0 +78,4 @@////// La suppression du réseau et de l'image est best-effort : ils peuvent déjà être absents.pub async fn remove(&self) -> Result<(), ContainerError> {self.runtime.remove_container(&self.name).await?;La méthode quitte immédiatement si
remove_containeréchoue, alors que sa documentation indique que le réseau et l'image doivent également être supprimés en best-effort. Dans ce cas, l'image et le réseau restent orphelins, etSandbox::cleanupne peut pas récupérer ces ressources. Il faut tenter les suppressions même après l'échec du container, puis retourner une erreur agrégée ou la première erreur rencontrée.@@ -0,0 +122,4 @@// est refusé (EACCES), ce qui fait échouer tous les outils de la// sandbox. C'est le compromis inverse de l'alternative `:Z` sur le// montage, qui re-labellise le clone et garde le confinement SELinux.security_opt: Some(vec![String::from("label=disable")]),label=disabledésactive explicitement le confinement SELinux du container. Pour une sandbox qui exécute du code provenant d'une PR non fiable, cela élargit fortement l'impact d'une évasion ou d'une vulnérabilité du runtime. Il faudrait privilégier un montage correctement relabellisé (:Z/équivalent) ou rendre cette option explicite et désactivée par défaut, avec une justification claire du niveau de risque.@@ -0,0 +193,4 @@let workspace = sandbox.workspace_folder();let candidate = Path::new(workspace).join(path);let normalized =La normalisation est uniquement lexicale et ne résout pas les liens symboliques. Un dépôt contrôlé par l'auteur de la PR peut donc contenir un lien tel que
secret -> /proc/self/environousecret -> /etc/...;read_filetransmettra ensuite ce chemin àcatet permettra de lire des fichiers hors du workspace. Il faut résoudre et valider la cible réelle dans le container, ou refuser les liens symboliques avant toute lecture.Review Feedback
3 issues found.
Summary
La structure générale de la sandbox et les outils argv sont bien séparés, mais l'isolation annoncée n'est pas suffisamment garantie : la configuration du daemon peut être ignorée, le contexte de build peut sortir du clone et le chemin de workspace peut être manipulé pour lire tout le filesystem du container. Ces points doivent être corrigés avant de considérer le sandboxing comme fiable.
Cost: $0.06976124
@@ -0,0 +37,4 @@std::env::var("DOCKER_HOST").unwrap_or_else(|_| String::from(DEFAULT_ENDPOINT));Ok(Self {docker: Docker::connect_with_defaults()?,endpointlitDOCKER_HOST, mais le client Bollard est créé avecconnect_with_defaults()sans utiliser cette valeur. Ainsi, le daemon configuré viaDOCKER_HOST(notamment le socket Podman rootless documenté) risque d'être ignoré, tandis que les logs indiquent un endpoint différent de celui réellement utilisé. Il faut construire le client à partir de l'endpoint résolu, ou vérifier que l'API utilisée prend effectivement en chargeDOCKER_HOST.@@ -0,0 +59,4 @@.parent().ok_or_else(|| ParseError::InvalidDevContainerPath(devcontainer_path.clone()))?;let container_file_path = base_dir.join(schema.build.dockerfile);Le chemin
build.dockerfilefourni par une pull request est concaténé sans normalisation ni vérification qu'il reste dans le dépôt. Une valeur comme../../...peut faire choisir un Dockerfile situé hors du clone ; comme son parent devient ensuite le contexte envoyé au daemon, cela peut empaqueter des fichiers d'autres sandboxes ou du système dans le contexte de build. Résolvez le chemin puis imposez qu'il soit situé sous la racine du dépôt avant de l'utiliser.@@ -0,0 +216,4 @@let normalized =normalize(&candidate).with_context(|| format!("path `{path}` escapes the workspace"))?;if !normalized.starts_with(workspace) {La vérification de confinement dépend entièrement de
workspace, qui est contrôlé par ledevcontainer.jsonde la pull request. Une PR peut définirworkspaceFolderà/, après quoistarts_with(workspace)autorise pratiquement tout le système de fichiers du container et les outils peuvent lire des fichiers hors dépôt. Le workspace doit être validé et forcé sous un répertoire dédié au dépôt, indépendamment de la configuration non fiable.Review Feedback
6 issues found.
Summary
La fonctionnalité de sandbox présente plusieurs problèmes importants : la configuration DOCKER_HOST documentée n'est pas effectivement utilisée, les contextes de build standards des devcontainers ne sont pas supportés, et workspaceFolder permet de contourner la restriction des outils de lecture. Le traitement des erreurs de build et le nettoyage des ressources doivent également être durcis, et la suppression de .dockerignore expose inutilement le contexte de build.
Cost: $0.08248642
@@ -1,4 +0,0 @@target/La suppression de
.dockerignorefait que le contexte envoyé lors debuildah budcontient désormais notamment.git, les répertoires de build et le fichier.envlocal. Même si le Containerfile ne le copie pas explicitement, ce contexte est transmis au moteur de build et peut contenir des secrets ou devenir inutilement volumineux. Il faut conserver un.dockerignoreexcluant au minimum.git,.env*,targetet les fichiers locaux.@@ -0,0 +78,4 @@////// La suppression du réseau et de l'image est best-effort : ils peuvent déjà être absents.pub async fn remove(&self) -> Result<(), ContainerError> {self.runtime.remove_container(&self.name).await?;Le nettoyage s'arrête immédiatement si
remove_containeréchoue, et les suppressions du réseau et de l'image ne sont alors jamais tentées. Une course, un conteneur déjà supprimé ou une erreur transitoire peut donc laisser systématiquement le réseau et l'image derrière lui. Il faut tenter les trois nettoyages indépendamment, puis agréger ou retourner l'erreur pertinente.@@ -0,0 +34,4 @@/// Se connecte au daemon désigné par `DOCKER_HOST`, ou au socket local par défaut.pub fn connect() -> Result<Self, ContainerError> {let endpoint =std::env::var("DOCKER_HOST").unwrap_or_else(|_| String::from(DEFAULT_ENDPOINT));La valeur de
DOCKER_HOSTest seulement stockée dansendpointpour les logs :Docker::connect_with_defaults()ne l'utilise pas. Ainsi, la configuration documentée pour Podman ou pour un socket Docker non standard est ignorée et le runtime tente toujours sa connexion par défaut. Il faut construire le client Bollard avec l'endpoint lu dans l'environnement, ou supprimer cette configuration trompeuse.@@ -0,0 +108,4 @@while let Some(info) = stream.next().await {let info: BuildInfo = info?;if let Some(message) = info.error_detail.and_then(|detail| detail.message) {Le flux de build n'est considéré en échec que lorsque
error_detail.messageest renseigné. L'API Docker peut aussi fournir l'erreur dans le champerrordeBuildInfo; dans ce cas la méthode retourneOk(())alors que l'image n'a pas été construite. Il faut traiter les deux champs et préserver le message d'erreur le plus utile.@@ -0,0 +22,4 @@pub struct DevContainerSchema {#[serde(default)]pub name: Option<String>,pub build: DevContainerBuildSchema,Le schéma ne prend en charge que
dockerfileetargs, puisDevContainer::buildutilise systématiquement le répertoire du Dockerfile comme contexte. La spécificationdevcontainer.jsonpermet pourtant de définirbuild.context(souvent la racine du dépôt) ; les Dockerfile qui fontCOPY . ...ou qui utilisent un contexte distinct échoueront ou ne verront pas les fichiers attendus. Il faut modéliser et résoudre le contexte de build, avec une validation empêchant qu'il sorte du dépôt.@@ -0,0 +216,4 @@let normalized =normalize(&candidate).with_context(|| format!("path `{path}` escapes the workspace"))?;if !normalized.starts_with(workspace) {La vérification ne protège pas réellement la frontière du dépôt lorsque
workspaceFoldervient du dépôt non fiable. Une valeur comme/ou/tmppasse la normalisation et permet aux outils de lire n'importe quel chemin du conteneur, contrairement au contrat annoncé (« confined to the repository workspace »). Il faut imposer un workspace absolu dédié et vérifier qu'il s'agit bien du répertoire de dépôt, plutôt que de faire confiance àworkspaceFolderfourni par la PR.Review Feedback
3 issues found.
Summary
La PR améliore nettement l'isolation en ignorant les
runArgsnon fiables, en coupant le réseau après les hooks, en bornant les diffusions API et en ajoutant une boucle d'agent avec des outils en lecture seule. Il reste toutefois une échappée de chemin par comparaison de chaînes, une collecte de sortie non bornée avant troncature et un nettoyage de ressources interrompu au premier échec.Cost: $0.04950894
@@ -0,0 +78,4 @@////// La suppression du réseau et de l'image est best-effort : ils peuvent déjà être absents.pub async fn remove(&self) -> Result<(), ContainerError> {self.runtime.remove_container(&self.name).await?;[performance] Si
remove_containeréchoue, le?quitte immédiatement la méthode et le réseau ainsi que l'image ne sont jamais supprimés. Après un daemon indisponible, un container déjà supprimé ou une erreur transitoire, chaque sandbox peut donc laisser des ressources persistantes. Effectuer le nettoyage du réseau et de l'image même lorsque la suppression du container échoue, puis retourner l'erreur.@@ -0,0 +222,4 @@)));};let mut stdout = String::new();[performance] La collecte de la sortie d'un exec accumule toute la sortie dans deux
Stringsans limite. La limitation à 32 KiB dans l'agent intervient seulement après le retour de cette fonction ; unread_filesur un gros fichier ou ungreptrès bavard peut donc consommer une quantité arbitraire de mémoire avant d'être tronqué. Il faut borner la sortie pendant la lecture, ou interrompre l'exec dès que la limite est atteinte.@@ -0,0 +216,4 @@let normalized =normalize(&candidate).with_context(|| format!("path `{path}` escapes the workspace"))?;if !normalized.starts_with(workspace) {[security] La vérification
normalized.starts_with(workspace)compare des chaînes et ne respecte pas les frontières de composants. Avec un workspace/workspaces/repo, un chemin comme../repo-secretsdevient/workspaces/repo-secretset passe ce test, ce qui permet aux outils de lire un répertoire voisin dans le container. Utiliser une comparaison de chemins basée sur les composants, par exemplestrip_prefix(workspace).is_ok()après normalisation.