govulncheck é a ferramenta oficial do ecossistema Go para responder uma pergunta mais útil do que “existe algum CVE em alguma dependência?”: o seu código realmente chama o caminho vulnerável? Em projetos Go com APIs, workers, CLIs e serviços internos, essa diferença muda a rotina de segurança. Um scanner genérico pode apontar dezenas de alertas em módulos transitivos. O govulncheck cruza seu código, suas dependências e a base de vulnerabilidades do Go para priorizar o que tem caminho de chamada alcançável.
Isso não significa ignorar dependência velha. Significa separar urgência real de ruído. Em um time brasileiro pequeno, onde a mesma pessoa escreve feature, revisa PR, opera deploy e responde incidente, o custo de triagem importa. Uma rotina boa combina go mod tidy, atualização planejada, testes, CI e govulncheck como portão de segurança proporcional ao risco.
Este guia mostra como usar govulncheck em projetos Go, como interpretar o resultado, como colocar no CI sem travar o time por falso positivo e como decidir upgrade, mitigação ou aceite temporário. Ele complementa Go Modules na prática, TDD e CI/CD em Go, segurança em Go e GoReleaser com checksums e SBOM.
Por que scanner genérico não basta
Muitos scanners olham para go.mod, go.sum ou para o SBOM e comparam versões contra bases de CVEs. Isso é útil, mas incompleto. Se uma dependência transitiva tem uma vulnerabilidade em um pacote que seu binário nunca importa, o risco é diferente de uma falha em crypto, net/http, parser de JWT, template HTML ou client exposto a dados de usuário.
Go tem uma vantagem técnica: o ecossistema consegue analisar pacotes, símbolos e chamadas com bastante precisão. O govulncheck usa a base pública de vulnerabilidades do Go e tenta descobrir se o programa chama funções afetadas. O resultado fica mais perto da pergunta operacional: “preciso agir antes do próximo deploy?”
Exemplo de diferença:
- Um módulo vulnerável existe no grafo, mas nenhum pacote afetado é importado.
- Um pacote afetado é importado, mas a função vulnerável não é chamada.
- A função vulnerável está no caminho de chamada do seu código.
- A vulnerabilidade está na biblioteca padrão usada pela versão de Go do build.
Os quatro cenários pedem respostas diferentes. Tratar todos como emergência cria fadiga. Tratar todos como detalhe cria risco acumulado.
Instalação e primeiro uso
Instale a ferramenta oficial:
go install golang.org/x/vuln/cmd/govulncheck@latest
Depois rode na raiz do módulo:
govulncheck ./...
Em um monorepo com vários módulos, rode por módulo ou automatize a descoberta de diretórios com go.mod. O importante é executar no mesmo contexto em que o projeto compila: variáveis de build, tags e versão de Go podem mudar o conjunto de pacotes analisados.
Para ver mais detalhes:
govulncheck -v ./...
Em projetos com tags específicas:
govulncheck -tags "prod,postgres" ./...
Se seu serviço usa código condicionado por //go:build, não esqueça esse ponto. Um scanner rodando sem as tags de produção pode deixar de enxergar justamente o caminho usado no deploy.
Como ler o resultado
O resultado do govulncheck normalmente traz três blocos importantes: o identificador da vulnerabilidade, os módulos/versões afetados e a cadeia de chamada quando há código alcançável.
A parte mais importante é a call stack. Se ela aparece, existe um caminho do seu código até uma função vulnerável. Algo como:
Vulnerability #1: GO-2026-1234
Example issue in module example.org/lib
Found in: example.org/[email protected]
Fixed in: example.org/[email protected]
Example traces found:
main.handleUpload calls parser.Parse
parser.Parse calls example.org/lib.Decode
Nesse cenário, a triagem é objetiva:
- Existe versão corrigida?
- O upgrade quebra API?
- Há teste cobrindo o caminho afetado?
- O endpoint recebe entrada externa?
- Existe mitigação temporária, como bloquear formato, limitar tamanho ou desativar feature?
Se o relatório aponta módulo vulnerável sem caminho alcançável, ainda vale planejar atualização, mas a urgência cai. Em produto com deploy frequente, o melhor caminho costuma ser atualizar no próximo ciclo normal e manter o scanner verde.
Biblioteca padrão também entra na conta
Nem toda vulnerabilidade vem de dependência externa. O próprio toolchain Go e a biblioteca padrão recebem correções de segurança. Se uma falha afeta net/http, crypto/x509, archive/zip, html/template ou outro pacote padrão, a correção pode exigir atualizar a versão de Go usada no build.
Por isso, o pipeline precisa deixar claro qual Go compila o binário. Não basta rodar govulncheck localmente com Go novo se o CI ainda usa uma imagem antiga. Verifique:
go version
go env GOVERSION
Em Docker, prefira imagem de build com versão explícita:
FROM golang:1.25-bookworm AS build
E evite deixar latest como contrato de produção. latest muda sem review. Use atualização intencional, changelog e testes.
Esse cuidado conversa com GoReleaser: release confiável precisa dizer qual commit, qual versão de Go, quais dependências e qual artefato foram publicados.
CI sem virar bloqueio burro
O govulncheck deve rodar no CI, mas a política precisa ser honesta. Em projetos novos ou pequenos, um job que falha quando há vulnerabilidade alcançável é razoável. Em bases grandes, talvez seja necessário começar em modo informativo por uma semana, limpar o backlog e só então tornar obrigatório.
Um workflow simples de GitHub Actions ficaria assim:
name: security
on:
pull_request:
push:
branches: [main]
jobs:
govulncheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- run: go test ./...
- run: go install golang.org/x/vuln/cmd/govulncheck@latest
- run: govulncheck ./...
Se você já usa TDD e CI/CD em Go, coloque esse job perto de testes e lint. A ordem prática costuma ser:
gofmtougofmt -wverificado.go test ./....go vet ./...ou lint.govulncheck ./....- Build ou empacotamento.
Rodar teste antes ajuda a distinguir projeto quebrado de vulnerabilidade. Se o módulo nem compila, o relatório de segurança perde valor.
Política de triagem para PRs
Sem política, cada alerta vira discussão repetida. Uma política simples pode caber no README:
| Situação | Ação padrão |
|---|---|
| Vulnerabilidade alcançável com fix disponível | Atualizar dependência ou Go antes do merge |
| Vulnerabilidade alcançável sem fix | Mitigar, documentar risco e abrir issue datada |
| Módulo vulnerável sem caminho alcançável | Atualizar no ciclo normal, salvo pacote crítico |
| Scanner falhou por indisponibilidade externa | Reexecutar; não ignorar sem evidência |
| Dependência nova traz alerta | Bloquear PR até justificar ou trocar dependência |
O ponto é tirar subjetividade do calor do deploy. Em sistemas que processam pagamento, autenticação, documento, saúde, dado pessoal ou upload de usuário, suba o nível. Em ferramentas internas sem entrada externa, ainda trate segurança com respeito, mas priorize proporcionalmente.
Atualização segura de dependências
Ao corrigir um alerta, não rode go get -u ./... sem pensar. Isso atualiza mais do que você pretendia e pode criar mudança grande demais. Prefira upgrade direcionado:
go get example.org/[email protected]
go mod tidy
go test ./...
govulncheck ./...
Depois revise o diff de go.mod e go.sum. Se muitas dependências transitivas mudaram, entenda por quê. Em bibliotecas de segurança, HTTP, banco ou serialização, leia release notes quando houver.
Para atualizar a versão de Go do módulo:
go mod edit -go=1.25
go mod tidy
go test ./...
govulncheck ./...
Também atualize a imagem de CI, Dockerfile, .tool-versions, mise.toml, goenv ou qualquer arquivo que fixe versão. Metade das falhas de segurança em build vem de atualizar um lugar e esquecer outro.
O que govulncheck não resolve
Govulncheck é excelente, mas não substitui engenharia de segurança. Ele não prova que sua API valida autorização corretamente, que seu segredo não vazou, que seu bucket está privado ou que seu SQL está protegido contra lógica de negócio ruim.
Ele também não cobre tudo que um scanner de container cobre. Um binário Go pode estar limpo, mas a imagem Docker pode trazer pacote do sistema vulnerável, shell desnecessário, certificado antigo ou usuário root. Para serviços em produção, combine:
- Govulncheck para código Go e dependências Go.
- Scanner de imagem para base Docker.
- SBOM para rastrear artefatos.
- Revisão de segredos e configuração.
- Testes de autorização e validação de entrada.
- Logs e métricas para detectar abuso.
Se você trabalha com múltiplas stacks, compare com a rotina de auditoria de dependências Python. A linguagem muda, mas a disciplina é parecida: reduzir ruído, corrigir caminho explorável e manter o build reproduzível.
Checklist para produção
Use este checklist em projetos Go que já rodam fora da sua máquina:
-
govulncheck ./...roda localmente. - O CI usa a mesma versão de Go esperada para produção.
- Tags de build relevantes são passadas ao scanner.
- Vulnerabilidade alcançável bloqueia merge ou deploy.
- Dependência nova passa por revisão mínima de manutenção e licença.
- Atualizações usam
go get pacote@versão, não upgrade global sem motivo. -
go.modego.sumsão revisados no PR. - Imagem Docker não depende de
latestsem controle. - Release registra versão de Go, commit e artefatos.
- Existe dono claro para triagem de alertas de segurança.
Esse processo não precisa ser pesado. O erro é tratar segurança como auditoria anual. Em Go, a ferramenta é rápida o suficiente para virar hábito de PR.
Conclusão
Govulncheck ajuda projetos Go a sair do teatro de segurança e chegar em uma triagem mais útil: quais vulnerabilidades estão realmente no caminho do meu código? Use a ferramenta para reduzir ruído, mas não para reduzir responsabilidade.
A rotina saudável é simples: dependências explícitas, versão de Go fixada, testes passando, govulncheck no CI, upgrades pequenos e release rastreável. Isso protege o usuário, reduz surpresa em produção e ainda melhora sua maturidade como pessoa desenvolvedora Go.
Se o seu projeto já tem go test, go vet, build reproduzível e govulncheck rodando, você está bem à frente de muita base que só descobre vulnerabilidade quando o alerta vira incidente.