Validação de Entrada e Tratamento Seguro de HTML/JSON
Todo manipulador HTTP e gRPC que aceita entrada do usuário deve validar a estrutura, o tamanho e a semântica antes de acessar bancos de dados, shells ou saída HTML.
Busque em todas as páginas da documentação
Todo manipulador HTTP e gRPC que aceita entrada do usuário deve validar a estrutura, o tamanho e a semântica antes de acessar bancos de dados, shells ou saída HTML.
Os parsers do Go ajudam com a sintaxe, mas não impõem sua política de segurança.
A validação de entrada em Go começa na borda do transporte (limites de tamanho do corpo, tipos de conteúdo) e continua através da decodificação tipada (encoding/json, json.Decoder, protobuf) e verificações explícitas de regras (comprimento, formato, listas de permissão).
A codificação de saída segura depende do contexto: HTML requer html/template, APIs JSON precisam de Content-Type correto e sem HTML embutido em campos de string sem codificação.
Falhas de injeção (SQL, comando, template, log) compartilham uma causa raiz: tratar dados não confiáveis como código ou estrutura.
Valide cedo, parametrize consultas e falhe com mensagens de erro seguras.
Cartão de receita de referência rápida - pronto para copiar e colar.
type CreateUser struct {
Email string `json:"email"`
Age int `json:"age"`
}
func decodeCreateUser(r *http.Request) (CreateUser, error) {
r.Body = http.MaxBytesReader(nil, r.Body, 1<<20)
dec := json.NewDecoder(r.Body)
dec.DisallowUnknownFields()
var u CreateUser
if err := dec.Decode(&u); err != nil {
return u, err
}
if !strings.Contains(u.Email, "@") || u.Age < 0 || u.Age > 150 {
return u, errors.New("invalid user")
}
return u, nil
}Quando usar isso:
package main
import (
"database/sql"
"encoding/json"
"errors"
"html/template"
"net/http"
"regexp"
"strings"
_ "modernc.org/sqlite"
)
var emailRe = regexp.MustCompile(`^[^@\s]+@[^@\s]+\.[^@\s]+$`)
type Profile struct {
DisplayName string `json:"display_name"`
Email string `json:"email"`
}
func validateProfile(p Profile) error {
p.DisplayName = strings.TrimSpace(p.DisplayName)
if len(p.DisplayName) == 0 || len(p.DisplayName) > 64 {
return errors.New("invalid display name")
}
if !emailRe.MatchString(p.Email) {
return errors.New("invalid email")
}
return nil
}
func createProfileHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, 64<<10)
dec := json.NewDecoder(r.Body)
dec.DisallowUnknownFields()
var p Profile
if err := dec.Decode(&p); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
if err := validateProfile(p); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
_, err := db.Exec(
"INSERT INTO profiles (display_name, email) VALUES (?, ?)",
p.DisplayName, p.Email,
)
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
}
}
func renderProfileHandler() http.HandlerFunc {
tmpl := template.Must(template.New("page").Parse(`<h1>{{.}}</h1>`))
return func(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if len(name) > 64 {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
tmpl.Execute(w, name)
}
}
func main() {
db, _ := sql.Open("sqlite", ":memory:")
mux := http.NewServeMux()
mux.HandleFunc("POST /profiles", createProfileHandler(db))
mux.HandleFunc("GET /view", renderProfileHandler())
http.ListenAndServe(":8080", mux)
}O que isso demonstra:
DisallowUnknownFields para contratos de API estritos.?, não formatação de string.html/template com escape automático para exibição de parâmetros de consulta.json.Unmarshal e json.Decoder mapeiam JSON para tipos Go; valores zero preenchem campos ausentes, a menos que você use ponteiros para detecção opcional.go-playground/validator, funções personalizadas) são executadas após a decodificação para impor formatos, intervalos e regras entre campos.DisallowUnknownFields rejeita chaves JSON extras - útil para APIs públicas; às vezes relaxado para APIs internas compatíveis com versões futuras.html/template lida com o texto do elemento, enquanto contextos JavaScript ou URL precisam de regras mais rigorosas ou CSP.db.Query("SELECT * FROM users WHERE id = ?", id) nunca interpola strings do usuário no texto da consulta.| Camada | Verificação | Exemplo |
|---|---|---|
| Transporte | Tamanho, tipo de conteúdo | MaxBytesReader, rejeitar não application/json |
| Sintaxe | Sucesso da análise | json.Decoder.Decode, protobuf proto.Unmarshal |
| Esquema | Tipos, campos obrigatórios | Tags de struct, DisallowUnknownFields |
| Semântica | Regras de negócio | Formato de e-mail, lista de permissão de enumeração, propriedade |
| Autorização | Quem pode definir este campo | Remover is_admin de DTOs de criação públicos |
// Evite atribuição em massa: use DTOs separados para criar vs. atualizar
type PublicCreateOrder struct {
SKU string `json:"sku"`
Quantity int `json:"quantity"`
// sem Status, sem UserID - o servidor define esses
}Age: -1 impossível ou Role: "admin". Sempre execute validação semântica.is_admin, owner_id). Use DTOs de entrada.html/template ou retorne apenas JSON.fmt.Sprintf("SELECT * FROM t WHERE id = %s", id) é injetável. Use placeholders sempre.err.Error() de drivers de banco de dados vaza dicas de esquema. Registre internamente; responda com mensagens genéricas."" e campo omitido ambos se tornam "" em campos string. Use ponteiros ou sql.NullString quando a distinção for importante.| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
Tags de struct go-playground/validator | Grandes conjuntos de DTOs com formatos padrão | Você precisa de regras de domínio que as tags não expressam |
| OpenAPI + geração de código | APIs públicas baseadas em contrato | Pequenos manipuladores internos onde a geração de código é pesada |
| Protobuf + protovalidate | gRPC com esquemas fortes | Serviços REST simples apenas JSON |
| WAF na entrada | Assinaturas de ataque amplas | Substituir a validação no nível do aplicativo (nunca é suficiente sozinha) |
Decoder transmite e suporta DisallowUnknownFields por solicitação sem alocar o corpo bruto completo.
Unmarshal é bom para corpos pequenos depois que você já limitou o tamanho com MaxBytesReader.
Habilite para APIs públicas versionadas onde campos inesperados sinalizam bugs do cliente ou sondagens.
Serviços internos podem permitir campos desconhecidos para compatibilidade futura se você documentar a política.
Leia com r.URL.Query().Get ou variáveis de caminho do mux, depois valide comprimento, regex e listas de permissão antes de usar.
Nunca passe segmentos de caminho brutos para comandos SQL ou de shell.
Ele faz o escape automático do texto do elemento HTML.
Incorporar dados do usuário em <script>, manipuladores de eventos ou URLs javascript: ainda requer CSP e regras de entrada rigorosas.
Use uma forma fixa como {"error":"bad request"} sem traces de pilha ou mensagens do driver.
Registre o erro real com ID da solicitação no lado do servidor.
Protobuf impõe tipos na transmissão, não regras de negócio.
Adicione validação em manipuladores ou use protovalidate / verificações personalizadas após a descompactação.
Valide URLs contra uma lista de permissão de hosts e esquemas.
Bloqueie IPs de loopback, metadados e intervalos privados antes de http.Get.
A validação do cliente melhora a experiência do usuário.
A validação do servidor é obrigatória - os clientes podem ser contornados completamente.
Limite o tamanho, detecte o tipo MIME pelo conteúdo e não pela extensão, armazene fora da raiz da web e escaneie se a política exigir.
Nunca execute conteúdo carregado.
Normalize e compare e-mails e nomes de usuário com uma política definida de NFC/NFKC antes das verificações de exclusividade.
Documente a regra de normalização para evitar casos de borda de contas duplicadas.
Regex captura lixo óbvio; a validação completa de e-mail RFC é complexa.
Combine regex razoável com fluxos de confirmação para contas de alto risco.
Testes orientados por tabela com valores de limite: vazio, comprimento máximo, enums inválidos, chaves JSON desconhecidas e metacaracteres SQL em campos de string.
Versões da Stack: Esta página foi escrita para Go 1.26.x (GC padrão Green Tea, go fix modernizers - verifique o patch na compilação), chi (mais recente - verifique na compilação), gin (mais recente - verifique na compilação), echo (mais recente - verifique na compilação), google.golang.org/grpc (mais recente - verifique na compilação), sigs.k8s.io/controller-runtime (mais recente - verifique na compilação), kubebuilder (mais recente - verifique na compilação), tinygo (mais recente - verifique os alvos de placa na compilação), wazero (mais recente - verifique na compilação) e golangci-lint (mais recente - verifique na compilação).
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026