2 pontos por GN⁺ 2024-03-27 | 1 comentários | Compartilhar no WhatsApp
  • No ecossistema Rust, no momento em que uma dependência descontinuada entra no RUSTSEC, até uma biblioteca sem problema direto vira dívida técnica por causa dos usuários e do CI
  • O yaml-rust, do qual o insta dependia, estava acumulando pedidos de funcionalidade e bugs depois que o autor original perdeu o interesse
  • Após entrar no RUSTSEC, o CI de usuários diretos e indiretos começou a falhar e, na analogia financeira, isso equivaleu a um rebaixamento de rating e uma chamada de margem
  • Bibliotecas alternativas ou forks também não eram uma solução clara, porque mesmo trocando a dependência ainda restavam o peso da manutenção e o risco de novas dependências
  • A resposta final foi fazer vendor do código de yaml-rust dentro de insta, o que levou à crítica de que isso se parece mais com empacotar dívida técnica problemática como um CDO com nota AAA

Como a dependência yaml-rust se revelou como dívida técnica

  • insta dependia de yaml-rust, e yaml-rust vinha acumulando issues depois que o autor original perdeu o interesse no projeto
    • Algumas eram pedidos de funcionalidade, outras eram bugs reais
    • O mantenedor de insta não enfrentava esses problemas diretamente, mas o fato de ser uma dependência sem manutenção já a tornava dívida técnica
  • A situação mudou quando yaml-rust entrou na discussão para ser adicionado ao banco de dados do RUSTSEC
    • Na analogia financeira, o RUSTSEC cumpre o papel de agência de classificação de risco
    • Depois do registro, o CI de muitos projetos que usavam yaml-rust direta ou indiretamente começou a falhar em poucos minutos
    • Quando os usuários passaram a apontar ao mantenedor de insta o problema de usar yaml-rust, foi como se tivesse ocorrido uma chamada de margem na analogia financeira

As opções e a resposta real

  • Migrar para uma alternativa não parecia atraente
    • Uma das alternativas era um fork de yaml-rust, tinha apenas 1 mantenedor e adicionava 3 dependências
    • Uma delas já tinha recebido nota “B-”
    • Outra opção do ecossistema já havia tomado a decisão de mudar o padrão antes de ser cobrada por isso
  • Fazer um fork por conta própria também não era uma solução fundamental
    • A biblioteca forkada traria as mesmas exigências de manutenção
    • Se os relatórios de bug não fossem tratados, no fim ela acabaria sendo criticada da mesma forma que o yaml-rust
    • Portanto, o fork pode comprar tempo, mas não elimina o problema
  • A resposta real foi incorporar o código de yaml-rust dentro de insta por meio de vendor
    • insta agora passou a ser uma combinação do código de insta com yaml-rust
    • Isso cria uma estrutura parecida com elevar uma dívida técnica problemática para AAA
    • O CDO do título se refere à obrigação de dívida colateralizada, que ganhou má fama na crise financeira de 2007
  • A avaliação final fica próxima de “ninguém saiu ganhando”
    • O código problemático não desapareceu; ele apenas foi movido para dentro de insta
    • A pressão de parecer uma dependência externa diminuiu, mas o peso da manutenção em si continua

1 comentários

 
GN⁺ 2024-03-27
Comentários do Hacker News
  • Falando sem rodeios, a situação é que o autor de serde_yaml(https://lib.rs/crates/serde_yaml), o parser de YAML mais popular, largou o projeto de repente sem aviso prévio nem nomear um sucessor, e o marcou como deprecated e unmaintained
    Não é exatamente a mesma coisa que left-pad. O pacote ainda funciona e o crates.io nem permite exclusão, mas esse pacote é usado por 4.000 outros crates
    Ferramentas de auditoria e de atualização automática vão começar a sinalizar o uso de crates sem manutenção como um problema

    • Não é bem isso. O texto original diz que ele depende de yaml-rust(https://github.com/chyh1990/yaml-rust), e é esse projeto que agora ficou sem manutenção
      Ao mesmo tempo, serde_yaml também foi marcado como unmaintained para evitar que todo mundo migrasse em massa para ele
    • Não se aplica neste caso, mas bibliotecas sem dependências às vezes realmente estão em um estado de funcionalidade completa e quase não precisam de mudanças além de correções de segurança
      Seria bom se ferramentas de auditoria e políticas corporativas fossem inteligentes o bastante para distinguir esses casos, em vez de continuar alertando “sem manutenção” só porque não houve commits recentes
    • Esse autor não é alguém bem prolífico no ecossistema de bibliotecas Rust? Ele não tomou medidas assim com outras bibliotecas
      Fico curioso se existe mais contexto
  • Eu não entendi de imediato a sigla CDO, solta sem explicação, mas como o texto usa várias vezes a palavra collateralized, imagino que seja obrigação de dívida colateralizada (collateralized debt obligation)
    https://en.wikipedia.org/wiki/Collateralized_debt_obligation
    No começo pensei em chief data officer

    • A metáfora aqui está no fato de que um CDO é um produto financeiro criado a partir de outras dívidas
      Em 2008, especificamente, era algo como propriedade parcial de várias hipotecas, e quando as hipotecas ruins entraram em inadimplência, os CDOs quebraram junto [1]
      De todo modo, o que o autor parece ter em mente é menos uma metáfora baseada em dívida e mais algo próximo de risco sistêmico, como leftpad ou falhas em cascata de DNS. Um dos grandes motivos de 2008 ter sido tão caótico também foi mais o problema sistêmico do que o simples fato de terem empacotado dívidas em produtos [2]
      1. Como curiosidade, antes de 2008 a demanda por produtos financeiros baseados em hipotecas ficou tão infinita que começaram a criar até CDOs feitos de CDOs. Concediam hipotecas até para pessoas sem emprego e, ao empacotar isso como um produto com classificação AAA, virava um ativo rentável e fácil de vender
      2. Também havia o fato de que Lehman e Bear Stearns tinham alavancagem de 30 a 40 vezes. Isso é um nível insano
    • Exato. A implicação é que, se houver incentivo para empacotar dívida técnica ruim em pacotes de boa reputação, isto é, pacotes AAA, o ecossistema Rust pode caminhar para uma bolha e acabar colapsando
      Também significa que as agências de classificação, que deveriam exercer algum papel regulador, já foram capturadas
    • Você precisa assistir a The Big Short. Foi lá que aprendi o que era CDO
    • Percebi que agora já existe uma geração jovem demais para ter lido as manchetes da época em que CDOs estavam derrubando a economia mundial. Me senti velho
    • Eu pensei em https://en.wikipedia.org/wiki/Collaboration_Data_Objects ;)
  • Não quero discutir se isso é uma “vitória”, porque depende demais da definição de “vitória”, mas certamente há vantagens. Caminhos de código vulneráveis que não são executados e nem podem ser alcançados por bibliotecas externas agora passam a ser caminhos de código seguros
    Claro, continua sendo um estado meio preocupante, mas é seguro
    Há outra vantagem nesse tipo de vendorização. Se você tiver uma boa cobertura de testes na sua própria biblioteca, poderá rodar ferramentas de cobertura de código na biblioteca recém-incorporada
    Modificar a biblioteca pode ser difícil, mas talvez você consiga remover com relativa facilidade as partes que seu código não toca. Depende da estrutura, mas se todo o código vulnerável puder ser deletado, é um ganho claro, e mesmo que você descubra que de fato usava parte dele, isso pode ser um ganho ainda mais evidente
    Em outras palavras, fazer fork de uma biblioteca para mantê-la publicamente é uma grande responsabilidade, mas incorporar e ajustar apenas as partes necessárias é um peso bem menor. Mesmo sem fazer a poda de fato, o simples fato de isso ficar fácil já é um avanço
    Há desvantagens evidentes, mas não é tudo ruim

    • Na prática, isso é um resultado muito bom. Toda dependência é um risco de segurança, e o argumento para não vendorizar todas as dependências é que, para evitar problemas de segurança conhecidos, você precisa manter as dependências externas atualizadas
      Mas se não há mais ninguém acompanhando essa dependência externa, esse argumento desaparece, e a dependência vira um fardo
      Uma dependência ser marcada como abandoned e começar a aparecer em relatórios de segurança é o comportamento desejado. Isso permite decidir de forma informada se vale a pena vendorizar, ou seja, eliminar o risco de “algum agente malicioso colocar algo escondido ali”, ou escolher outro caminho
      Também é bom que o sistema de build do Rust permita isso com facilidade
    • Vale notar que no Google, por política, todas as dependências de terceiros precisam ser vendorizadas
      Normalmente também só é permitida uma única versão dessa dependência em todo o enorme monorepo. Pode ser que isso tenha mudado desde a minha época
      E cada dependência em third_party tem um responsável designado ou OWNERS
      Isso ajuda a manter tudo relativamente organizado
      Dito isso, esse tipo de disciplina imposta pode funcionar bem em uma organização como o Google, que tem bastante tempo e dinheiro e não está presa à filosofia de “mova-se rápido e quebre coisas”. Não sei o quanto isso funcionaria bem em startups
    • Fico curioso se existe alguma ferramenta que permita ao mantenedor de uma biblioteca calcular a cobertura de código transitiva, por exemplo quais suítes de teste de outras bibliotecas usam a sua
      Em certo sentido, outras bibliotecas oferecem chamadas quase aleatórias, o que seria uma forma interessante de teste fuzzing guiado
  • Também vi o mesmo padrão no ecossistema JS npm
    O npm audit geralmente age como um menino que gritava lobo em questões de segurança, e, se a licença permitir, trazer o código para dentro é uma das formas mais estáveis de evitar que os usuários sejam soterrados por falsos problemas
    Muitas vezes os usuários não entendem o contexto, ou simplesmente não se importam porque as políticas de seus empregadores foram feitas em lugares desconectados da realidade
    Não é porque uma regex usada em parte do código de uma dependência transitiva do pipeline de build poderia, em tese, ser explorada para negação de serviço que isso realmente vai acontecer
    “Problemas” em dependências transitivas profundas podem ser especialmente chatos de contornar. Muitas vezes é difícil provar tecnicamente, pela própria estrutura, coisas como “nunca passamos por esse caminho de código, então não somos afetados pelo defeito” ou “o único caso em que passamos por esse caminho é com entrada confiável em ambiente offline”

    • No mundo JS, problemas em dependências de desenvolvimento são realmente um pé no saco
      Existem cenários em que esse tipo de problema importa. Por exemplo, quando uma ferramenta de build comprometida injeta código malicioso na biblioteca que está sendo compilada
      Mas casos assim são extremamente raros e acabam enterrados em meio a uma enxurrada de regexes potencialmente sujeitas a negação de serviço que, na prática, não importam porque só são chamadas no build
      Some a isso o fato de que ferramentas de build típicas costumam ter árvores com algo como 50 bilhões de dependências transitivas, e vira um trabalho ingrato
      Acho que as ferramentas que reportam esse tipo de problema deveriam distinguir entre “explorável ao redistribuir” e “explorável ao usar no pipeline de build”
    • Mesmo que hoje esse caminho problemático não seja alcançado, no futuro ele pode passar a ser, conforme a dependência for atualizada e o caminho problemático se torne acessível
  • Em “agora uma dívida técnica ruim de repente virou AAA”, o “de repente” parece querer dizer que não faz sentido o mesmo código receber uma classificação melhor de dívida só porque foi vendorado
    Mas isso olha apenas para o valor do código em si e perde a parte mais importante da proposta de valor como um todo
    Quando um mantenedor traz o código para dentro, esse código passa a ser dele. Se um mantenedor ativo faz vendoring do código de um projeto morto, o valor desse código aumenta porque agora existe uma pessoa ativa que pode responder a problemas, revisar pull requests e corrigir bugs
    Em outra analogia, é como enviar um animal de estimação negligenciado para um novo dono: ele passa a receber mais cuidado, fica mais saudável e pode viver mais, então seu valor aumenta

    • Isso só vale para quem usa essa dependência indiretamente. Nesse caso, é bem provável que haja pouquíssimos olhos olhando para encontrar bugs
      O mantenedor também vai ter que se adaptar a uma base de código grande e desconhecida, o que cria uma barreira de entrada para implementar correções ou revisar mudanças
  • É um pensamento um pouco tangencial e controverso, mas acho difícil evitar problemas terríveis se um gerenciador de pacotes baseado em código-fonte não garantir ao registro o direito legal de assumir à força a manutenção de pacotes publicados
    Problemas como abandono, mudanças maliciosas, remoção maliciosa e impersonação
    Se um pacote for considerado importante o suficiente para a comunidade maior, precisa haver uma forma de tirar do dono original a entrada desse pacote no registro e fazê-la apontar para um fork
    Claro que uma medida dessas traria muito drama, mas permitiria proteger ativamente os usuários downstream

    • Isso não parece grande problema. A beleza do open source é justamente poder fazer fork
      O problema real é que manter projetos open source exige tempo e esforço, e não é fácil encontrar outra pessoa com tempo livre para assumir isso
    • Um gerenciador de pacotes baseado em código-fonte que toma a propriedade de pacotes quando o registro acha necessário deveria ter dificuldade para atrair contribuições
      É ainda menos atraente do que um modelo de copyright em que toda contribuição automaticamente vira propriedade de um grupo específico
      Talvez dê para argumentar que o GitHub, ao hospedar software livre, já seja um lugar especializado em violação de copyright desse software, mas pelo menos por enquanto ele não tenta tomar a propriedade nominal dos pacotes
    • Se a justificativa é a importância para a comunidade, isso no máximo faz sentido no nível do pacote, não no nível do registro
      Já que é uma questão comunitária, não se trata de uma relação cliente-fornecedor, e a maioria dos pacotes só é “importante” o suficiente para continuar sendo fornecida de graça
      Pode acabar virando algo humilhante como “vamos pagar US$ 10 por mês para vocês administrarem 100 mil pacotes para nós”
    • Esses problemas não são todos do mesmo tipo. Pacotes congelados significam que as dependências downstream terão que tomar decisões, mas há opções de sobra, inclusive vendoring
      A internet está cheia de código-fonte sem manutenção; ele simplesmente está lá, publicado uma vez do jeito que estava. Ninguém tem garantia de atualizações para código gratuito
    • A alternativa é o problema do Kik NPM. De um jeito ou de outro, é ruim
  • Foi muita sorte alguém já ter feito um fork do yaml-rust como yaml-rust2(https://github.com/Ethiraric/yaml-rust2/blob/master/document...)
    Também é ótimo que esse fork passe completamente na suíte de testes do YAML e ainda seja mais rápido nos benchmarks. A migração também parece simples
    No fim, o problema continua. Hoje dependemos do trabalho de outras pessoas que, por enquanto, estão dispostas a oferecer trabalho gratuito, mas isso pode não durar para sempre
    Não sei se existe saída além de recompensar o tempo e o esforço delas e torcer para que continuem fazendo um bom trabalho

    • O yaml-rust era originalmente uma implementação pura em Rust, e isso estava literalmente escrito no slogan
      “A pure rust YAML implementation.”
      Já o serde_yaml era até menos “puro Rust”, porque dependia do unsafe-libyaml, que era libyaml convertida com c2rust
    • Projetos com bus factor 1 são inerentemente arriscados
      Em um horizonte de tempo longo o suficiente, a probabilidade de um mantenedor de open source abandonar o projeto é 1
      A única forma de evitar isso é tratar projetos mantidos por uma única pessoa como tabu
    • Minha solução padrão para esse problema é evitar dependências de terceiros sempre que possível
      Talvez isso não seja uma opção para projetos em Rust, já que as pessoas que fizeram a biblioteca padrão de Rust parecem querer mantê-la enxuta
      Mas em outras linguagens com uma biblioteca padrão suficientemente completa, isso com certeza é uma opção viável
      Eu deliberadamente uso linguagens que já trazem o que preciso embutido, então quase nunca uso bibliotecas externas, exceto drivers de banco de dados
  • Essa situação toda parece meio ridícula. Se o código funciona e funciona há anos, não vejo por que ser não mantido seria um problema
    Se não precisa ser corrigido e você conhece as limitações e funcionalidades, tudo bem
    Código não estraga sozinho. Já peguei emprestado ou integrei código de décadas atrás e usei muito bem várias vezes
    Pessoalmente, acho que simplesmente ignoraria todas as reclamações sobre essa biblioteca

    • A resposta óbvia é que ela não “funciona” em vários sentidos
      Já se acumularam muitos bugs, e há correções existentes que nunca serão aplicadas. Basta ver https://github.com/chyh1990/yaml-rust/issues e /pulls
    • Quando você diz que pegou emprestado ou integrou código de décadas atrás, não foi fazendo vendoring da biblioteca dentro do seu próprio projeto?
      É o mesmo que assumir a manutenção dos pedaços de código que você usa
      Às vezes isso pode ser ruim, porque pode virar programação de copiar e colar ou esforço duplicado
      Mas, se um componente de terceiros está completamente sem manutenção, é algo bem compreensível
  • Sim, dependências podem ser incorporadas via vendoring. Dependências que estão “quase prontas” e cujo desenvolvimento e manutenção desaceleraram geralmente têm sido tratadas assim há uns 20 anos
    Só que eu nunca trabalhei em uma linguagem em que “batteries are not included”

  • Será que daria para criar algo como cargo vendor --aggressive, para podar todo o código morto dentro das dependências com base no meu crate?
    Fico pensando se isso não tornaria o problema de “revisar dependências” mais administrável
    Foge um pouco do ponto principal do texto, mas no fim tem relação com a ideia de que a escolha das dependências e todas as responsabilidades que vêm com isso são nossas
    Parece haver espaço para ferramentas que ajudem a assumir mais responsabilidade pelo que de fato vai compilado para dentro do crate

    • Então você também copiaria todos os relatórios de bugs da biblioteca para o seu próprio rastreador de bugs?
      Se não, não vejo o que estaria melhorando