Secure coding — validação de input, encoding de output, falha segura
Escrever código funcional é diferente de escrever código seguro. A maioria das vulnerabilidades conhecidas — SQL Injection, XSS, Path Traversal — nasce de três falhas simples: confiar no input do usuário, misturar dados com código na saída, e deixar a aplicação expor informações quando algo dá errado.
Validação de Input
Toda entrada externa é potencialmente maliciosa. Isso inclui campos de formulário, query strings, cabeçalhos HTTP, cookies, arquivos enviados e até dados vindos de APIs de terceiros.
Regras práticas:
- Whitelist, não blacklist. Defina o que é aceito; rejeite o resto.
- Valide tipo, tamanho e formato antes de qualquer processamento.
- Não confie em validação só no cliente. JavaScript pode ser desabilitado ou contornado.
import re
def validar_username(username: str) -> bool:
# Aceita apenas letras, números e underscore, 3-32 chars
padrao = r'^[a-zA-Z0-9_]{3,32}$'
return bool(re.match(padrao, username))
# Ruim: aceita qualquer string
# Bom: restringe ao formato esperado
if not validar_username(username):
raise ValueError("Username inválido")
Encoding de Output
Dados exibidos na saída devem ser codificados para o contexto onde aparecem. Um mesmo dado pode precisar de encodings diferentes dependendo do destino.
| Contexto | Risco | Solução |
|---|---|---|
| HTML body | XSS | HTML entity encoding |
| Atributo HTML | XSS | Attribute encoding |
| JavaScript | XSS | JS string encoding |
| SQL | SQL Injection | Prepared statements |
| Filesystem | Path traversal | Sanitizar e resolver caminho |
# Ruim — concatenação direta no SQL
query = f"SELECT * FROM users WHERE name = '{nome}'"
# Bom — prepared statement
cursor.execute("SELECT * FROM users WHERE name = %s", (nome,))
<!-- Ruim — dado bruto no HTML -->
<p>{{ usuario.bio }}</p>
<!-- Bom — escapado pelo template engine (Jinja2, etc.) -->
<p>{{ usuario.bio | e }}</p>
Falha Segura (Fail Secure)
Quando algo dá errado, a aplicação deve negar acesso por padrão, não concedê-lo. E nunca deve vazar informação interna.
# Ruim — exceção expõe detalhes do banco
@app.route("/user/<id>")
def get_user(id):
user = db.query(f"SELECT * FROM users WHERE id={id}")
return jsonify(user)
# Bom — captura exceção, log interno, resposta genérica
@app.route("/user/<id>")
def get_user(id):
try:
user = db.get_user_by_id(id)
if user is None:
return jsonify({"error": "Not found"}), 404
return jsonify(user.to_dict())
except Exception:
logger.exception("Erro ao buscar usuário id=%s", id)
return jsonify({"error": "Internal server error"}), 500
Mensagens de erro para o usuário devem ser genéricas. Detalhes ficam no log do servidor.
Princípio do Menor Privilégio no Código
O código deve operar com o mínimo de permissão necessária para a tarefa:
- Conexão com banco: usuário com SELECT/INSERT apenas, não DBA.
- Leitura de arquivo: abrir como read-only quando não precisa escrever.
- Processo: não rodar como root quando não é necessário.
Checklist Rápido
- Toda entrada validada por whitelist antes de usar
- Prepared statements em todas as queries
- Output escapado no contexto correto
- Erros logados internamente, mensagem genérica para o cliente
- Dependências atualizadas (sem CVEs conhecidos)
- Sem segredos hardcoded no código