Dívida técnica: minha biblioteca Rust agora virou um CDO
(lucumr.pocoo.org)- 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 oinstadependia, 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-rustdentro deinsta, o que levou à crítica de que isso se parece mais com empacotar dívida técnica problemática como um CDO com notaAAA
Como a dependência yaml-rust se revelou como dívida técnica
instadependia deyaml-rust, eyaml-rustvinha acumulando issues depois que o autor original perdeu o interesse no projeto- Algumas eram pedidos de funcionalidade, outras eram bugs reais
- O mantenedor de
instanã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-rustentrou 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-rustdireta ou indiretamente começou a falhar em poucos minutos - Quando os usuários passaram a apontar ao mantenedor de
instao problema de usaryaml-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
- Uma das alternativas era um fork de
- 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-rustdentro deinstapor meio de vendorinstaagora passou a ser uma combinação do código deinstacomyaml-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
- O código problemático não desapareceu; ele apenas foi movido para dentro de
1 comentários
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
Ao mesmo tempo, serde_yaml também foi marcado como unmaintained para evitar que todo mundo migrasse em massa para ele
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
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
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]
Também significa que as agências de classificação, que deveriam exercer algum papel regulador, já foram capturadas
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
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
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_partytem um responsável designado ou OWNERSIsso 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
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”
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”
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
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
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
É 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
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”
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
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
“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
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
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
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
É 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
Se não, não vejo o que estaria melhorando