
Nous avons audité zx de Google avec et sans compétence de révision
Le banc d'essai des compétences, partie 4 sur 4. Même modèle, même invite, une compétence installée ou non. Également dans la série : une page de destination, un bot Telegram, et le débogage d'un jeu de serpent.
Après trois entrées dans cette série, le schéma a été constant : la branche avec compétence en fait plus, vérifie plus, dépense plus de jetons et produit un résultat plus réfléchi. Cette entrée rompt ce schéma. C'est la seule fois sur l'ensemble du banc d'essai où l'exécution avec compétence a été moins coûteuse que la référence, et c'est aussi la seule fois où la branche avec compétence a manqué la découverte la plus importante de la série. Ces deux faits sont vrais simultanément, et la tension entre eux est le résultat le plus utile que nous ayons tiré de ce projet.
Pourquoi zx de Google
Nous voulions une tâche d'audit de code avec trois propriétés : du code réel, pas un extrait synthétique avec des bugs implantés ; quelque chose qu'un développeur de niveau intermédiaire pourrait plausiblement être invité à réviser un mardi ; et une base de code suffisamment connue pour que les lecteurs puissent vérifier nos affirmations par rapport à la source réelle au lieu de nous faire confiance aveuglément.
google/zx remplissait ces trois critères. C'est la bibliothèque de Google pour écrire des scripts shell en JavaScript, suffisamment populaire pour qu'une part significative des outils Node en dépende, et suffisamment petite pour qu'un audit de fichier unique soit une tâche juste plutôt qu'un projet de recherche. Nous avons choisi src/core.ts, le fichier qui gère l'invocation de processus et l'environnement, récupéré directement du dépôt. Rien ici n'est une vulnérabilité artificielle. C'est un fichier réel, activement maintenu, et zx est généralement bien construit : structure propre, valeurs par défaut sensées dans la plupart des cas, le genre de code qui passe une lecture rapide. C'est exactement le scénario où un audit prouve sa valeur en trouvant la seule chose qu'une lecture rapide manque, ou non.
Méthodologie
Deux exécutions, même modèle, même instruction : auditer src/core.ts de google/zx et produire des résultats classés de P0 à P3, chacun avec un emplacement, ce qui ne fonctionne pas, quand cela ne fonctionne pas, et une correction minimale. La première exécution a reçu cette consigne et rien d'autre. La deuxième exécution a reçu la même consigne plus notre propre liste de contrôle de révision de sécurité et de code, celle qui est livrée dans notre Security Pack et Developer Toolkit, lue intégralement avant le début de l'audit.
Même mise en garde que pour toutes les autres entrées de cette série : il s'agit d'une exécution par branche, un modèle, un fichier. Nous ne prétendons pas que le nombre spécifique de découvertes se reproduira lors d'une nouvelle exécution. Ce que nous rapportons est ce qui s'est passé, avec de la télémétrie de harnais réelle, et un schéma dans le type de choses que chaque branche détecte, qui, selon nous, se généralise mieux que les chiffres bruts.
Ce que la référence a trouvé
Laissé seul, le modèle a lu core.ts et, de sa propre initiative, a également récupéré util.ts et error.ts pour le contexte avant de rédiger quoi que ce soit. Personne ne lui a dit de le faire. Il a décidé que le fichier n'avait pas de sens en isolation et est allé chercher ses voisins, ce qui s'est avéré important.
La principale découverte de la référence, et la découverte de plus haute gravité de l'ensemble du banc d'essai, est celle-ci. À la ligne 139, core.ts définit par défaut son option d'environnement à env: process.env. Ce n'est pas une copie de l'environnement, c'est une référence directe à celui-ci. Une fois que vous le savez, la conséquence est simple : si un chemin de code fait quelque chose comme $.env.FOO = 'x', il ne définit pas une variable limitée à cet appel shell. Il mute process.env pour l'ensemble de l'application en cours d'exécution, ce qui signifie que toutes les autres parties du programme, toutes les autres bibliothèques, tout ce qui lit les variables d'environnement après ce point, voit le changement. Un ajustement de configuration destiné à un appel de sous-processus fuit dans l'état global. Dans un processus de serveur de longue durée, ou dans tout script qui déploie plusieurs appels zx avec des environnements légèrement différents, c'est le genre de bug qui n'apparaît pas dans un test rapide et qui corrompt ensuite une partie complètement non liée du système des jours plus tard. Nous l'avons vérifié directement par rapport à la source plutôt que de nous fier à la parole du modèle, et la ligne fait exactement ce que la découverte indique.
Le reste de la liste de la référence, classé P2 et P3, était solide sans être spectaculaire : une défaillance de détection bash qui est silencieusement ignorée et qui refait surface plus tard sous la forme d'un message d'erreur trompeur et non lié au lieu de la cause réelle, et un cas où break() lève une exception de manière synchrone depuis l'intérieur d'un gestionnaire .catch(), ce qui est le genre d'erreur de flux de contrôle facile à écrire et ennuyeuse à déboguer car la trace de pile pointe vers un endroit inutile.
Total de la référence : un P1, deux P2, quatre P3.
PACK DE DÉMARRAGE GRATUIT
Curieux de savoir ce qu'un audit Claude non guidé détecte sur votre propre code avant d'ajouter une liste de contrôle ? Notre pack de démarrage gratuit est un moyen rapide d'obtenir une deuxième passe.
Obtenez le pack de démarrage gratuitCe que la branche avec liste de contrôle a trouvé
L'exécution guidée par compétence a suivi notre liste de contrôle de révision de code, la même que celle couverte dans notre tour d'horizon des compétences de sécurité, et elle a donné l'impression d'un réviseur entièrement différent : même fichier, même accès, un ensemble de préoccupations complètement différent a émergé.
Sa meilleure découverte a été un rejet de promesse non géré : les sites d'appel pour break() et timeout() invoquent kill() sans l'attendre, de sorte qu'un rejet de cet appel n'a nulle part où atterrir et peut faire planter le processus en dehors de tout try/catch écrit par un appelant. Il a également détecté un TypeError qui apparaît lorsque quelque chose tente d'itérer de manière asynchrone une ProcessPromise après qu'elle a déjà été arrêtée, un cas limite réel qui n'apparaît que sous une séquence d'appels spécifique. Et il a signalé que ZX_PREFIX et ZX_POSTFIX sont insérés directement dans chaque invocation de shell sans assainissement, ce qui mérite l'attention d'un mainteneur si l'une ou l'autre de ces valeurs peut provenir de l'extérieur de l'auteur de script de confiance.
Ce sont des découvertes réelles et bien formulées, pas du remplissage. Le rapport de la branche avec liste de contrôle était également le plus utilisable des deux documents en soi : structure uniforme, langage de gravité cohérent, chaque entrée suivant la même forme emplacement-impact-correction sans que le modèle ait à inventer ce format à la volée.
Total de la liste de contrôle : deux P2, cinq P3. Aucun P1, et notamment, aucune découverte liée à process.env.
Le chevauchement, et sa faible étendue
Alignez les deux listes et le chevauchement est proche de zéro. Six découvertes de la référence, sept découvertes de la liste de contrôle, treize au total, et aucune d'entre elles ne nomme la même cause racine. La mutation de process.env. L'ingestion de la détection bash. Le lancer synchrone dans un catch. Le kill() non attendu. Le TypeError de l'itérateur arrêté. L'insertion de préfixe/postfixe non assaini. Chacun d'eux est un chemin de code différent.
C'est une surprise plus grande que l'une ou l'autre des listes individuelles. Deux réviseurs compétents examinant les mêmes quelque 300 lignes de TypeScript, l'un guidé par une liste de contrôle structurée et l'autre non, et ils aboutissent à des ensembles de problèmes presque entièrement disjoints. Si vous nous aviez dit au départ que l'exécution avec liste de contrôle vérifierait en gros la liste de la référence et y ajouterait du raffinement, nous l'aurions cru. Ce n'est pas ce qui s'est passé. La liste de contrôle n'a pas affiné la même recherche, elle a effectué une recherche différente.
Les chiffres
| Référence (sans compétence) | Branche avec compétence (liste de contrôle de révision de code) | Delta | |
|---|---|---|---|
| Jetons utilisés | 87,899 | 81,743 | −7% |
| Découvertes P1 | 1 | 0 | −1 |
| Découvertes P2 | 2 | 2 | 0 |
| Découvertes P3 | 4 | 5 | +1 |
| Total des découvertes | 7 | 7 | 0 |
| Lecture proactive des fichiers voisins | Oui (util.ts, error.ts) | Non | — |
| Découverte de plus haute gravité | Mutation process.env (vérifiée) | — | — |
Toutes les autres entrées de cette série ont montré que la branche avec compétence coûtait plus de jetons pour un résultat plus discipliné. C'est la seule exception : la branche avec liste de contrôle était 7% moins chère. Notre interprétation est qu'une liste de contrôle réduit l'espace de recherche, et un espace de recherche plus étroit est moins coûteux à exécuter, même s'il ferme également certains des chemins qu'une passe en exploration libre aurait empruntés.
Plancher vs. plafond
C'est la chose la plus marquante que nous ayons apprise à travers les quatre entrées, et elle apparaît le plus clairement ici. La liste de contrôle a rendu l'audit moins cher et lui a donné un format plus rigoureux et plus cohérent. Ce qu'elle n'a pas fait, c'est trouver le bug process.env, car cette découverte ne provenait d'aucune catégorie de la liste de contrôle. Elle est venue du fait que la référence a remarqué que core.ts seul était difficile à analyser, a décidé d'elle-même d'aller chercher util.ts et error.ts, et a suivi cette intuition là où la structure de la liste de contrôle n'a jamais pointé.
Une liste de contrôle élève le plancher. Elle garantit un standard minimum de couverture : chaque catégorie est vérifiée, chaque découverte est rédigée de la même manière, et vous ne manquez pas une détection facile à cause d'un mauvais jour. Elle n'élève pas le plafond. La meilleure découverte possible sur un fichier donné pourrait se trouver en dehors de chaque catégorie énumérée par la liste de contrôle, et un processus qui ne fait que parcourir la liste de contrôle passera juste à côté, avec confiance, dans un rapport bien formaté.
Ce n'est pas un argument contre les listes de contrôle. Aucune des sept découvertes de la liste de contrôle n'était mauvaise, et deux d'entre elles étaient le genre de choses qu'un réviseur humain occupé manque plausiblement sous la pression du temps. C'est un argument pour savoir à quoi sert une liste de contrôle. C'est un outil qui élève le plancher, pas le plafond, et le traiter comme les deux est la façon dont un vrai P1 passe à travers une révision qui semble autrement approfondie.
PACK SKILLPROOF
La liste de contrôle exacte qui a été exécutée dans ce test, celle qui a trouvé le kill() non attendu et l'insertion de préfixe non assaini, est livrée dans notre Security Pack aux côtés du reste de nos compétences de révision les mieux notées.
Obtenez le Security Pack — 10 $Comment effectuer un audit en deux passes vous-même
Compte tenu de ce que nous avons trouvé, notre recommandation réelle n'est pas "utilisez une liste de contrôle" ou "sautez la liste de contrôle". C'est d'exécuter les deux, sur tout ce qui compte.
Commencez par une passe libre. Pointez le modèle vers le fichier, donnez-lui la consigne d'audit, et laissez-le lire tout ce qu'il veut lire d'autre. Ne lui donnez pas de grille d'évaluation. C'est la passe qui a les meilleures chances de détecter ce que personne n'a pensé à mettre sur une liste, car elle n'est pas contrainte par la liste.
Ensuite, effectuez une deuxième passe distincte avec une liste de contrôle structurée, la nôtre ou la vôtre. C'est la passe qui garantit la couverture : les catégories ennuyeuses à vérifier à la main mais faciles à sauter lorsque vous suivez une intuition, l'assainissement, la gestion des erreurs, le nettoyage des ressources, sont parcourues à chaque fois.
Comparez les deux rapports avant de considérer l'un ou l'autre comme définitif. Si notre nombre de chevauchement se maintient sur votre code comme il s'est maintenu sur celui de zx, attendez-vous à ce que les deux listes partagent moins de la moitié de leurs découvertes. Considérez cela comme le résultat attendu, et non comme un signe d'échec de l'une ou l'autre des passes. Le protocole de test complet explique plus en détail comment nous structurons ce type d'exécution appariée, y compris comment nous contrôlons le fait que le modèle lise sa propre sortie antérieure.
Si vous n'avez de budget que pour une seule passe, notre conseil honnête basé sur ce résultat est : exécutez d'abord la passe libre. C'est celle qui est la plus susceptible de trouver la découverte que vous ne saviez pas chercher. Ensuite, si le temps le permet, suivez-la avec la liste de contrôle pour vous assurer que rien d'ennuyeux n'a été manqué. Pour une vue plus large des compétences qui gèrent bien ce type de révision, consultez notre tour d'horizon des meilleures compétences de codage.
FAQ
La découverte liée à process.env est-elle une réelle vulnérabilité dans zx ?
C'est un comportement réel qui mérite l'attention d'un mainteneur, pas une CVE divulguée et non quelque chose que nous présentons comme un exploit actif. zx est une bibliothèque globalement bien construite et activement maintenue, et c'est un choix de conception, une référence directe au lieu d'une copie, qui a une véritable conséquence de mutation pour tout chemin de code qui écrit dans $.env. Nous avons vérifié la ligne nous-mêmes par rapport à la source plutôt que de nous fier au rapport du modèle, ce qui est exactement la raison pour laquelle nous sommes à l'aise de l'appeler la principale découverte de l'ensemble du banc d'essai.
Pourquoi la branche avec compétence a-t-elle coûté moins cher ici, alors qu'elle a coûté plus cher partout ailleurs dans cette série ? Notre meilleure explication est la portée. Une compétence de conception dans l'entrée de la page de destination invite à l'itération : vérifier la sortie, réviser, vérifier à nouveau. Une liste de contrôle de révision fonctionne différemment. Elle définit un ensemble fixe de catégories à parcourir une seule fois, ce qui réduit la recherche plutôt que de l'étendre. Une recherche plus étroite, moins de jetons. C'est un mécanisme plausible, non prouvé, car il s'agit d'une seule exécution.
Devrais-je faire confiance à une révision de code IA guidée par une liste de contrôle pour tout détecter ? Non, et c'est la principale découverte ici. Une liste de contrôle est un dispositif qui élève le plancher : elle garantit un balayage minimum cohérent à travers les catégories connues. Ce n'est pas un dispositif qui élève le plafond, et la plus grande découverte de tout ce banc d'essai est venue d'une passe qui n'en suivait pas une. Utilisez une liste de contrôle pour la couverture et la cohérence. Ne l'utilisez pas comme votre seule passe sur du code qui compte réellement.
Qu'est-ce que cela signifie pour le choix d'une compétence de révision de code Claude en pratique ? Ne traitez pas "quelle compétence" comme la seule décision. Traitez "combien de passes" comme la plus importante. Une bonne compétence de liste de contrôle, comme celle de notre liste de contrôle de révision de code, vaut la peine d'être installée pour la cohérence et les catégories qu'elle garantit. Associez-la à au moins une passe non guidée sur tout ce que vous mettriez réellement en production, et lisez notre couverture des compétences de sécurité pour savoir comment nous évaluons les compétences de révision les unes par rapport aux autres dans le catalogue.
C'est la série. Quatre tâches, quatre verdicts honnêtes, et le fil conducteur à travers toutes est le même : une compétence change ce qu'un modèle vérifie, pas s'il est capable du travail. La valeur de cet échange dépend entièrement de ce que vous construisez et de l'attention que quiconque y portera par la suite.
★ 9.6/10 × 3
Le pack de démarrage gratuit
Les 3 skills avec nos meilleurs scores de test, plus la checklist d'installation — le setup qu'on mettrait sur une machine neuve. Gratuit, par e-mail.