Retour à tous les articles
Publié le · par Renaud Deraison

La wordlist connaissait déjà votre répertoire personnel

F5 Labs a relevé 807 attaques et 32 000 événements en un mois contre des serveurs de développement Vite exposés, au moyen d'une astuce de chaîne de requête qui passe tout droit devant la deny-list de Vite elle-même. Les scanners ne cherchent pas votre application. Ils demandent /home/ubuntu/.aws/credentials par chemin absolu, et /proc/self/environ, et terraform.tfstate. Dans un workspace Bromure Agentic Coding, ce port fait face à un switch privé, et le fichier que réclame la wordlist ne contient aucune clé.

Cet attaquant ne vous envoie ni paquet ni archive. C'est votre propre serveur de développement qui écoute, et un inconnu lui réclame des fichiers par leur nom. La machine qui répond décide de ce qu'il obtient.

Vous demandez à l'agent de lancer le frontend pour pouvoir y jeter un œil. Il tape npm run dev, ouvre le port, charge la page, et vous retournez à la relecture du diff. Quelque part dans cette boucle, dans un docker-compose.yml ou dans un drapeau --host que vous aviez ajouté il y a des semaines pour rendre l'aperçu accessible depuis votre téléphone, le serveur s'est lié à toutes les interfaces plutôt qu'à la boucle locale. Douze minutes plus tard, quelque chose qui se présente comme Googlebot demande à votre serveur de développement /@fs/../.env?raw?? et reçoit un HTTP 200.

F5 Labs a publié les chiffres le 11 septembre. Sur un seul mois de données de capteurs, en août 2026, leurs honeypots ont enregistré 807 attaques regroupées par session et environ 32 000 événements bruts visant des serveurs de développement Vite exposés, contre une base de 1 732 événements sur les trois mois précédents. Soit dix-huit fois le trafic, d'un mois sur l'autre, pour un seul bogue. BleepingComputer l'a couvert le 14 septembre.

Une chaîne de requête qui contourne la deny-list

Vite sert des fichiers du système de fichiers de l'hôte par une route interne appelée /@fs/, celle qui permet au serveur de développement de remettre à votre éditeur un module situé hors de la racine du projet. Parce que cette route peut atteindre n'importe quoi, Vite livre une deny-list, server.fs.deny, pour bloquer les cibles évidentes : fichiers .env, certificats, sources privées.

CVE-2026-39364, publiée le 7 avril, permet à un attaquant de sauter cette deny-list en décorant la requête d'une chaîne de requête. F5 décrit le mécanisme :

Le serveur traite la requête, normalise le chemin, puis supprime ou interprète mal la chaîne de requête pendant la validation d'accès, si bien que le contrôle server.fs.deny n'est pas déclenché.

Le serveur n'applique pas le filtrage par deny-list et sert le fichier visé avec une réponse HTTP 200.

Le bogue affecte Vite 7.1.0 à 7.3.2, et les 8.x avant 8.0.5. F5 a intercepté ces formes de requête dans la nature, chacune assez courte pour tenir sur une ligne : GET /@fs/.env?raw??, GET /@fs/../.env?raw??, GET /@fs/..%252f..%252f..%252f..%252froot/.env?raw?? et GET /@fs/..%252f..%252f..%252f..%252fproc/self/environ?raw??. D'autres variantes du même outillage utilisent ?import&raw, ?import&url&inline, ?inline&import et ?raw?import.

Ces astuces de paramètres portent leurs propres numéros de CVE : CVE-2025-30208, CVE-2025-31125 et CVE-2024-45811, trois contournements plus anciens de @fs toujours chargés dans les mêmes scanners. Celui qui pilote la flotte de scan n'a pas réoutillé pour un nouveau bogue. Il a ajouté une ligne de plus à un fichier qu'il possédait déjà.

une requête, un fichierscannerGET /@fs/../.env?raw??User-Agent: Googlebot/2.1serveur de dev vitechemin normalisé pour le service/@fs/ → lecture disqueserver.fs.denyrequête mal gérée, contrôle sautéfichier lu et renvoyéaucune auth, aucune session, aucun log luHTTP 200le contenu du fichier,dans le corps de la réponsedéjà dans les mêmes scannersCVE-2025-30208 · ?raw??CVE-2025-31125 · ?inline&importCVE-2024-45811 · ?import&raw
Le contournement en un seul échange. La route /@fs/ de Vite est censée servir les fichiers du projet, et server.fs.deny est censée la tenir à l'écart des secrets. Ajouter une chaîne de requête finale, dont ?raw?? est la forme courante, fait normaliser le chemin pour le service mais mal gérer le contrôle d'accès : la deny-list reste muette et le fichier revient avec un 200. La traversée de chemin dans la même requête sort du projet, et c'est ainsi qu'un serveur de développement frontend finit par lire /proc/self/environ.

La wordlist est une carte de la machine d'un développeur

Lisez ce que réclament les scanners, et voyez à quel point cela concerne peu votre application.

Ils demandent .env, .env.local, .env.production, .env.development et .env.staging. Ils demandent terraform.tfstate, terraform.tfvars, .terraform/terraform.tfstate, serverless.yml et .serverless/serverless-state.json. Ils demandent .azure/credentials et .azure/accessTokens.json. Ils demandent /etc/passwd, /proc/1/environ, /proc/self/cwd/.env et /proc/self/environ. Ce dernier chemin contient le bloc d'environnement du processus du serveur de développement lui-même, là où atterrit une API_KEY exportée par votre shell.

Et ensuite ils demandent des identifiants AWS en parcourant la liste des répertoires personnels sous lesquels le processus d'un développeur pourrait tourner :

/root/.aws/credentials, /home/ec2-user/.aws/credentials, /home/ubuntu/.aws/credentials, /home/node/.aws/credentials, /home/www-data/.aws/credentials, /home/admin/.aws/credentials, /home/debian/.aws/credentials, /var/www/.aws/credentials, /usr/src/app/.aws/credentials, /app/.aws/credentials. Puis .aws/config, .aws/credentials.backup, .aws/credentials.bak, .aws/sso/cache/, rootkey.csv, aws-exports.js et amplifyconfiguration.json.

Ces chemins dressent l'inventaire d'une machine de développeur, énumérée par nom d'utilisateur, et Vite n'est que la porte. Le bogue est accessoire ; trois bogues plus anciens de la même route voyagent dans les mêmes requêtes. Les opérateurs parient qu'un processus à l'écoute sur une adresse routable tourne sous un utilisateur dont le répertoire personnel contient de vraies clés.

Le trafic est déguisé pour survivre à un coup d'œil distrait aux journaux. F5 a relevé des en-têtes User-Agent falsifiés alternant entre Googlebot/2.1, ClaudeBot/1.0, GPTBot/1.4, PerplexityBot/1.0, OAI-SearchBot/1.3 et Amazonbot/0.1, plus des valeurs X-Forwarded-For et X-Real-IP falsifiées pour franchir les allow-lists d'adresses IP. Les sources se situent dans les plages 34.x et 35.x de Google Cloud, réparties sur plusieurs régions, menées par les États-Unis à 17 297 événements, puis la Belgique à 4 407 et les Pays-Bas à 4 011. Ce mois-ci, une ligne de votre journal d'accès qui se prétend un crawler IA est une preuve bien faible qu'un crawler l'a envoyée.

Deux des recommandations de F5 sont des questions d'architecture

F5 conclut par cinq conseils. Trois sont ordinaires et justes : corriger vers 7.3.2 ou 8.0.5, filtrer /@fs/ en bordure, vérifier les crawlers par DNS inverse plutôt que de faire confiance à l'en-tête. Les deux autres décrivent une posture à tenir plutôt qu'une tâche à terminer.

Veillez à ce que les serveurs de développement ne se lient pas à des interfaces externes. Auditez les configurations Docker compose, les règles d'ingress Kubernetes et les groupes de sécurité cloud.

Faites tourner les secrets exposés : si un serveur de développement Vite non corrigé était joignable depuis des réseaux externes en août 2026, considérez les variables .env locales, les identifiants AWS, les jetons d'accès Azure et les fichiers d'état Terraform comme potentiellement compromis.

Le premier vous demande de tenir une promesse sur chaque port, dans chaque fichier compose, à travers chaque branche, aussi longtemps que vit le projet, à un moment où ce qui tape npm run dev est souvent un agent plutôt que vous. Le second demande ce que vous faites ensuite, et répond : faites tourner ce que la machine pouvait voir.

Ces deux points deviennent plus faciles si le port s'ouvre ailleurs, et si les fichiers que nomme la wordlist ne contiennent rien qui vaille d'être renouvelé.

Où un workspace Bromure place le port

Bromure Agentic Coding exécute les agents de code à l'intérieur d'une VM Linux virtualisée par le matériel sur votre Mac, avec les contrôles de sécurité du côté hôte de cette frontière. Deux d'entre eux répondent à cette campagne.

Le serveur de développement se lie à l'intérieur d'un switch privé. Dans le mode NAT par défaut, chaque VM de workspace se rattache à un unique switch logiciel L2 à l'échelle du processus, multiplexé sur une seule interface vmnet, sur un sous-réseau privé. Ce sous-réseau est 192.168.64.0/24 à moins que votre propre LAN ne l'utilise déjà, Bromure y fait tourner son propre serveur DHCP, et chaque workspace conserve une MAC déterministe et un bail stable. Le manuel en énonce la conséquence : votre Mac peut joindre les VM, votre LAN physique ne les voit pas, et les connexions entrantes depuis ailleurs sont impossibles à moins de publier vous-même un service. Un agent qui se lie à 0.0.0.0 dans cette VM s'est lié à chacune des interfaces qu'il possède, et chacune d'elles fait face à un switch qui commence et finit sur votre portable. Vous n'avez aucun groupe de sécurité à auditer, parce que vous n'avez aucune route entrante à sécuriser.

Vous pouvez quand même voir ce qui écoute, et c'est une liste, pas un audit. Le tableau de bord du workspace porte une carte Listening Ports qui montre chaque socket joignable de l'extérieur dans l'invité, sous forme de point de terminaison <IP-VM>:<port> avec copie en un clic. Cela transforme la recommandation n° 2 de F5 en une carte que vous consultez d'un coup d'œil, rafraîchie depuis l'invité toutes les secondes et demie, au lieu d'un audit que vous planifiez. Quand vous voulez vraiment montrer un aperçu au monde, un service vraisemblablement HTTP reçoit un bouton globe qui publie ce service-là par un tunnel rapide Cloudflare, derrière une boîte de dialogue de consentement unique. Vous exposez un service en cliquant dessus, service par service, sur l'hôte, plutôt qu'en laissant vivre un drapeau dans un fichier compose.

Vous gardez la raison pour laquelle vous aviez atteint --host au départ. Le sidecar Chromium jetable partage le même segment L2 que la VM du workspace, si bien que le navigateur intégré charge le serveur de développement de l'agent à l'adresse de la VM. Pas sur localhost, puisque le navigateur tourne sur une machine distincte.

Si d'autres machines doivent réellement joindre la VM, le mode Bridged la raccorde à votre LAN physique. Vous réglez cela par workspace dans l'éditeur de workspace, sur l'hôte, et il retombe sur le NAT si l'interface est indisponible au lancement. Vous faites ce choix dans un panneau, pas dans un fichier de configuration que l'agent peut modifier.

un poste de dev exposéune adresse routable, un vrai utilisateur, de vrais fichiersGET /@fs/../.env?raw??arrive depuis 34.x, 200 OK/home/ubuntu/.aws/credentialsun identifiant de clé et un secret vivant/proc/self/environchaque jeton exporté par le shellensuiterenouveler les clés, les jetons, le fichier d'état,et deviner la fenêtredans un workspaceun switch privé, un home plein de leurresaucune route entrante pour arrivervmnet NAT · 192.168.64.0/24carte Listening Portschaque socket ouvert, et un globe pour en publier unla wordlist tombe sur des leurressk-ant-api03-brm-… · ghp_… · credential_processensuiteréinitialiser le disque si vous voulez,et ne rien renouveler
Le même scan, contre deux machines. Sur un poste de développement à adresse routable, la requête atteint un processus tournant sous un vrai utilisateur, et chaque chemin de la wordlist résout vers un vrai fichier : .env avec des clés vivantes, ~/.aws/credentials avec un secret, /proc/self/environ avec les jetons exportés par le shell. Dans un workspace Bromure Agentic Coding, le port fait face à un switch vmnet privé sans aucune route entrante. Publiez le service exprès et la wordlist atterrit quand même sur des leurres, et sur un ~/.aws/config qui nomme un assistant au lieu de contenir une clé.

Et si vous publiez le service exprès

Parfois vous voulez vraiment l'aperçu sur internet, pour un client ou un collègue. Alors cliquez sur le globe, ouvrez le tunnel, et laissez les scanners le trouver. Descendez la wordlist, entrée par entrée, et regardez ce qui revient.

.env et /proc/self/environ renvoient l'environnement du workspace, et chaque identifiant qu'il contient est un leurre. Bromure dérive chaque valeur de substitution de la vraie valeur plus un sel de 32 octets propre à l'installation, par HKDF-SHA256, en conservant la forme qu'attend un validateur côté client : une clé Anthropic se lit sk-ant-api03-brm-…, un jeton GitHub est ghp_ suivi de 36 caractères, GitLab est glpat- suivi de 20. Bromure écrit ces leurres dans les variables d'environnement et dans ~/.git-credentials, ~/.docker/config.json, ~/.kube/config et les configurations MCP. Les vraies valeurs n'entrent jamais dans la VM ; elles restent chiffrées sur le Mac, et un proxy côté hôte substitue chacune d'elles sur le fil au dernier moment, cantonnée à l'hôte de destination auquel elle appartient.

/home/ubuntu/.aws/credentials est l'entrée la plus aiguisée, parce que /home/ubuntu est bien le répertoire personnel d'une VM de workspace Bromure. Le scanner devine juste quant à l'utilisateur qui fait tourner le serveur. Le fichier n'est pourtant pas là. La configuration AWS de Bromure n'écrit aucun fichier d'identifiants ; elle écrit ~/.aws/config avec une ligne credential_process pointant vers un assistant qui sert, par une socket de l'hôte, votre véritable identifiant de clé d'accès associé à une fausse clé secrète de quarante caractères, et omet le jeton de session. Les SDK AWS, la CLI aws, terraform et boto3 reprennent tous cela d'eux-mêmes. Un scanner qui lit le fichier de configuration obtient le chemin d'un assistant qu'il ne peut pas appeler.

Prenez quand même la fausse clé secrète et vous aurez pris quelque chose d'inerte. L'invité signe ses requêtes avec ce leurre, et l'hôte retire la signature puis re-signe avec la vraie clé au moment de sortir. Une requête qui atteint AWS par n'importe quelle autre voie échoue avec InvalidSignatureException. La dernière recommandation de F5 est de traiter chaque secret local comme compromis et de le renouveler. La ligne correspondante dans la documentation de Bromure dit l'inverse : puisque seul le leurre a fuité, le véritable identifiant n'a jamais besoin d'être renouvelé.

Une entrée de la wordlist nomme un fichier qui vaut de l'argent bien réel : terraform.tfstate. Les fichiers d'état vivent là où vit le dépôt, si bien que votre liste de dossiers partagés décide de leur exposition. Les dossiers de l'hôte s'attachent à un workspace comme des montages virtiofs sous /home/ubuntu/<basename>, plafonnés à huit par workspace, et ce sont les seules parties du système de fichiers de votre Mac que la VM peut atteindre. Partagez le dépôt sur lequel l'agent travaille et rien d'autre, et la traversée trouve un répertoire au lieu d'un disque.

La plupart des histoires de sécurité pour développeurs cette année décrivent quelque chose qui arrive : un paquet, une archive, un fichier de documentation. Ici, rien n'arrive. À la place, 32 000 requêtes par mois demandent à un processus que vous avez lancé de lire des fichiers à voix haute à un inconnu, et l'issue tient à la machine qui fait tourner ce processus et à ce qui se trouve dans son répertoire personnel.

Mettez le serveur de développement de l'agent sur un switch où votre Mac est seul, et remplissez son répertoire personnel de leurres. Installez Bromure Agentic Coding, puis laissez les scanners demander.