
Auditamos zx de Google con y sin una Skill de Revisión
El Banco de Skills, parte 4 de 4. Mismo modelo, mismo prompt, una skill instalada o no. También en la serie: una página de aterrizaje, un bot de Telegram, y depuración de un juego de serpientes.
Después de tres entradas en esta serie, el patrón ha sido consistente: el brazo con skill hace más, verifica más, gasta más tokens y produce un resultado más considerado. Esta entrada rompe ese patrón. Es la única vez en todo el banco donde la ejecución con skill resultó más económica que la línea base, y también es la única vez que el brazo con skill pasó por alto el hallazgo más significativo de la serie. Ambas cosas son ciertas a la vez, y la tensión entre ellas es el resultado más útil que hemos obtenido de este proyecto.
Por qué zx de Google
Queríamos una tarea de auditoría de código con tres propiedades: código real, no un fragmento sintético con errores plantados; algo que un desarrollador de nivel medio podría plausiblemente ser solicitado a revisar un martes; y una base de código lo suficientemente conocida como para que los lectores puedan verificar nuestras afirmaciones contra la fuente real en lugar de confiar ciegamente en nosotros.
google/zx cumplía con las tres. Es la biblioteca de Google para escribir scripts de shell en JavaScript, lo suficientemente popular como para que una parte significativa de las herramientas de Node dependa de ella, y lo suficientemente pequeña como para que una auditoría de un solo archivo sea una tarea justa en lugar de un proyecto de investigación. Elegimos src/core.ts, el archivo que realiza la creación de procesos y el manejo del entorno, obtenido directamente del repositorio. Nada aquí es una vulnerabilidad artificial. Es un archivo real, mantenido activamente, y zx está generalmente bien construido: estructura limpia, valores predeterminados sensatos en la mayoría de los lugares, el tipo de código que pasa una lectura casual. Esa es exactamente la configuración donde una auditoría o bien justifica su existencia al encontrar lo que una lectura casual pasa por alto, o no lo hace.
Metodología
Dos ejecuciones, mismo modelo, misma instrucción: auditar src/core.ts de google/zx y producir hallazgos clasificados de P0 a P3, cada uno con una ubicación, qué falla, cuándo falla y una solución mínima. La ejecución uno recibió esa instrucción y nada más. La ejecución dos recibió la misma instrucción más nuestra propia lista de verificación de revisión de seguridad y código, la que se incluye en nuestro Security Pack y Developer Toolkit, leída en su totalidad antes de que comenzara la auditoría.
La misma advertencia que en cada otra entrada de esta serie: esta es una ejecución por brazo, un modelo, un archivo. No afirmamos que los recuentos específicos de hallazgos se repliquen en una nueva ejecución. Lo que estamos informando es lo que sucedió, con telemetría real del arnés, y un patrón en el tipo de cosas que cada brazo detecta que creemos que se generaliza mejor que los números brutos.
Qué encontró la línea base
Dejado solo, el modelo leyó core.ts y, por iniciativa propia, también incluyó util.ts y error.ts para contextualizar antes de redactar cualquier cosa. Nadie se lo dijo. Decidió que el archivo no tenía sentido de forma aislada y fue a buscar en sus vecinos, lo que resultó ser importante.
El principal hallazgo de la línea base, y el hallazgo de mayor gravedad de todo el banco, es este. En la línea 139, core.ts establece su opción de entorno por defecto en env: process.env. Eso no es una copia del entorno, es una referencia viva a él. Una vez que se sabe eso, la consecuencia es sencilla: si cualquier ruta de código hace algo como $.env.FOO = 'x', no está configurando una variable con alcance para esa única llamada de shell. Está mutando process.env para toda la aplicación en ejecución, lo que significa que cualquier otra parte del programa, cualquier otra biblioteca, cualquier cosa que lea variables de entorno después de ese punto, ve el cambio. Un ajuste de configuración destinado a una llamada de subproceso se filtra al estado global. En un proceso de servidor de larga duración, o en cualquier script que ramifica múltiples llamadas zx con entornos ligeramente diferentes, ese es el tipo de error que no aparece en una prueba rápida y luego corrompe una parte completamente no relacionada del sistema días después. Verificamos esto directamente contra la fuente en lugar de confiar en la palabra del modelo, y la línea hace exactamente lo que dice el hallazgo.
El resto de la lista de la línea base, clasificada P2 y P3, fue sólida sin ser espectacular: un fallo en la detección de bash que se suprime silenciosamente y luego aparece como un mensaje de error engañoso y no relacionado en lugar de la causa real, y un caso donde break() lanza sincrónicamente desde dentro de un manejador .catch(), que es el tipo de error de flujo de control fácil de escribir y molesto de depurar porque el rastreo de pila apunta a un lugar inútil.
Recuento total de la línea base: un P1, dos P2, cuatro P3.
PAQUETE DE INICIO GRATUITO
¿Tienes curiosidad por saber qué detecta una auditoría de Claude sin guía en tu propio código antes de añadir una lista de verificación? Nuestro paquete de inicio gratuito es una forma rápida de obtener una segunda revisión.
Obtén el paquete de inicio gratuitoQué encontró el brazo con lista de verificación
La ejecución guiada por skill siguió nuestra lista de verificación de revisión de código, la misma que se cubre en nuestro resumen de skills de seguridad, y se leyó como un revisor completamente diferente: mismo archivo, mismo acceso, un conjunto de preocupaciones completamente distinto salió a la superficie.
Su mejor hallazgo fue un rechazo de promesa no manejado: los sitios de llamada para break() y timeout() invocan kill() sin esperarlo, por lo que un rechazo de esa llamada no tiene dónde aterrizar y puede bloquear el proceso fuera de cualquier try/catch que un llamador haya escrito. También detectó un TypeError que aparece cuando algo intenta iterar asincrónicamente un ProcessPromise después de que ya ha sido detenido, un caso límite real que solo se manifiesta bajo una secuencia específica de llamadas. Y señaló que ZX_PREFIX y ZX_POSTFIX se insertan directamente en cada invocación de shell sin sanitización, lo que merece la atención de un mantenedor si cualquiera de los valores puede provenir de fuera del autor del script de confianza.
Estos son hallazgos reales y bien formados, no relleno. El informe del brazo con lista de verificación también fue el más utilizable de los dos documentos en sus propios términos: estructura uniforme, lenguaje de gravedad consistente, cada entrada siguiendo la misma forma de ubicación-impacto-solución sin que el modelo tuviera que inventar ese formato sobre la marcha.
Recuento total de la lista de verificación: dos P2, cinco P3. Ningún P1, y notablemente, ningún hallazgo de process.env en absoluto.
La superposición, y lo pequeña que fue
Alinear ambas listas y la superposición es cercana a cero. Seis hallazgos de la línea base, siete hallazgos de la lista de verificación, trece en total, y ninguno de ellos nombra la misma causa raíz. La mutación de process.env. La supresión de la detección de bash. El lanzamiento síncrono en un catch. El kill() no esperado. El TypeError del iterador detenido. La inserción no sanitizada de prefijo/sufijo. Cada uno es una ruta de código diferente.
Eso es una sorpresa mayor que cualquiera de las listas individuales. Dos revisores competentes examinando las mismas aproximadamente 300 líneas de TypeScript, uno guiado por una lista de verificación estructurada y otro no, y aterrizan en conjuntos de problemas casi completamente disjuntos. Si nos hubieran dicho al principio que la ejecución con lista de verificación básicamente verificaría dos veces la lista de la línea base y añadiría pulido, lo habríamos creído. Eso no fue lo que pasó. La lista de verificación no refinó la misma búsqueda, ejecutó una búsqueda diferente.
Los números
| Línea base (sin skill) | Brazo con skill (lista de verificación de revisión de código) | Delta | |
|---|---|---|---|
| Tokens usados | 87,899 | 81,743 | −7% |
| Hallazgos P1 | 1 | 0 | −1 |
| Hallazgos P2 | 2 | 2 | 0 |
| Hallazgos P3 | 4 | 5 | +1 |
| Hallazgos totales | 7 | 7 | 0 |
| Leyó proactivamente archivos vecinos | Sí (util.ts, error.ts) | No | — |
| Hallazgo de mayor gravedad | Mutación de process.env (verificado) | — | — |
Cada otra entrada de esta serie mostró que el brazo con skill costaba más tokens para un resultado más disciplinado. Esta es la única excepción: el brazo con lista de verificación fue un 7% más económico. Nuestra interpretación es que una lista de verificación reduce el espacio de búsqueda, y un espacio de búsqueda más estrecho es más económico de ejecutar, incluso cuando también es uno que cierra algunos de los caminos por los que una pasada de exploración libre habría divagado.
Piso vs. techo
Esto es lo más claro que hemos aprendido a lo largo de las cuatro entradas, y se manifiesta más claramente aquí. La lista de verificación hizo la auditoría más económica y le dio un formato más riguroso y consistente. Lo que no hizo fue encontrar el error de process.env, porque ese hallazgo no provino de ninguna categoría de la lista de verificación. Vino de la línea base al notar que core.ts por sí solo era difícil de razonar, decidiendo por su cuenta ir a buscar util.ts y error.ts, y siguiendo esa intuición a un lugar al que la estructura de la lista de verificación nunca apuntó.
Una lista de verificación eleva el piso. Garantiza un estándar mínimo de cobertura: cada categoría se verifica, cada hallazgo se redacta de la misma manera y no se pierde una detección fácil por un mal día. No eleva el techo. El mejor hallazgo posible en un archivo dado podría existir fuera de cada categoría que la lista de verificación enumera, y un proceso que solo sigue la lista de verificación pasará de largo, con confianza, en un informe bien formateado.
Eso no es un argumento en contra de las listas de verificación. Cero de los siete hallazgos de la lista de verificación fueron malos, y dos de ellos eran el tipo de cosas que un revisor humano ocupado plausiblemente pasa por alto bajo presión de tiempo. Es un argumento para saber para qué sirve una lista de verificación. Es una herramienta para elevar el piso, no para elevar el techo, y tratarla como ambas es cómo un P1 real se escapa de una revisión que de otro modo parece exhaustiva.
PAQUETE SKILLPROOF
La lista de verificación exacta que se ejecutó en esta prueba, la que encontró el kill() no esperado y la inserción de prefijo no sanitizada, se incluye en nuestro Security Pack junto con el resto de nuestras skills de revisión mejor valoradas.
Obtén el Security Pack — $10Cómo ejecutar una auditoría de dos pasadas por tu cuenta
Dado lo que encontramos, nuestra recomendación real no es "usa una lista de verificación" o "omite la lista de verificación". Es ejecutar ambas, en cualquier cosa que importe.
Comienza con una pasada libre. Apunta el modelo al archivo, dale el resumen de la auditoría y deja que lea lo que quiera leer. No le entregues una rúbrica. Esta es la pasada que tiene las mejores probabilidades de detectar aquello que nadie pensó en incluir en una lista, porque no está limitada a la lista.
Luego, ejecuta una segunda pasada separada con una lista de verificación estructurada, la nuestra o la tuya. Esta es la pasada que garantiza la cobertura: las categorías que son aburridas de revisar manualmente pero fáciles de omitir cuando sigues una intuición, la sanitización, el manejo de errores, la limpieza de recursos, se revisan cada vez.
Compara los dos informes antes de leer cualquiera de ellos como final. Si nuestro número de superposición se mantiene en tu código como lo hizo en zx, espera que las dos listas compartan menos de la mitad de sus hallazgos. Trata eso como el resultado esperado, no como una señal de que alguna pasada falló. El protocolo de prueba completo cubre cómo estructuramos este tipo de ejecución emparejada con más detalle, incluyendo cómo controlamos que el modelo lea su propia salida anterior.
Si solo tienes presupuesto para una pasada, nuestro consejo honesto basado en este resultado es: ejecuta primero la pasada libre. Es la que tiene más probabilidades de encontrar el hallazgo que no sabías que debías buscar. Luego, si el tiempo lo permite, síguela con la lista de verificación para asegurarte de que no se haya pasado por alto nada aburrido. Para una visión más amplia de qué skills manejan bien este tipo de revisión, consulta nuestro resumen de las mejores skills de codificación.
Preguntas Frecuentes
¿Es el hallazgo de process.env una vulnerabilidad real en zx?
Es un comportamiento real que merece la atención de un mantenedor, no una CVE divulgada y no algo que estemos presentando como un exploit activo. zx es una biblioteca bien construida y mantenida activamente en general, y esta es una elección de diseño, una referencia viva en lugar de una copia, que tiene una consecuencia de mutación genuina para cualquier ruta de código que escriba en $.env. Verificamos la línea nosotros mismos contra la fuente en lugar de confiar en el informe del modelo, que es exactamente por qué nos sentimos cómodos llamándolo el hallazgo principal de todo el banco.
¿Por qué el brazo con skill costó menos aquí, cuando costó más en todas las demás partes de esta serie?
Nuestra mejor explicación es el alcance. Una skill de diseño en la entrada de la página de aterrizaje invita a la iteración: verificar la salida, revisar, verificar de nuevo. Una lista de verificación de revisión funciona de manera diferente. Define un conjunto fijo de categorías para recorrer una vez, lo que reduce la búsqueda en lugar de expandirla. Búsqueda más estrecha, menos tokens. Es un mecanismo plausible, no probado, ya que esta es una única ejecución.
¿Debería confiar en una revisión de código de IA guiada por una lista de verificación para detectarlo todo?
No, y ese es el hallazgo principal aquí. Una lista de verificación es un dispositivo para elevar el piso: garantiza un barrido mínimo consistente a través de categorías conocidas. No es un dispositivo para elevar el techo, y el hallazgo más grande de todo este banco provino de una pasada que no seguía una. Usa una lista de verificación para la cobertura y la consistencia. No la uses como tu única pasada en código que realmente importa.
¿Qué significa esto para elegir una skill de revisión de código de Claude en la práctica?
No trates "qué skill" como la única decisión. Trata "cuántas pasadas" como la más importante. Una buena skill de lista de verificación, como la de nuestro listado de code-review-checklist, vale la pena instalarla por la consistencia y las categorías que garantiza. Combínala con al menos una pasada sin guía en cualquier cosa que realmente vayas a desplegar, y lee nuestra cobertura de skills de seguridad para saber cómo sopesamos las skills de revisión entre sí en el catálogo.
Esa es la serie. Cuatro tareas, cuatro veredictos honestos, y el hilo conductor en todas ellas es el mismo: una skill cambia lo que un modelo verifica, no si es capaz de realizar el trabajo. Si ese intercambio vale la pena depende enteramente de lo que estés construyendo y de cuán de cerca lo vaya a examinar alguien después.
★ 9.6/10 × 3
El pack de inicio gratis
Los 3 skills con nuestras mejores puntuaciones de test más la checklist de instalación: el setup que pondríamos en una máquina recién estrenada. Gratis, por email.