TL;DR: a elttam publicou uma nova lista de notas de revisão de código em Go com problemas que passam fácil por scanners automáticos: overflow silencioso, httputil.ReverseProxy com Director, ponteiros compartilhados de url.URL, bytes nulos atravessando cgo, MarshalJSON que falha por causa do receiver e APIs JSON vulneráveis a CSRF. O valor do texto está menos em uma grande novidade da linguagem e mais em transformar detalhes conhecidos da biblioteca padrão em uma checklist prática para revisar serviços Go em produção.
Por que isso importa
Go costuma dar uma sensação saudável de segurança: tipos fortes, runtime com memória gerenciada, biblioteca padrão conservadora e ferramentas como go test, govulncheck e race detector. Isso ajuda muito, mas não elimina vulnerabilidades que nascem de semântica sutil, contrato de API mal entendido ou interação com navegadores, proxies e código C.
O artigo Golang code review notes II, de Zoltan Madarassy e Alex Brown, é interessante justamente por focar nesses pontos de revisão manual. Não é uma lista genérica de OWASP. São casos em que o código compila, passa em testes comuns e ainda assim pode criar bypass de autenticação, vazamento entre requisições ou parsing ambíguo.
Para quem mantém APIs em Go, a leitura combina bem com uma revisão periódica de segurança: buscar padrões no repositório, confirmar hipóteses com testes pequenos e trocar APIs antigas por alternativas mais explícitas quando elas existem.
Overflow e truncamento continuam sendo problemas
Go não entra em pânico quando um inteiro estoura. Em código de aplicação, isso muitas vezes é irrelevante. Em parsers, serializadores e protocolos binários, pode virar vulnerabilidade.
Um padrão perigoso é transformar o resultado de len, que é int, em um tipo menor sem checar limite:
func writeLength(buf *bytes.Buffer, payload []byte) {
n := len(payload)
if n > math.MaxUint32 {
return
}
binary.Write(buf, binary.BigEndian, uint32(n))
buf.Write(payload)
}
A parte importante não é o exemplo em si, mas a pergunta de revisão: algum tamanho controlado por entrada externa é convertido para uint16, uint32 ou int32 antes de ser gravado, alocado ou usado como limite? Se sim, a checagem precisa aparecer antes do cast, não depois.
ReverseProxy: prefira Rewrite para headers sensíveis
O ponto mais prático para serviços web é httputil.ReverseProxy. O artigo destaca um detalhe perigoso: quando Director adiciona headers sensíveis, headers hop-by-hop podem ser removidos depois. Um atacante pode abusar do header Connection para fazer o proxy descartar algo que o backend esperava receber, como X-Internal-Auth ou X-Forwarded-Proto.
Em revisões, procure construções como:
proxy := &httputil.ReverseProxy{
Director: func(r *http.Request) {
r.Header.Set("X-Internal-Auth", token)
},
}
Para código novo, a direção recomendada é usar Rewrite e httputil.ProxyRequest, que deixam mais claro o que acontece com a requisição de entrada e a requisição de saída. Isso reduz a chance de misturar headers controlados pelo cliente com headers de confiança adicionados pela infraestrutura.
Ponteiros de URL e estado compartilhado
url.Parse retorna *url.URL. Copiar a variável copia o ponteiro, não a estrutura. Em um handler HTTP, isso pode criar vazamento entre requisições se uma URL global for reutilizada e modificada.
var baseRedirect, _ = url.Parse("https://app.example/callback")
func handler(w http.ResponseWriter, r *http.Request) {
u := *baseRedirect
q := u.Query()
q.Set("request_id", r.URL.Query().Get("id"))
u.RawQuery = q.Encode()
http.Redirect(w, r, u.String(), http.StatusFound)
}
O detalhe é o u := *baseRedirect: ele copia o valor apontado. Em código real, a revisão deve procurar u := baseRedirect seguido de mutações como u.RawQuery = ..., u.Path = ... ou alterações em u.User.
Fronteiras com C e bytes nulos
Strings Go podem conter \x00. APIs em C, por outro lado, frequentemente tratam byte nulo como fim da string. Esse desencontro pode gerar bypass quando uma validação em Go vê admin\x00 como diferente de admin, mas uma camada C de autenticação, LDAP, PAM ou driver nativo interpreta apenas admin.
A regra de revisão é simples: antes de passar dados externos para cgo ou bibliotecas que cruzam para C, rejeite bytes nulos nos campos relevantes. Isso vale especialmente para nomes de usuário, caminhos, identificadores, filtros LDAP e credenciais.
JSON: receiver de MarshalJSON e CSRF
Outro ponto fácil de perder envolve encoding/json. Se MarshalJSON foi definido apenas em *T, serializar um T por valor pode não aplicar a lógica de mascaramento esperada. Para tipos usados em redaction, teste os dois caminhos: json.Marshal(v) e json.Marshal(&v).
O artigo também reforça que API JSON não é automaticamente imune a CSRF. Se o endpoint aceita corpo sem exigir Content-Type: application/json, usa cookies com SameSite=None e decodifica apenas o primeiro valor JSON deixando bytes extras no corpo, um formulário malicioso pode chegar mais longe do que deveria. Para serviços HTTP, vale combinar validação de Content-Type, política correta de cookies, proteção de origem quando aplicável e leitura completa do corpo.
Como transformar em checklist
Uma boa forma de usar o artigo é rodar buscas pequenas no repositório e abrir revisões focadas:
httputil.ReverseProxycomDirectorque adiciona headers de confiança;- casts de
len(...)ou valores externos para inteiros menores; *url.URLglobal ou compartilhado sendo mutado em handlers;MarshalJSONcom receiver de ponteiro em tipos que escondem dados sensíveis;- entrada externa atravessando cgo sem bloqueio de
\x00; - endpoints JSON autenticados por cookie sem checagem rígida de origem e conteúdo.
Isso complementa, não substitui, ferramentas automáticas. Scanners encontram CVEs conhecidas e alguns padrões estáticos; revisão humana ainda é melhor para entender confiança entre camadas.