Intermediário Aplicação e Código

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.

ContextoRiscoSolução
HTML bodyXSSHTML entity encoding
Atributo HTMLXSSAttribute encoding
JavaScriptXSSJS string encoding
SQLSQL InjectionPrepared statements
FilesystemPath traversalSanitizar 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