2 puntos por GN⁺ 2024-03-27 | 1 comentarios | Compartir por WhatsApp
  • En el ecosistema de Rust, en cuanto una dependencia sin mantenimiento aparece en RUSTSEC, incluso una biblioteca que no tenía problemas directos se convierte en deuda técnica a través de sus usuarios y CI
  • insta dependía de yaml-rust, que había acumulado solicitudes de funcionalidades y bugs después de que disminuyera el interés de su autor original
  • Tras su inclusión en RUSTSEC, el CI de usuarios directos e indirectos empezó a fallar; usando una analogía financiera, fue como una rebaja de calificación y una margin call
  • Ni una biblioteca alternativa ni un fork eran una solución clara: aunque se cambiara la dependencia, seguirían existiendo la carga de mantenimiento y el riesgo de nuevas dependencias
  • La respuesta final fue vendorizar el código de yaml-rust dentro de insta, lo que llevó a críticas de que era parecido a empaquetar deuda técnica de mala calidad como un CDO con calificación AAA

Cómo la dependencia yaml-rust se reveló como deuda técnica

  • insta dependía de yaml-rust, y yaml-rust tenía issues acumulándose después de que su autor original perdiera interés
    • Algunos eran solicitudes de funcionalidades y otros eran bugs reales
    • El mantenedor de insta no sufría esos problemas directamente, pero el hecho de que fuera una dependencia sin mantenimiento la convertía en deuda técnica
  • La situación cambió cuando yaml-rust entró en una discusión para ser agregado a la base de datos RUSTSEC
    • En la analogía financiera, RUSTSEC cumple el papel de agencia calificadora
    • Después de su registro, el CI de muchos proyectos que usaban yaml-rust directa o indirectamente empezó a fallar en cuestión de minutos
    • Cuando los usuarios empezaron a señalarle al mantenedor de insta el problema de usar yaml-rust, en términos de la analogía financiera se produjo una margin call

Opciones y respuesta real

  • Cambiarse a una alternativa no resultaba atractivo
    • Una alternativa era un fork de yaml-rust, tenía un solo mantenedor y agregaba 3 dependencias
    • Una de ellas ya tenía una calificación “B-”
    • Otra opción del ecosistema tomó la decisión de cambiar el valor predeterminado antes de ser señalada
  • Hacer un fork propio tampoco era una solución de fondo
    • Una biblioteca forkeada trae consigo las mismas exigencias de mantenimiento
    • Si no se responde a los reportes de bugs, tarde o temprano terminará siendo señalada igual que el yaml-rust original
    • Por lo tanto, un fork puede ganar tiempo, pero no elimina el problema
  • La respuesta real fue vendorizar el código de yaml-rust dentro de insta
    • insta ahora queda como una combinación del código de insta y yaml-rust
    • Es una estructura parecida a elevar deuda técnica de mala calidad a AAA
    • El CDO del título se refiere a las obligaciones de deuda colateralizada, que se volvieron tristemente célebres durante la crisis financiera de 2007
  • La evaluación final se acerca a “nadie ganó”
    • El código problemático no desapareció; solo se movió dentro de insta
    • La presión de verlo como una dependencia externa disminuyó, pero la carga de mantenimiento en sí permanece

1 comentarios

 
GN⁺ 2024-03-27
Opiniones en Hacker News
  • Dicho sin rodeos, el autor de serde_yaml(https://lib.rs/crates/serde_yaml), el parser de YAML más popular, de pronto se retiró sin aviso ni designar a un mantenedor sucesor, y lo marcó como deprecated y unmaintained.
    No es exactamente lo mismo que left-pad. El paquete sigue funcionando y crates.io ni siquiera permite borrarlo, pero este paquete se usa en otros 4,000 crates.
    Las herramientas de auditoría y actualización automática van a empezar a marcar como problema el uso de crates sin mantenimiento.

    • No es exacto. El texto original dice que depende de yaml-rust(https://github.com/chyh1990/yaml-rust), y ese proyecto es el que ahora quedó sin mantenimiento.
      Al mismo tiempo, serde_yaml también fue marcado como unmaintained para evitar que la gente migre masivamente hacia allá.
    • Aunque aquí no aplica, una biblioteca sin dependencias a veces puede estar realmente en un estado de funcionalidad completa, y casi no necesitar cambios salvo correcciones de seguridad.
      Ojalá las herramientas de auditoría y las políticas de empresa fueran lo bastante inteligentes para distinguir estos casos, en vez de seguir alertando “sin mantenimiento” solo porque no hay commits recientes.
    • ¿Ese autor no es alguien bastante prolífico en el ecosistema de bibliotecas de Rust? No hizo algo así con sus otras bibliotecas.
      Me pregunto si hay más información.
  • La sigla CDO, usada sin explicación, no me resultó obvia de inmediato, pero como en el texto aparece varias veces “collateralized”, supongo que se refiere a obligación de deuda colateralizada (collateralized debt obligation).
    https://en.wikipedia.org/wiki/Collateralized_debt_obligation
    Al principio pensé en chief data officer.

    • La analogía aquí está en que un CDO es un producto financiero hecho a partir de otras deudas.
      En concreto, en 2008 eran algo así como participaciones parciales sobre varias hipotecas, y cuando las hipotecas basura entraron en default, los CDO también se rompieron [1].
      En cualquier caso, lo que el autor tiene en mente no es tanto una analogía basada en deuda, sino algo más parecido a riesgo sistémico, como leftpad o fallas en cadena de DNS. Una razón importante por la que 2008 fue un caos tan grande también tuvo más que ver con problemas sistémicos que con el hecho en sí de crear productos a partir de deuda [2].
      1. Dato curioso: antes de 2008, la demanda de productos financieros basados en hipotecas creció sin parar, hasta el punto de que empezaron a crear CDO hechos de CDO. Si le dabas una hipoteca incluso a alguien sin trabajo y luego la empaquetabas en un producto con calificación AAA, se volvía un producto rentable y fácil de vender.
      2. También está el hecho de que los ratios de apalancamiento de Lehman y Bear Stearns eran de 30 a 40 veces. Eso es una locura.
    • Correcto. La implicación es que, si se crean incentivos para empaquetar deuda técnica defectuosa dentro de paquetes con buena reputación, es decir, paquetes AAA, el ecosistema Rust puede encaminarse hacia una burbuja y terminar colapsando.
      También implica que las agencias de calificación que deberían actuar como reguladores ya fueron capturadas.
    • Hay que ver The Big Short. Ahí aprendí qué era un CDO.
    • Me doy cuenta de que ya existe una generación que era demasiado joven para leer los titulares de la época en que los CDO derrumbaban la economía mundial. Me siento viejo.
    • Pensé en https://en.wikipedia.org/wiki/Collaboration_Data_Objects ;)
  • No quiero discutir si esto es una “victoria”, porque depende demasiado de cómo se defina “victoria”, pero definitivamente tiene ventajas. Una ruta de código vulnerable que ni siquiera se ejecuta y a la que no se puede llegar desde una biblioteca externa ahora se convierte en una ruta de código segura.
    Claro, sigue siendo una situación inquietante, pero es segura.
    Este tipo de vendorización también tiene otra ventaja. Si tu biblioteca tiene una cobertura de pruebas sólida, puedes correr herramientas de cobertura de código sobre la biblioteca que acabas de incorporar.
    Modificar una biblioteca puede ser difícil, pero también puedes ir eliminando con relativa facilidad las partes que tu propio código no toca. Depende de la estructura, pero si todo el código vulnerable se elimina, es una ganancia clara; y si descubres que en realidad estabas usando parte de él, puede ser una ganancia todavía más evidente.
    Es decir, hacer un fork de una biblioteca para mantenerla públicamente es una gran responsabilidad, pero vendorizar solo las partes que necesitas y ajustarlas es una carga mucho menor. Incluso si no haces esa poda en la práctica, el solo hecho de facilitarla ya es un avance.
    Sin duda hay desventajas, pero no todo es malo.

    • En realidad es un resultado muy bueno. Toda dependencia es un riesgo de seguridad, y el argumento para no vendorizar todas las dependencias es que, para evitar problemas de seguridad conocidos, hay que mantener actualizadas las dependencias externas.
      Pero si ya no hay nadie vigilando una dependencia externa, ese argumento desaparece, y esa dependencia se vuelve una carga.
      Que una dependencia se marque como abandoned y empiece a aparecer en reportes de seguridad es el comportamiento deseado. Gracias a eso puedes decidir con información si vendorizarla, es decir, eliminar el riesgo de que “un actor malicioso meta algo a escondidas”, o tomar otra opción.
      También es bueno que el sistema de build de Rust permita hacer esto fácilmente.
    • Vale la pena señalar que en Google, por política, todas las dependencias de terceros deben estar vendorizadas.
      Normalmente también se permite una sola versión de esa dependencia en todo el enorme monorepo. Puede que eso haya cambiado desde que estuve ahí.
      Y cada dependencia en third_party tiene responsables u OWNERS designados.
      Eso ayuda a mantener bastante orden.
      Dicho eso, esta disciplina impuesta puede encajar bien en una organización como Google, con suficiente tiempo y dinero y no atada a la filosofía de “muévete rápido y rompe cosas”. No sé qué tan bien funcionaría en una startup.
    • Me pregunto si existe alguna herramienta que permita a los mantenedores de bibliotecas calcular la cobertura de código transitiva de su propia biblioteca; por ejemplo, qué suites de pruebas de otras bibliotecas la usan.
      En cierto sentido, como otras bibliotecas aportan llamadas casi aleatorias, podría ser una estrategia de pruebas interesante, parecida a un fuzz test direccional.
  • Vi el mismo patrón en el ecosistema de JS npm
    npm audit suele comportarse como el pastorcito mentiroso con los problemas de seguridad, y si la licencia lo permite, traer el código hacia adentro es una de las formas más estables de no quedar abrumado por issues falsos de los usuarios
    Muchas veces los usuarios no entienden el contexto, o no les importa porque las políticas de su empleador se crearon en un lugar desconectado de la realidad
    Que una expresión regular usada en alguna parte del codebase de una dependencia transitiva del pipeline de build pueda explotarse realmente para un ataque de denegación de servicio no es el caso
    Los “issues” en dependencias transitivas profundas pueden ser especialmente molestos de esquivar. Por la estructura, muchas veces es difícil demostrar técnicamente hechos como “nunca entramos en esa ruta de código, así que el defecto no nos afecta” o “la única vez que se entra en esa ruta es con input confiable en un entorno offline”

    • En el mundo JS, los issues en dependencias de desarrollo son un verdadero dolor de cabeza
      Sí existen escenarios donde estos problemas importan. Por ejemplo, cuando una herramienta de build comprometida inyecta código malicioso en la librería que se está compilando
      Pero esos casos son extremadamente raros y quedan sepultados bajo una ola de expresiones regulares con potencial de denegación de servicio que en realidad no importan porque solo se invocan durante el build
      Si a eso se suma que una herramienta de build típica tiene un árbol de unos 50 mil millones de dependencias transitivas, se vuelve un trabajo realmente pesado
      Creo que las herramientas que reportan estos issues deberían distinguir entre “explotable si se redistribuye” y “explotable si se usa en el pipeline de build”
    • Aunque ahora no entres en esa ruta problemática, más adelante una dependencia podría actualizarse y hacer que sí entres en la ruta problemática
  • En “ahora la deuda técnica mala de repente recibió calificación AAA”, la expresión “de repente” parece querer decir que no tiene sentido que el mismo código reciba una mejor calificación de deuda solo porque fue vendorizado
    Pero eso mira solo el valor del código en sí y se pierde la parte más importante de la propuesta de valor completa
    Cuando un maintainer trae el código hacia adentro, ese código pasa a ser de su propiedad. Si un maintainer activo vendoriza código de un proyecto muerto, aparece una persona activa que puede responder a issues, revisar pull requests y corregir bugs, así que el valor de ese código aumenta
    Como otra analogía, es como cuando una mascota abandonada pasa a un nuevo dueño: puede recibir mejores cuidados, estar más sana y vivir más tiempo, por lo que su valor sube

    • Eso solo aplica para quienes usan esa dependencia indirectamente. En este caso, probablemente haya muy pocos ojos buscando bugs
      El maintainer también tendrá que adaptarse a un codebase grande y desconocido, así que habrá una barrera de entrada para implementar o revisar cambios
  • Es una idea un poco tangencial y polémica, pero creo que si un gestor de paquetes basado en código fuente no garantiza al registro el derecho legal de tomar por la fuerza el mantenimiento de un paquete publicado, es difícil evitar problemas terribles
    Problemas como abandono, cambios maliciosos, eliminación maliciosa o suplantación
    Si se determina que un paquete es lo suficientemente importante para una comunidad más amplia, hace falta una forma de quitar de manos del dueño original la entrada del registro del paquete y hacer que apunte a un fork
    Obviamente una medida así vendría con mucho drama, pero podría proteger activamente a los usuarios aguas abajo

    • No me parece un gran problema. La belleza del open source está en que se puede hacer fork
      El problema real es que mantener un proyecto open source requiere tiempo y esfuerzo, y no es fácil encontrar a otra persona con tiempo libre
    • Un gestor de paquetes basado en código fuente que quite la propiedad de un paquete cuando el registro lo considere necesario debería tener dificultades para atraer contribuciones
      Es incluso menos atractivo que un modelo de copyright donde todas las contribuciones pasan automáticamente a ser propiedad de un grupo específico
      Se podría argumentar que GitHub, al alojar software libre, es un lugar especializado en infringir el copyright de ese software, pero al menos por ahora no intenta quitar la propiedad nominal de los paquetes
    • Si el motivo es la importancia para la comunidad, en el mejor de los casos es algo que debe hacerse a nivel de paquete, no a nivel de registro
      Como estamos hablando de comunidad, no es una relación cliente-proveedor, y la mayoría de los paquetes solo son lo bastante importantes como para ofrecerse gratis
      Incluso podría terminar siendo una cifra humillante, como “te damos 10 dólares al mes, así que mantén 100.000 paquetes para nosotros”
    • Estos problemas no son todos del mismo tipo. Un paquete congelado significa que las dependencias aguas abajo tienen que tomar decisiones, pero hay muchas opciones, incluido el vendoring
      En internet hay mucho código fuente sin mantenimiento, simplemente publicado una vez tal cual. Nadie tiene garantizadas actualizaciones para código gratis
    • La alternativa es el problema Kik NPM. De cualquier forma es malo
  • Fue bastante afortunado que alguien ya haya hecho un fork de yaml-rust y creado yaml-rust2(https://github.com/Ethiraric/yaml-rust2/blob/master/document...)
    También es genial que ese fork pase por completo el test suite de YAML y que además sea más rápido en los benchmarks. La migración parece sencilla
    Al final el problema sigue ahí. Dependemos del trabajo de otras personas que hoy están dispuestas a aportar trabajo gratis, pero eso puede no continuar para siempre
    No sé si hay una forma de evitarlo aparte de compensarlas por su tiempo y esfuerzo y esperar que sigan haciendo buen trabajo

    • yaml-rust originalmente era una implementación pura en Rust, y el tagline literalmente decía eso
      “A pure rust YAML implementation.”
      En cambio, serde_yaml era más difícil de considerar Rust puro, porque dependía de unsafe-libyaml, que era libyaml convertido con c2rust
    • Un proyecto con bus factor 1 es inherentemente riesgoso
      En un horizonte de tiempo lo suficientemente largo, la probabilidad de que un maintainer de open source deje el proyecto es 1
      La única forma de evitarlo es volver tabú los proyectos mantenidos por una sola persona
    • Mi solución por defecto para este problema es evitar tanto como sea posible las dependencias de terceros
      Probablemente no sea una opción para proyectos Rust, porque quienes hicieron la biblioteca estándar de Rust quieren mantenerla minimalista
      Pero al usar otros lenguajes con una biblioteca estándar suficientemente completa, sin duda es una opción posible
      Como uso deliberadamente lenguajes que ya traen incorporado lo que necesito, casi no uso librerías externas aparte de drivers de bases de datos
  • Toda esta situación se ve un poco ridícula. Si el código funciona y lo ha hecho durante años, no veo por qué sería un problema que no esté mantenido.
    Si no hace falta modificarlo y conoces sus límites y funcionalidades, está bien.
    El código no se echa a perder solo. Varias veces he tomado prestado o integrado código de hace décadas y lo he usado sin problemas.
    Personalmente, creo que simplemente ignoraría todas las quejas sobre esa biblioteca y seguiría adelante.

    • La respuesta obvia es que, en muchos sentidos, no “funciona”.
      Ya se acumularon muchos bugs, y aunque existen correcciones, nunca se van a aplicar. Basta ver https://github.com/chyh1990/yaml-rust/issues y /pulls.
    • Cuando dices que tomaste prestado o integraste código de hace décadas, ¿no fue que el autor metió la biblioteca como vendored dentro de su propio proyecto?
      Es como hacerse cargo del mantenimiento del fragmento de código que uno usa.
      A veces puede ser malo, porque puede convertirse en programación de copiar y pegar o en duplicación de esfuerzo.
      Pero si un componente de terceros está totalmente sin mantenimiento, es bastante entendible.
  • Sí, las dependencias se pueden vendorear. En general, eso es lo que se ha hecho durante 20 años con dependencias que están “casi completas” y cuyo desarrollo y mantenimiento se volvieron lentos.
    Aunque nunca he trabajado en un lenguaje donde “batteries are not included”.

  • ¿Habrá alguna forma de crear algo como cargo vendor --aggressive, para podar todo el código muerto dentro de las dependencias desde la perspectiva de mi crate?
    Me pregunto si eso podría hacer más manejable el problema de “revisar las dependencias”.
    Se sale un poco del tema principal del texto, pero tiene relación en el sentido de que al final elegir dependencias y asumir todas las responsabilidades que conllevan depende de nosotros.
    Parece haber espacio para herramientas que nos ayuden a hacernos más responsables de lo que realmente termina compilado dentro del crate.

    • ¿Entonces también copias todos los reportes de bugs de la biblioteca a tu propio rastreador de bugs?
      Si no, no veo qué mejora.