cargo-geiger: Audite Unsafe em Rust | Rust Brasil

Use cargo-geiger para mapear unsafe no código Rust e nas dependências. Guia com interpretação, triagem, CI, Miri, supply chain, políticas e boas práticas.

Use cargo-geiger para descobrir onde existe unsafe no seu projeto Rust e no grafo de dependências antes de começar uma auditoria manual. O fluxo básico é instalar o subcomando, executá-lo na raiz do repositório e tratar o resultado como um mapa de revisão — não como um placar automático de segurança.

cargo install --locked cargo-geiger
cargo geiger

A ferramenta lista estatísticas relacionadas ao uso de Rust unsafe na crate atual e em suas dependências. Isso ajuda a responder perguntas importantes: o código do produto contém blocos unsafe? Uma crate transitiva concentra operações de baixo nível? Uma atualização aumentou a superfície que exige confiança? Quais pacotes devem receber revisão mais cuidadosa?

A interpretação correta é essencial. Encontrar unsafe não prova vulnerabilidade, e encontrar zero ocorrências não prova segurança. Unsafe é parte legítima da linguagem para FFI, alocadores, runtimes, drivers, bindings, acesso ao sistema operacional e abstrações eficientes. O valor do cargo-geiger está em tornar essa superfície visível e comparável.

Resposta rápida: como usar cargo-geiger

EtapaComando ou açãoObjetivo
Instalarcargo install --locked cargo-geigerAdicionar o subcomando localmente
Executarcargo geigerAnalisar o pacote e as dependências
Confirmar opçõescargo geiger --helpVer flags da versão instalada
Localizar origemcargo tree -i nome-da-crateDescobrir quem introduziu uma dependência
Revisar advisoriescargo auditConsultar vulnerabilidades conhecidas
Exercitar unsafecargo +nightly miri testProcurar classes de comportamento indefinido nos testes
Revisar políticacargo deny checkValidar fontes, licenças, bans e advisories configurados
Comparar mudançasalvar relatórios antes/depoisDetectar crescimento da superfície de unsafe

O comando e o formato do relatório podem evoluir. Antes de criar parsing, thresholds ou automação de CI, fixe uma versão homologada e leia cargo geiger --help no ambiente que executará a análise.

O que unsafe significa em Rust

Rust seguro impede várias classes de erro por meio do sistema de tipos, ownership, borrowing e verificação de lifetimes. Ainda assim, algumas operações não podem ser provadas pelo compilador com as regras normais. Um bloco ou função unsafe permite assumir responsabilidade manual por invariantes específicas.

Entre as operações associadas a unsafe estão:

  • desreferenciar raw pointers;
  • chamar funções unsafe;
  • acessar determinados estados globais mutáveis;
  • implementar traits unsafe;
  • acessar campos de unions;
  • atravessar fronteiras FFI nas quais o compilador não conhece o contrato externo.

O restante do sistema de tipos continua ativo. unsafe não transforma Rust em uma linguagem sem regras; ele cria uma fronteira em que o programador precisa provar condições que o compilador não consegue verificar.

O guia de Unsafe Rust explica essas operações em profundidade. Para uma auditoria, a pergunta não é apenas “quantas linhas unsafe existem?”, mas quais invariantes elas assumem, quem as mantém e qual API segura depende delas?

Instalando cargo-geiger

A instalação padrão documentada pelo projeto é:

cargo install --locked cargo-geiger

Depois, confirme a versão e a ajuda local:

cargo geiger --version
cargo geiger --help

Em alguns ambientes, a compilação da ferramenta pode depender da disponibilidade de OpenSSL no sistema. O projeto também documenta uma instalação que compila e vincula uma cópia vendorizada:

cargo install --locked cargo-geiger --features vendored-openssl

Não adote essa variante automaticamente em todo ambiente. Em estações e runners administrados, uma biblioteca de sistema atualizada pode ser preferível. Em uma imagem de CI mínima, a opção vendorizada pode simplificar a instalação. A decisão deve considerar tempo de build, política de dependências nativas e reprodutibilidade.

Para pipelines, evite instalar “a versão mais nova disponível” em toda execução sem controle. Fixe a versão validada pelo time, porque mudanças no analisador ou na apresentação podem quebrar scripts e alterar a baseline sem que o código do produto tenha mudado.

Executando a primeira análise

Entre no diretório que contém o Cargo.toml:

cd meu-projeto
cargo geiger

Faça a primeira execução em um repositório limpo e registre o contexto:

git status --short
rustc --version
cargo geiger --version
cargo geiger

Essas informações ajudam a reproduzir o resultado. O grafo analisado pode variar com:

  • versão do compilador e do Cargo;
  • conteúdo do Cargo.lock;
  • features ativadas;
  • target e sistema operacional;
  • membros selecionados em um workspace;
  • dependências de desenvolvimento e build;
  • configuração do ambiente.

Se o projeto não versiona o Cargo.lock, duas execuções em momentos diferentes podem resolver conjuntos distintos de dependências. Para aplicações, serviços e CLIs, normalmente vale versionar o lockfile e usar builds com --locked conforme a política do repositório.

Como interpretar o relatório sem tirar conclusões erradas

O relatório aponta ocorrências e estatísticas de unsafe, mas não conhece o contrato completo de cada crate. Uma dependência com operações de baixo nível pode ter revisão rigorosa, testes extensos e uma API pública segura. Outra pode ter poucas ocorrências, mas invariantes frágeis e manutenção abandonada.

Classifique os resultados em camadas.

1. Código local do produto

Unsafe escrito pela própria equipe merece uma revisão direta porque você controla a implementação. Para cada bloco ou função, verifique:

  • existe uma alternativa safe na biblioteca padrão ou em uma crate madura?
  • o bloco possui o menor escopo possível?
  • as pré-condições estão documentadas em uma seção # Safety?
  • a API pública impede que chamadores safe violem invariantes?
  • existem testes de fronteira e entradas adversas?
  • Miri consegue executar a parte relevante da suíte?
  • outro desenvolvedor revisou a lógica de ponteiros, aliasing, inicialização e lifetimes?

Um bloco curto não é automaticamente simples. Uma única conversão incorreta pode introduzir comportamento indefinido. Da mesma forma, uma implementação longa pode ser bem encapsulada e auditada. Contagem ajuda a localizar; revisão determina o risco.

2. Dependências diretas

Para crates declaradas no seu Cargo.toml, você pode avaliar:

  • por que a dependência foi escolhida;
  • se o unsafe está ligado a uma feature opcional;
  • se existe uma alternativa com superfície menor;
  • se a crate documenta sua política de segurança;
  • se recebe atualizações e correções;
  • se há advisories conhecidos;
  • se a API usada pelo projeto atravessa a parte unsafe.

Não troque uma crate madura apenas para reduzir uma contagem. Reimplementar parsing, criptografia, concorrência ou FFI dentro do produto pode aumentar o risco real, mesmo que o relatório fique aparentemente menor.

3. Dependências transitivas

Quando uma crate inesperada aparece, descubra quem a trouxe:

cargo tree -i nome-da-crate

O guia de cargo tree mostra como investigar caminhos, versões duplicadas e features. O resultado pode revelar que:

  • uma feature padrão ativou um backend nativo;
  • uma dependência de desenvolvimento trouxe tooling adicional;
  • duas versões da mesma crate estão resolvidas;
  • um pacote do workspace introduziu a dependência para todos;
  • o componente existe apenas em determinado target.

A ação correta pode ser desativar uma feature, atualizar a dependência direta, isolar um pacote ou simplesmente documentar por que a cadeia é aceita.

Unsafe não é sinônimo de vulnerabilidade

Um erro comum é tratar qualquer ocorrência como defeito. Isso produz incentivos ruins: esconder o uso, substituir bibliotecas testadas por código próprio ou escolher abstrações menos adequadas apenas para chegar a zero.

Unsafe é esperado em áreas como:

  • bindings para C e APIs do sistema operacional;
  • runtimes assíncronos e primitivas de sincronização;
  • estruturas de dados com layout especializado;
  • drivers e sistemas embarcados;
  • SIMD e otimizações específicas de arquitetura;
  • alocação e gerenciamento de memória;
  • bibliotecas que encapsulam ponteiros em APIs safe;
  • kernels, hipervisores e componentes no_std.

A metáfora do nome “Geiger” é útil: a ferramenta detecta uma superfície que pede atenção, como um contador detecta radiação. Ela não explica sozinha a fonte, a dose aceitável ou a proteção existente.

Uma política madura evita dois extremos:

  • “Unsafe é sempre ruim.” Ignora que a própria infraestrutura de baixo nível precisa dele.
  • “Rust garante segurança, então unsafe não importa.” Ignora que as garantias dependem da correção das fronteiras unsafe.

Comparando relatórios antes e depois de uma mudança

Uma aplicação prática é revisar o impacto de uma atualização de dependências. Salve uma baseline:

cargo geiger > /tmp/geiger-antes.txt

Atualize a crate ou o lockfile de forma controlada e gere outro relatório:

cargo geiger > /tmp/geiger-depois.txt
diff -u /tmp/geiger-antes.txt /tmp/geiger-depois.txt

O diff pode indicar:

  • nova dependência que usa unsafe;
  • remoção de uma implementação antiga;
  • troca de backend por feature;
  • crescimento no código local;
  • mudança ampla causada por uma atualização aparentemente pequena.

Não transforme o texto bruto em uma API estável sem verificar o suporte da versão usada. Para revisão humana dentro do mesmo ambiente, o diff já é valioso. Para dashboards e gates duradouros, avalie os formatos e bibliotecas serializáveis expostos pelo ecossistema do projeto, fixando versões e testes para o parser.

Combine essa comparação com cargo-outdated e revisão do Cargo.lock. O objetivo é explicar a mudança, não impedir toda evolução do grafo.

cargo-geiger, Miri, cargo-audit e cargo-vet

Essas ferramentas são complementares porque observam dimensões diferentes.

FerramentaPergunta principal
cargo-geigeronde existe uso estatístico de unsafe?
Mirios testes executados violam determinadas regras do modelo de memória?
cargo-audito lockfile contém crates com advisories conhecidos?
cargo-vetquais dependências receberam auditorias ou critérios de confiança?
cargo-denyo grafo viola políticas de licença, fonte, ban ou advisory?
cargo-treequal caminho introduziu cada dependência?

Miri

Miri interpreta MIR e consegue detectar determinadas classes de comportamento indefinido nos caminhos exercitados pelos testes:

rustup +nightly component add miri
cargo +nightly miri test

Ele não prova ausência de bugs em caminhos não executados e nem todo código, FFI ou operação de plataforma é compatível com o ambiente interpretado. Ainda assim, é uma ferramenta importante para testar abstrações unsafe. Veja o guia de Miri em Rust.

cargo-audit

cargo-audit consulta advisories conhecidos para versões presentes no lockfile. Uma crate pode usar unsafe e não ter advisory; outra pode ser escrita sem unsafe e possuir uma vulnerabilidade lógica ou de protocolo. Por isso, rode cargo-audit separadamente.

cargo-vet

O cargo-vet ajuda organizações a registrar auditorias e critérios de confiança sobre dependências. cargo-geiger pode orientar prioridade: componentes de baixo nível e crates transitivas críticas merecem uma trilha de confiança mais explícita.

cargo-deny e SBOM

O guia de supply chain Rust com cargo-deny e SBOM amplia a análise para licenças, fontes, versões banidas e inventário. Unsafe é apenas uma dimensão da dependência; procedência e manutenção também importam.

Política para código local: forbid(unsafe_code) ou unsafe revisado

Projetos que não precisam de operações de baixo nível podem declarar uma regra forte:

#![forbid(unsafe_code)]

Isso impede que o código sob aquela configuração use unsafe. É uma boa escolha para aplicações de domínio, serviços CRUD e bibliotecas que conseguem cumprir seu contrato usando apenas Rust safe.

Mas forbid(unsafe_code) não significa que todas as dependências sejam livres de unsafe. O atributo governa o código em que é aplicado, não reescreve o ecossistema inteiro. É justamente aí que cargo-geiger acrescenta contexto sobre o grafo.

Para projetos que precisam de unsafe, uma política possível é:

  1. negar unsafe por padrão em módulos comuns;
  2. concentrar exceções em um módulo pequeno;
  3. documentar invariantes com # Safety;
  4. oferecer uma API safe aos consumidores;
  5. exigir revisão adicional para mudanças no módulo;
  6. executar Miri e testes de propriedade quando aplicável;
  7. acompanhar o relatório do cargo-geiger nas atualizações.

A meta não é esconder unsafe, mas conter responsabilidade.

Colocando cargo-geiger na CI

Comece com um job informativo. Ele gera visibilidade sem bloquear o time antes de existir uma baseline entendida.

name: unsafe-audit

on:
  pull_request:
  workflow_dispatch:

jobs:
  cargo-geiger:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable

      - name: Instalar cargo-geiger
        run: cargo install cargo-geiger --locked

      - name: Gerar relatório de unsafe
        run: cargo geiger

Esse exemplo funciona como ponto de partida conceitual para GitHub Actions e Gitea Actions. Em produção:

  • fixe a versão da ferramenta;
  • use a estratégia de OpenSSL adequada ao runner;
  • preserve o relatório como artefato se o log for insuficiente;
  • execute com o conjunto de features e targets relevante;
  • defina timeout para instalações e análises;
  • valide que o comando cobre os membros esperados do workspace;
  • não interprete qualquer número positivo como falha.

Depois de algumas execuções, o time pode adotar uma política de mudança:

  • novo unsafe no código local exige justificativa e revisão;
  • nova dependência de baixo nível exige triagem;
  • crescimento inesperado no relatório exige explicação;
  • exceções aceitas ficam documentadas;
  • pacotes críticos recebem Miri, fuzzing ou auditoria adicional.

Um gate baseado apenas em “contagem deve ser zero” funciona para poucos projetos. Um gate baseado em mudança não explicada costuma ser mais útil em sistemas reais.

Workspaces, features e targets

O resultado precisa representar o artefato que você entrega. Em um workspace, uma CLI administrativa pode depender de componentes diferentes da biblioteca pública ou do serviço principal.

Antes de automatizar, verifique na ajuda da versão instalada como selecionar pacotes e opções de Cargo. Também compare as configurações suportadas pelo projeto:

  • features padrão;
  • --no-default-features quando essa combinação é válida;
  • features de TLS, banco, runtime ou bindings;
  • targets Linux, Windows, macOS, WASM e embedded;
  • dependências normais, de desenvolvimento e de build.

Uma análise no notebook Linux não representa necessariamente o binário Windows nem o firmware no_std. Dependências condicionais podem concentrar unsafe justamente no target que não foi analisado.

Use cargo tree --target ... e a matriz de CI para entender as diferenças. Para testar combinações de features, o cargo-hack ajuda a manter a matriz explícita.

Triagem prática de uma dependência com unsafe

Quando o relatório destacar uma crate, siga um processo reproduzível.

1. Descubra o caminho

cargo tree -i crate-destacada

2. Entenda a função no produto

Ela implementa TLS, parsing, FFI, compressão, acesso ao kernel, armazenamento ou apenas uma ferramenta de desenvolvimento?

3. Revise features

A crate entra por uma feature necessária? Existe backend alternativo? Desativar defaults remove funcionalidade importante?

4. Consulte manutenção e advisories

Verifique releases, changelog, repositório, issues relevantes e RustSec. Não confunda ausência de advisory com auditoria concluída.

5. Procure encapsulamento

O unsafe está restrito a uma camada pequena com API safe? As invariantes são documentadas? Existem testes, fuzzing e uso de Miri?

6. Compare alternativas pelo risco total

Considere maturidade, performance, compatibilidade, manutenção, licença e custo de migração. “Menos unsafe” é um critério; não é o único.

7. Registre a decisão

Uma issue, ADR ou comentário de dependência pode explicar por que a crate foi aceita, quais controles existem e quando a escolha será revista.

Armadilhas comuns

Usar o relatório como ranking de crates “boas” e “ruins”

Contagem não mede qualidade de invariantes, revisão ou impacto no produto. Use-a para prioridade, não para reputação.

Remover uma dependência madura e reescrever o código

Você pode trocar unsafe auditado por bugs lógicos, parsing incorreto ou uma nova implementação sem testes. Compare risco total.

Analisar somente o código local

Grande parte da superfície de baixo nível pode estar nas transitivas. O grafo resolvido importa.

Ignorar features e targets

O relatório pode variar de acordo com a configuração. Audite o que realmente é compilado e entregue.

Falhar a CI sem baseline

A equipe recebe um número vermelho sem saber se houve regressão. Comece informativo, classifique e só então defina gates.

Tratar zero unsafe como prova de segurança

Erros de autenticação, autorização, criptografia, lógica, protocolo e configuração podem existir em Rust totalmente safe.

Tratar qualquer unsafe como vulnerabilidade

Unsafe pode ser necessário e corretamente encapsulado. A investigação precisa chegar às invariantes e ao uso concreto.

Checklist de auditoria

  • instalar uma versão controlada do cargo-geiger;
  • executar na raiz correta e registrar toolchain;
  • distinguir código local, dependências diretas e transitivas;
  • localizar caminhos com cargo tree -i;
  • revisar features e targets realmente entregues;
  • verificar advisories com cargo-audit;
  • examinar manutenção, documentação e política de segurança;
  • conferir se unsafe está encapsulado por APIs safe;
  • exigir documentação # Safety no código local;
  • executar Miri nos testes compatíveis;
  • usar proptest ou fuzzing em parsers e invariantes complexas;
  • comparar relatórios antes e depois de atualizações;
  • documentar exceções e decisões de dependência;
  • começar a CI em modo informativo;
  • bloquear apenas violações de uma política compreendida.

cargo-geiger para carreira e portfólio Rust

Em um projeto de portfólio, executar a ferramenta é menos importante do que explicar a decisão. Um README técnico pode mostrar:

  • baseline do código local e das dependências;
  • uma crate transitiva investigada com cargo tree -i;
  • justificativa para uma fronteira FFI;
  • módulo que encapsula raw pointers em API safe;
  • testes com Miri;
  • cargo-audit e cargo-deny na CI;
  • política para novos blocos unsafe;
  • comparação do relatório após uma atualização.

Esse material demonstra repertório de engenharia de sistemas, segurança e supply chain. Ele é relevante para vagas Rust em infraestrutura, embedded, segurança, bancos de dados, runtimes, developer tooling e plataformas de alta performance. O guia de carreira em segurança com Rust ajuda a conectar essas ferramentas a projetos e responsabilidades profissionais.

Não apresente a conclusão “minhas dependências são seguras porque o relatório tem pouco unsafe”. Uma formulação madura seria: “mapeei a superfície, investiguei os caminhos críticos, documentei invariantes, executei controles complementares e defini quando a análise precisa ser repetida”.

Perguntas frequentes

Para que serve cargo-geiger?

Ele lista estatísticas relacionadas ao uso de unsafe no pacote e nas dependências. O relatório orienta auditoria, comparação e triagem de supply chain.

Como instalar?

cargo install --locked cargo-geiger
cargo geiger

Se o ambiente precisar de OpenSSL vendorizado, consulte a variante documentada pelo projeto e valide o impacto no runner.

Uma crate com unsafe é insegura?

Não. Avalie por que ela usa unsafe, como as invariantes são encapsuladas, maturidade, testes, manutenção, advisories e o caminho pelo qual entra no produto.

cargo-geiger substitui Miri?

Não. cargo-geiger mapeia ocorrências; Miri executa caminhos de teste e procura determinadas violações do modelo de memória. Use os dois quando o risco justificar.

Devo bloquear a CI?

Comece com relatório informativo. Depois bloqueie mudanças que violem uma política explícita — por exemplo, novo unsafe local sem documentação e revisão — em vez de reprovar qualquer contagem positiva.

Conclusão

cargo-geiger torna visível uma parte crítica da confiança em Rust: as fronteiras onde o compilador delega invariantes ao programador e as dependências que implementam operações de baixo nível.

O fluxo recomendado é simples: gere uma baseline, separe código local de dependências, encontre os caminhos com cargo tree, revise contexto e combine o resultado com Miri, RustSec e políticas de supply chain. Não persiga zero como métrica universal e não ignore crescimento sem explicação.

Comece com:

cargo install --locked cargo-geiger
cargo geiger
cargo tree -i crate-que-voce-quer-investigar

Depois documente uma decisão real. Essa é a diferença entre apenas executar uma ferramenta e construir uma prática de auditoria: o relatório vira entrada para revisão, testes e manutenção contínua, não um selo automático de segurança.