Aller au contenu
OmniRoute source

Socket.dev / supply-chain finding attestation (Français)

§1 — Installation de l’autorité de certification racine pour l’interception MITM (77484.js)

Section intitulée « §1 — Installation de l’autorité de certification racine pour l’interception MITM (77484.js) »

Fichiers sources :

  • src/mitm/cert/install.ts — fonctions publiques installCert() / uninstallCert(), et fonctions propres à chaque plateforme installCertWindows/Mac/Linux.
  • src/mitm/systemCommands.ts — utilitaires partagés execFile / spawn / PowerShell utilisés par les chemins d’installation.

Déclencheur : l’utilisateur clique sur « Activer le proxy MITM » dans le tableau de bord local à l’adresse /dashboard/cli-tools/mitm. Cette route est accessible uniquement via l’interface de bouclage — voir la règle stricte no 17 dans CLAUDE.md et src/server/authz/routeGuard.ts::isLocalOnlyPath(). Un JWT divulgué par l’intermédiaire d’un tunnel ne peut pas déclencher ce chemin de code.

Opérations privilégiées effectuées (par plateforme) :

SE Commande(s)
Windows certutil -addstore Root <cert> via UAC
macOS sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain <cert>
Linux sudo cp <cert> <distro-trust-dir> + sudo update-ca-certificates (Debian) / sudo update-ca-trust (RHEL/SUSE)
Linux+Firefox/Chromium mise à jour de la base de données NSS de chaque profil via certutil -d sql:<profile>

Il s’agit des mêmes commandes que celles utilisées par mitmproxy, Charles Proxy, Fiddler et Caddy. Leur présence dans OmniRoute est documentée dans docs/security/STEALTH_GUIDE.md.

Mesures d’atténuation de la v3.8.6 :

  • runElevatedPowerShell() n’utilise plus -EncodedCommand <base64utf16le>. La charge utile élevée est écrite dans un fichier temporaire .ps1 propre à chaque appel (mode 0o600, à l’intérieur d’un répertoire privé mkdtempSync) et référencée via -File. Le fichier est supprimé dans finally. Cela élimine la signature classique d’élévation via PowerShell avec encodage en base64 signalée par le classificateur d’IA de Socket.dev.
  • installCertWindows contient un bloc intégré SECURITY-AUDITOR-NOTE: renvoyant vers le présent document.

Pourquoi nous le conservons : le proxy MITM est une fonctionnalité documentée utilisée par docs/security/STEALTH_GUIDE.md et docs/frameworks/MITM-PROXY.md. Sa suppression compromettrait l’ensemble des fonctionnalités de passerelle pour agents.


§2 — Importation des identifiants Zed (app/api/providers/zed/import/route.js)

Section intitulée « §2 — Importation des identifiants Zed (app/api/providers/zed/import/route.js) »

Fichiers sources :

  • src/app/api/providers/zed/discover/route.ts (nouveau dans v3.8.6)
  • src/app/api/providers/zed/import/route.ts
  • src/lib/zed-oauth/keychain-reader.ts
  • src/lib/zed-oauth/credentialFingerprint.ts (nouveau dans v3.8.6)

Déclencheur : l’utilisateur clique sur « Importer depuis Zed » dans la page Fournisseurs du tableau de bord local. L’accès au point de terminaison est contrôlé par requireManagementAuth. L’éditeur Zed lui-même enregistre les clés API de ses fournisseurs dans le trousseau du système d’exploitation sous des noms de services documentés — voir https://zed.dev/docs/ai/llm-providers.

Comportement de v3.8.5 (celui signalé par Socket.dev) :

POST /import détectait les identifiants et les enregistrait automatiquement dans le stockage SQLite local en un seul aller-retour. Aucune confirmation pour chaque compte, aucune empreinte, seulement « N jetons trouvés, tous importés ».

Mesure d’atténuation de v3.8.6 — confirmation en 2 étapes :

  1. POST /api/providers/zed/discover renvoie { candidates: [{ provider, service, account, fingerprint }] }. Le jeton brut n’est jamais transmis. L’empreinte est sha256(service|account|token).slice(0,16).
  2. Le tableau de bord affiche la liste des candidats, l’opérateur sélectionne ceux à importer, puis envoie { confirmedAccounts: [{ service, account, fingerprint }] } à POST /api/providers/zed/import.
  3. Le point de terminaison d’importation relit le trousseau sur le serveur et effectue un filtrage selon (service, account, fingerprint). Une réponse de détection falsifiée ou rejouée ne peut pas amener le point de terminaison d’importation à enregistrer un jeton sans rapport — si le jeton actif a changé depuis la détection, l’empreinte ne correspond plus et l’identifiant est ignoré.

Une variable d’environnement OMNIROUTE_ZED_IMPORT_LEGACY_ONE_STEP=true préserve le comportement de v3.8.5 pour les opérateurs qui n’ont pas encore mis à jour leur automatisation. Elle sera supprimée dans v3.9.

Pourquoi nous le conservons : l’importation depuis Zed constitue le parcours d’intégration le plus convivial pour les utilisateurs qui emploient déjà Zed et souhaitent répliquer leurs clés de fournisseurs dans OmniRoute sans avoir à les coller de nouveau.


§3 — execFile / spawn / PowerShell avec élévation de privilèges (21843.js)

Section intitulée « §3 — execFile / spawn / PowerShell avec élévation de privilèges (21843.js) »

Fichiers sources : src/mitm/systemCommands.ts.

Pourquoi cela a été signalé : le fragment réexporte execFileWithPassword, runElevatedPowerShell et la fonction utilitaire partagée quotePowerShell. Le classificateur d’IA de Socket.dev les considère comme une boîte à outils générique d’« exécution sur l’hôte + élévation de privilèges ». Dans OmniRoute, ils sont utilisés uniquement par le parcours d’installation du certificat MITM (§1) et par execFileWithPassword pour l’exécution de commandes sudo.

Mesure d’atténuation de v3.8.6 :

  • Refactorisation de runElevatedPowerShell (voir §1).
  • Un bloc SECURITY-AUDITOR-NOTE: intégré à la fois dans runElevatedPowerShell et execFileWithPassword documente les appelants autorisés et la liste fixe des exécutables.
  • L’appel spawn() de execFileWithPassword comporte un marqueur nosemgrep avec la liste des exécutables que la fonction utilitaire est autorisée à recevoir — il n’existe aucun chemin entre les entrées utilisateur et finalCommand/finalArgs.

§4 / §6 — Superviseur du service 9router (api/services/9router/{start,restart}/route.js)

Section intitulée « §4 / §6 — Superviseur du service 9router (api/services/9router/{start,restart}/route.js) »

Fichiers sources :

  • src/app/api/services/9router/_lib.ts — fabrique du superviseur.
  • src/app/api/services/9router/{start,stop,restart,status,install,update,auto-start}/route.ts.
  • src/lib/services/ServiceSupervisor.ts — gestion générique du lancement, interrogation de l’état de santé et tampon de journaux.

Déclencheur : l’utilisateur clique sur « Installer » / « Démarrer » sur la page des services intégrés du tableau de bord local.

Protections déjà en place :

  • Toutes les routes /api/services/* sont LOCAL_ONLY conformément à src/server/authz/routeGuard.ts (règle stricte nº 17). La restriction à l’interface de bouclage intervient avant toute vérification d’authentification — un JWT divulgué ne permet pas d’y accéder.
  • La ligne de 9router dans la base de données est initialisée avec status='not_installed', auto_start=0 (voir src/lib/db/migrations/071_services.sql:19). Le service ne démarre pas lors du premier lancement.
  • spawn() est appelé avec le chemin du binaire renvoyé par resolveSpawnArgs(apiKey, PORT) dans src/lib/services/installers/ninerouter.ts, qui repose sur une liste fixe de binaires pris en charge.
  • Les sorties standard et d’erreur sont mises en mémoire tampon (limite de 5 Mo, voir _lib.ts) — aucune écriture sur disque n’a lieu sauf si l’utilisateur active la journalisation depuis le tableau de bord.

Mesure d’atténuation de v3.8.6 : aucune modification fonctionnelle. Le profil de compilation minimal (OMNIROUTE_BUILD_PROFILE=minimal) remplace src/lib/services/installers/ninerouter.ts par un module factice pour les utilisateurs qui souhaitent que les chemins privilégiés soient physiquement retirés du paquet.

Pourquoi nous le conservons : 9router est un service compagnon facultatif pouvant être installé localement (à la manière d’une extension WordPress) — activation strictement volontaire.


§5 — Réécriture des identifiants OmniRoute Cloud Sync (api/keys/[id]/route.js)

Section intitulée « §5 — Réécriture des identifiants OmniRoute Cloud Sync (api/keys/[id]/route.js) »

Fichiers sources :

  • src/lib/cloudSync.ts — syncToCloud() / updateLocalTokens().
  • src/app/api/keys/[id]/route.ts — appelle syncKeysToCloudIfEnabled().

Déclencheur : isCloudEnabled() renvoie true (configuré depuis le tableau de bord) et CLOUD_URL est configurée. Lorsque les deux sont désactivés, aucun appel réseau sortant vers le point de terminaison Cloud n’est effectué.

Comportement de la v3.8.5 (le bug correctement détecté par Socket.dev) :

updateLocalTokens() remplaçait accessToken, refreshToken et providerSpecificData à partir de la réponse Cloud lorsque cloudUpdatedAt > localUpdatedAt. Aucun HMAC, aucune signature, aucune somme de contrôle. Une CLOUD_URL mal configurée ou hostile (ou une attaque MITM sur le canal) pouvait remplacer silencieusement les jetons OAuth des fournisseurs.

Mesures correctives de la v3.8.6 :

  1. Vérification HMAC : verifyCloudSignature(rawBody, sigHeader) vérifie l’en-tête X-Cloud-Sig (HMAC-SHA256(OMNIROUTE_CLOUD_SYNC_SECRET, rawBody)) avant d’analyser le JSON. Si le secret est défini, la signature est obligatoire. Dans le cas contraire (mode hérité), un avertissement est journalisé et la réponse est acceptée — le secret deviendra obligatoire dans la v3.9.
  2. Activation explicite des champs secrets : accessToken / refreshToken / providerSpecificData sont remplacés uniquement lorsque OMNIROUTE_CLOUD_SYNC_SECRETS=true. Le mode par défaut synchronise uniquement les métadonnées qui ne sont pas des identifiants (expiresAt, status, lastError*, rateLimitedUntil, updatedAt). Il s’agit d’un changement incompatible pour les utilisateurs qui dépendaient de la synchronisation distante des jetons — ils doivent l’activer explicitement.

Pourquoi nous le conservons : Cloud Sync est le seul moyen permettant à un locataire OmniRoute Cloud de centraliser les identifiants de l’équipe. Le correctif rend le modèle de menace explicite : « le serveur signe, le client vérifie, l’opérateur choisit de l’activer ».


Pour les utilisateurs qui ont besoin d’un artefact compatible avec Socket, effectuez le build avec :

Fenêtre de terminal
OMNIROUTE_BUILD_PROFILE=minimal npm run build

Le NormalModuleReplacementPlugin de webpack redirige quatre modules vers des stubs :

Module Stub
src/mitm/cert/install.ts src/mitm/cert/install.stub.ts
src/lib/zed-oauth/keychain-reader.ts src/lib/zed-oauth/keychain-reader.stub.ts
src/lib/cloudSync.ts src/lib/cloudSync.stub.ts
src/lib/services/installers/ninerouter.ts src/lib/services/installers/ninerouter.stub.ts

Chaque stub exporte la même interface, mais toutes les fonctions lèvent une featureDisabledError(name) lors de l’exécution. Les routes qui dépendent du module désactivé renvoient une réponse HTTP 503 accompagnée d’un message clair au lieu d’activer le chemin de code sensible.

Le bundle obtenu est destiné à être publié sous le nom omniroute-secure. Consultez docs/ops/PUBLISHING_SECURE.md pour connaître la procédure de publication.


À long terme, nous prévoyons de scinder le package npm en modules pouvant être audités séparément. Consultez le jalon v4 dans le système de suivi des issues GitHub pour accéder à l’issue de suivi.


Code source d’OmniRoute (a58000c7685f)

HagiCode

HagiCode est un espace de développement agentique qui associe workflows structurés, exécution multi-agent et vues Hero Dungeon.

Transformez vos idées en logiciels utiles grâce à un workflow agentique plus intelligent, rapide et agréable.

Interface principale de HagiCode en thème clair
  • SmartDes workflows structurés transforment une intention en parcours exécutable, de l’idée à la livraison.
  • EfficientLes workflows multi-agents font avancer recherche, réalisation et revue en parallèle.
  • FunHero Dungeon rend les longues sessions de code plus visuelles et collaboratives.
Visiter HagiCode