Que fait lan-direct ?
Acheminez le trafic de l'application via le FAI pendant que vous, l'agent, restez sur le VPN.
Fonctionne aussi bien avec Claude Code qu'avec Codex - le mécanisme est une liaison par socket, qui n'a rien à voir avec le harnais.
L'invariant
Ne jamais modifier la table de routage, les paramètres proxy WinINET/système ou la configuration du client VPN. Uniquement :
- définir des variables d'environnement limitées à un processus enfant, ou
- passer des drapeaux de liaison par outil.
Les deux sont par socket et facultatifs, ils ne peuvent donc pas toucher à votre propre connexion. C'est tout l'intérêt de la compétence - si vous utilisez route add, vous l'avez cassée (voir la section Boucle d'échappement à la fin).
Ne suggérez pas de déconnecter le VPN. C'est le problème que cette compétence existe pour éviter.
Flux de travail
1. Détecter et vérifier
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\lan-detect.ps1
Sous Linux ou macOS, utilisez plutôt l'équivalent - même JSON, même contrat de code de sortie :
bash scripts/lan-detect.sh
Émet du JSON : lanIp, gateway, lanDns, subnet, tunnelActive, vpnAdapter,
directEgress, lanEgress, fakeIpDns, bypassWorks. Sort avec le code 0 uniquement lorsque bypassWorks
est vrai - il prouve le contournement avec deux sondes en direct plutôt que de l'inférer.
tunnelActive: false-> aucun VPN n'est actif. Dites-le et arrêtez-vous ; il n'y a rien à contourner.bypassWorks: falseavec un tunnel actif -> allez dans Dépannage ci-dessous. Ne continuez pas. Le trafic resterait silencieusement sur le VPN et vous signaleriez un faux succès.
2. Choisir le mécanisme
Backend Node (y compris Next.js) ? Ignorez le proxy - passez directement à la recette Node dans la section
Recettes par pile. Une variable d'environnement, pas de processus proxy, et c'est la seule chose qui atteint
le fetch natif.
Tout le reste : démarrez le proxy lié au LAN. Un processus serveur n'a pas de drapeau --interface, donc
c'est ainsi qu'il obtient une sortie FAI.
node scripts/lan-proxy.mjs --bind <lanIp> --port 8899 # exécutez en arrière-plan
node scripts/lan-proxy.mjs --bind <lanIp> --port 8899 --dns <lanDns> # si fakeIpDns: true
Il écoute uniquement sur 127.0.0.1 et enregistre chaque hôte qu'il tunnelise, afin que vous puissiez voir ce que l'application
appelait réellement.
3. Redémarrer le backend en le pointant vers celui-ci
HTTP_PROXY=http://127.0.0.1:8899
HTTPS_PROXY=http://127.0.0.1:8899
NO_PROXY=localhost,127.0.0.1,<subnet>
NO_PROXY est important : sans lui, les appels du backend à ses propres services locaux sont envoyés
travers le proxy pour aucune raison. (Les bases de données locales sur TCP - MySQL, Postgres, Mongo - ne sont pas HTTP
et ne sont pas affectées de toute façon.)
Définissez-les sur la commande de lancement du backend, pas globalement. Voir Recettes par pile ci-dessous -
HTTP_PROXY ne suffit pas pour plusieurs piles.
4. Tester et confirmer
Appelez le point d'accès dont l'appel sortant échouait. Confirmez les deux :
- l'appel réussit maintenant, et
- le journal du proxy affiche l'hôte qu'il a composé.
Un succès sans ligne de journal correspondante signifie que la requête n'est jamais passée par le proxy.
5. Nettoyer
Arrêtez le proxy, redémarrez le backend sans les variables d'environnement. Rien d'autre à annuler - aucun état global n'a été modifié.
pkill -f lan-proxy depuis Git Bash ne tue pas un node.exe Windows. Utilisez :
Get-CimInstance Win32_Process -Filter "Name='node.exe'" |
Where-Object { $_.CommandLine -like '*lan-proxy*' } |
ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
Une instance résiduelle est inoffensive, mais provoquera un échec du prochain lancement en raison d'un conflit de port.
Recettes par pile
| Pile | Comment lui fournir le proxy |
|---|---|
Python requests / httpx | variables d'environnement, telles quelles |
Go, .NET HttpClient | variables d'environnement, telles quelles |
| PHP CLI | libcurl lit http_proxy / https_proxy depuis l'environnement du processus |
| Node (y compris Next.js) | pas le proxy - utilisez le préchargement lan-bind.mjs, voir ci-dessous |
| PHP sous Apache/XAMPP | voir ci-dessous - le service n'hérite pas de votre shell |
| curl, wget, npm, ssh, git | ignorez le proxy, liez directement (voir Liaison directe ci-dessous) |
Node - utilisez le préchargement, pas le proxy
Le fetch natif s'exécute sur l'undici intégré de Node, qui ignore HTTP_PROXY sur Node <= 23
et n'accepte pas https.Agent. Vérifié sur Node 22.20 : les variables d'environnement simples, NODE_USE_ENV_PROXY=1
et --use-env-proxy ne font rien - le journal du proxy reste vide et le trafic sort silencieusement
via le VPN. Cela concerne Next.js, le SDK openai et tout ce qui utilise le fetch natif.
Ignorez complètement le proxy et utilisez le préchargement, qui lie chaque socket sortant dans le processus :
LAN_BIND=<lanIp> node --import "file:///<chemin-compétence>/scripts/lan-bind.mjs" script.js
LAN_BIND=<lanIp> NODE_OPTIONS="--import file:///<chemin-compétence>/scripts/lan-bind.mjs" npm run dev
<chemin-compétence> est le répertoire de base de cette compétence avec des barres obliques - normalement
C:/Users/<vous>/.claude/skills/lan-direct sous Claude Code, ou
C:/Users/<vous>/.agents/skills/lan-direct sous Codex. --import nécessite une file:// absolue
URL sur Windows ; un chemin Windows brut avec des antislashs échoue.
NODE_OPTIONS se propage aux workers forkés, donc il couvre les serveurs de développement Next.js et Vite.
Cela atteint le fetch natif, le SDK openai, axios, node-fetch, got et les simples
http/https en même temps - pas de dépendance, pas de processus proxy, pas besoin de HTTP_PROXY. Il ignore les
destinations de boucle locale, donc une base de données locale ou un serveur de développement continue de fonctionner.
Uniquement si le préchargement n'est pas disponible : Node >= 24 honore NODE_USE_ENV_PROXY=1 avec les
variables d'environnement, et axios les honore sur n'importe quelle version.
PHP sous Apache / XAMPP
SetEnv/PassEnv dans httpd.conf ne remplit que $_SERVER ; libcurl lit l'environnement du processus,
donc il ne les récupérera pas. Utilisez l'une des méthodes suivantes :
- démarrez Apache depuis un shell qui a déjà
http_proxy/https_proxyexportés (par exemplexampp\apache_start.bat), ou putenv('https_proxy=http://127.0.0.1:8899');avantcurl_init(), oucurl_setopt($ch, CURLOPT_PROXY, '127.0.0.1:8899');directement.
file_get_contents() n'honore rien de tout cela - il a besoin d'un contexte de flux avec 'proxy' => 'tcp://127.0.0.1:8899'.
Liaison directe
Aucun proxy nécessaire lorsque l'outil peut lier son propre socket :
| Outil | Drapeau |
|---|---|
| curl | --interface <lanIp> |
| wget | --bind-address=<lanIp> |
| npm | --local-address=<lanIp> |
| ssh | -b <lanIp> |
| Chromium / Playwright | aucun drapeau de liaison n'existe - utilisez le MCP navigateur sur le port 8900, voir ci-dessous |
MCP Navigateur / Playwright
Chromium n'a pas de drapeau de liaison source, donc un navigateur n'atteint le FAI que via le proxy.
Utilisez lan-browser.mjs : c'est un serveur MCP Playwright qui démarre et possède son propre proxy, donc le
navigateur ne peut jamais démarrer sans.
Il est livré avec cette compétence - scripts/install.ps1 l'enregistre :
powershell -NoProfile -ExecutionPolicy Bypass -File <chemin-compétence>\scripts\install.ps1
Quels hôtes utilisent le FAI provient de trois sources fusionnées, donc c'est presque jamais une
réinstallation : lan-hosts.txt dans le répertoire de la compétence (valeurs par défaut livrées, remplacées lors du git pull),
~/.lan-direct-hosts (les vôtres, jamais écrasés), et --lan-hosts a,b au lancement.
Une règle par ligne, commentaires # autorisés, correspondance par suffixe.
Lorsqu'un utilisateur souhaite qu'un autre hôte soit routé via le FAI, ajoutez une ligne à ~/.lan-direct-hosts et
dites-lui de redémarrer le navigateur - ne modifiez pas lan-hosts.txt, que la prochaine mise à jour écrasera.
Si l'utilisateur demande de l'enregistrer manuellement, notez que -- doit survivre au liant de paramètres de
PowerShell - dispersez les arguments sous forme de tableau, ou exécutez-le depuis bash :
# depuis un clone
claude mcp add playwright-split --scope user -- \
node <chemin-compétence>/scripts/lan-browser.mjs --lan-hosts ".ir"
# depuis npm - survit au déplacement du checkout, donc préférez-le lorsque l'utilisateur l'a installé
claude mcp add playwright-split --scope user -- npx -y lan-direct browser
Autres sous-commandes CLI, lorsque le package est installé : lan-direct detect choisit le bon
script de détection pour la plateforme, et lan-direct route <host> répond "cet hôte passerait-il par le
FAI ou le tunnel ?" sans démarrer de navigateur. Utilisez celle-ci lorsqu'un utilisateur signale qu'un site ne se charge pas
- une mauvaise règle et un site en panne se ressemblent de l'extérieur.
Le serveur détecte l'IP LAN, refuse de démarrer si lan-detect indique que le contournement est cassé,
réutilise un proxy existant si un autre écoute déjà, et arrête ce qu'il a démarré lorsque le MCP se ferme.
Ses propres journaux vont sur stderr ; stdout est laissé propre pour JSON-RPC.
Il utilise le port 8900, délibérément pas le 8899 utilisé pour les tests backend ci-dessus. La réutilisation se fait par port uniquement, donc partager le nombre permettrait à un proxy backend qui lie tout de capturer silencieusement le navigateur et d'acheminer tout son trafic via le FAI.
La navigation mixte fonctionne dans un seul navigateur. --lan-hosts fait décider le proxy par hôte :
les hôtes correspondants sortent via le FAI, tout le reste reste non lié et emprunte le tunnel. Ainsi
"vérifier hoosha et youtube" ne nécessite aucun changement d'outil - hoosha sort via le FAI, youtube via
le VPN, même session. Le journal du proxy montre la décision sur chaque ligne :
CONNECT lan hoosha.com:443
CONNECT vpn youtube.com:443
Gardez la liste précise : un hôte domestique manquant reçoit le tunnel et échoue avec une réponse vide, et un hôte étranger ajouté par erreur reçoit le FAI et peut être filtré. Lorsqu'un site échoue d'une manière qui ressemble à une connexion morte, vérifiez quelle route le journal a choisie avant de supposer que le site est en panne.
Pourquoi une liste explicite plutôt qu'une auto-détection : se connecter à un hôte bloqué via le
tunnel réussit au niveau TCP (mesuré : connect@14ms) et ne meurt qu'environ 860 ms plus tard, après
que le proxy a déjà répondu 200 Connection Established. Revenir en arrière signifierait
mettre en mémoire tampon et rejouer la poignée de main TLS et payer ce délai sur chaque requête domestique.
Playwright route la boucle locale via le proxy. Chrome contourne normalement localhost, mais
Playwright remplace ce comportement lorsqu'un proxy est défini, donc http://127.0.0.1:3000 arrive à
lan-proxy.mjs plutôt que d'aller directement. Vérifié dans le journal du proxy. La garde de boucle locale du proxy
est ce qui maintient les serveurs de développement locaux accessibles - sans elle, chacune de ces requêtes
échoue avec EADDRNOTAVAIL. N'ajoutez pas --proxy-bypass=<-loopback> ; cela ne change rien ici
et ne fait que brouiller l'image.
Dépannage
directEgress égal à lanEgress avec un tunnel actif. Le VPN applique un kill-switch
ou un routage strict (un filtre WFP qui bloque les sockets liés). Corrigez-le dans le client VPN, pas
autour : désactivez le routage strict, ou ajoutez une règle de routage direct/de contournement pour les hôtes dont vous avez
besoin. nekoray/sing-box : désactivez strict_route, ou ajoutez une règle de sortie direct.
Sous Linux, cela peut également se produire sans kill-switch du tout : une adresse source seule ne remplace pas
la table de routage là comme elle le fait sous Windows, donc le routage politique maintient le trafic dans
le tunnel. La correction est une règle envoyant le trafic de l'IP LAN via la passerelle physique -
ip rule add from <lanIp> table <n> plus une route par défaut dans cette table. Cela
nécessite root et est un changement à l'échelle du système, alors expliquez-le et laissez l'utilisateur décider. Tout le
reste dans cette compétence reste sans privilèges et limité au processus.
fakeIpDns: true. Le résolveur du système d'exploitation renvoie des espaces réservés 198.18.x.x
qui n'ont de sens qu'à l'intérieur du tunnel. Passez --dns <lanDns> au proxy pour qu'il résolve via
le DNS propre du LAN.
La sonde de sortie ne renvoie rien. Aucune connectivité vers l'hôte de la sonde. Vérifiez le lien, réexécutez.
Boucle d'échappement
Si un backend ne peut vraiment pas être proxé (un binaire compilé sans support proxy), la seule option restante est une route statique spécifique à l'hôte :
route add <targetIp> mask 255.255.255.255 <gateway> metric 1 # nécessite admin
route delete <targetIp> # annuler, toujours
C'est global. Cela retire tous les processus du tunnel pour cette destination - y compris vous, si vous contactez jamais le même hôte. Nécessite une élévation, persiste jusqu'à sa suppression, et est facile à oublier. Demandez avant de l'utiliser, et supprimez-le dans la même session.