Aller au contenu

Authentification et durcissement

Le miroir web est conçu pour des réseaux de confiance. Le durcissement ci-dessous réduit la surface mais ne remplace pas les contrôles réseau — bindez sur 127.0.0.1 ou définissez un jeton avant de l’exposer à quoi que ce soit que vous ne contrôliez pas pleinement.

Définissez webMirrorAuthToken dans Réglages → Réseau → Jeton. Une fois défini, chaque requête doit le présenter :

  • Chaîne de requête : ?t=<token> — le plus simple pour les liens <a href>.
  • En-tête : Authorization: Bearer <token> — préféré pour les clients programmatiques.

La comparaison est en temps constant via timingSafeEqualStr. Le jeton ne peut pas être attaqué par force brute octet par octet via du sondage de latence.

Si le jeton est incorrect :

  • Les requêtes HTTP reçoivent un 401 Unauthorized sans corps.
  • Les upgrades WebSocket sont rejetés avant la fin du handshake.

?t=… est nettoyé de l’URL après la première authentification

Section intitulée « ?t=… est nettoyé de l’URL après la première authentification »

Quand la page charge depuis un lien ?t=<token>, le navigateur capture le jeton au chargement du module puis le retire de window.location via history.replaceState dès que la première ouverture WebSocket réussit. Les reconnexions continuent à s’authentifier parce que le jeton survit dans la portée du module — seule l’URL est nettoyée. Effet net : le jeton ne peut pas fuir via partage d’écran, la pile back/forward, le copier-coller de l’URL, ou les en-têtes Referer des liens sortants.

Si la connexion initiale échoue (401, erreur réseau), l’URL est laissée intacte intentionnellement pour que l’échec reste débuggable — vous voyez encore le jeton fourni dans la barre d’adresse et pouvez le copier/éditer.

Les upgrades WebSocket sont rejetés lorsque l’en-tête Origin est défini et ne correspond pas à Host. Cela empêche les navigateurs sur un autre site de détourner la connexion via une requête WS forgée.

Les clients natifs qui omettent Origin (par exemple curl, ht, des clients WebSocket personnalisés) se connectent toujours — seules les requêtes provenant d’un navigateur portent Origin, et un navigateur ne peut pas le falsifier.

Chaque trame client → serveur est plafonnée en taille :

  • 256 KiB par enveloppe (le wrapper JSON).
  • 64 KiB par charge utile stdin (après dépaquetage de l’enveloppe).

Les trames trop volumineuses sont silencieusement abandonnées ; la connexion reste ouverte.

Un token bucket limite chaque connexion à 256 trames par seconde. Les trames en excès sont silencieusement abandonnées. Suffisamment généreux pour que la frappe normale et les rafales de redimensionnement passent ; suffisamment serré pour qu’un client mal intentionné ne puisse pas inonder le serveur.

Les enveloppes surfaceResizeRequest sont validées :

  • cols borné à [10, 500].
  • rows borné à [4, 500].
  • Les valeurs non analysables sont entièrement rejetées (pas de valeur par défaut de repli).

Les jetons de reprise sont des chaînes hexadécimales 128 bits issues de crypto.getRandomValues. Aucune structure prévisible — deviner un id de reprise valide revient à attaquer par force brute 128 bits d’entropie.

Le bind par défaut est 0.0.0.0 (toutes les interfaces). Réglez webMirrorBind sur 127.0.0.1 pour rendre le miroir joignable uniquement depuis le portable lui-même — utile lorsque vous voulez l’URL mais pas l’exposer sur le LAN.

Depuis la v0.3.161, cette adresse de bind est honorée au démarrage automatique également — pas seulement lors de l’activation manuelle du miroir (voir la note sous Authentification par jeton).

La CLI ht communique avec l’application via un socket Unix. Le fichier socket est créé avec le mode 0600, de sorte que seul le même utilisateur système peut l’ouvrir. C’est la barrière de base.

Une seconde couche est arrivée en v0.3.163 et est activée par défaut depuis la v0.4.12 : Réglages → Réseau → « Exiger un jeton de socket RPC ». Le socket exige un jeton par démarrage (per-boot) pour les commandes qui modifient l’état — frappe dans les panneaux, arrêt de processus, création de splits, installation d’extensions, et autres. Les diagnostics en lecture seule restent ouverts même sans le jeton, afin qu’une incohérence de jeton reste diagnosticable :

  • ht version
  • ht identify
  • ht doctor
  • arbre / lecture d’écran et autres commandes d’inspection

Le ht fourni, les ponts pi / Claude et le SDK d’extensions lisent et présentent le jeton automatiquement — rien à configurer sur ces chemins, ce qui a permis d’inverser le défaut sans casser les usages internes. Les anciennes installations ht externes perdent les commandes de modification jusqu’à leur mise à jour (leurs diagnostics en lecture seule continuent de fonctionner) ; ne désactivez le réglage que pour un client tiers qui parle directement le protocole socket sans envoyer __token.

Le jeton est écrit dans un fichier nommé socket.token (mode 0600) à côté du socket. Définissez la variable d’environnement HT_RPC_TOKEN_PATH pour remplacer ce chemin.

Modèle de menace — soyons explicites : il s’agit d’une défense en profondeur contre un processus opportuniste du même utilisateur qui parlerait JSON-RPC à un chemin de socket bien connu. Ce n’est pas une barrière de sécurité ferme — n’importe quel processus du même utilisateur peut aussi lire le fichier de jeton 0600. Cela élève le niveau d’exigence ; cela ne scelle rien.

  • L’écoute réseau. Le fil utilise un WebSocket en clair, pas TLS. Quiconque sur le LAN avec une capture de paquets voit stdout. Utilisez un VPN ou tenez-vous-en à la loopback pour les flux sensibles.
  • L’élévation de privilèges à l’intérieur de τ-mux. Un miroir authentifié dispose d’un accès PTY complet — comme s’il était assis devant le portable. Le jeton est la barrière.
  • Les exploits de navigateur. Le miroir sert l’innerHTML provenant des panneaux HTML sideband. Si vous rendez du HTML contrôlé par un attaquant, vous êtes exposé.
  • src/bun/web/server.ts — logique d’authentification, d’origine, de limitation de débit et de plafonnage de taille.