Antes de colar código do ChatGPT em produção, eu passo por um checklist de dez pontos: segredos, injeção, validação de input, autenticação e autorização, dependências, criptografia, tratamento de erro, logs e dados pessoais, configuração e, por fim, ferramentas automáticas. Nenhum desses pontos é novo em segurança. O que muda com código gerado por IA é que ele chega pronto, bem escrito e confiante, e isso faz a gente baixar a guarda.
Este checklist vale para ChatGPT, mas também para Claude, Gemini, Copilot ou qualquer assistente. O problema não é a ferramenta, é colar sem olhar.
Por que código de IA precisa de cuidado extra
O modelo aprendeu com muito código público, e muito código público é inseguro: exemplo de tutorial, resposta antiga de fórum, snippet que “funciona” mas foi escrito para demo. Quando você pede uma solução, ele tende a entregar o caminho mais comum, não o mais seguro.
Além disso, o modelo não conhece o seu contexto. Ele não sabe que aquela rota é pública, que aquele campo vem do usuário, ou que aquela tabela tem CPF. Você sabe. A segurança depende de você aplicar esse contexto.
O checklist
1. Segredos e credenciais
- Tem chave de API, senha, token ou string de conexão no código?
- Tem valor de exemplo tipo
"sua-chave-aqui"que alguém vai trocar pelo valor real direto no arquivo? - E o caminho inverso: você colou um segredo real no prompt? Se sim, considere essa credencial comprometida e gire.
O certo é ler de variável de ambiente ou de um gerenciador de segredos, e falhar alto se não existir:
const apiKey = process.env.PAYMENT_API_KEY;
if (!apiKey) {
throw new Error('PAYMENT_API_KEY não configurada');
}
2. Injeção (SQL, shell, template)
Esse é o clássico. Peça uma busca no banco e há boa chance de vir algo assim:
# inseguro: input do usuário concatenado na query
cursor.execute(f"SELECT * FROM usuarios WHERE email = '{email}'")
A versão segura usa parâmetros:
cursor.execute("SELECT * FROM usuarios WHERE email = %s", (email,))
O mesmo vale para comandos de sistema. Fuja de shell=True com input externo, de exec com string montada, de eval em qualquer coisa que venha de fora:
# inseguro
subprocess.run(f"convert {arquivo} saida.png", shell=True)
# melhor: lista de argumentos, sem shell
subprocess.run(["convert", arquivo, "saida.png"], check=True)
E no front-end, cuidado com innerHTML, dangerouslySetInnerHTML e v-html com conteúdo vindo do usuário. É XSS esperando para acontecer.
3. Validação de input
- Todo dado que vem de fora (body, query string, header, arquivo, webhook) é validado antes de usar?
- Tamanho máximo, tipo, formato e valores permitidos estão definidos?
- Upload de arquivo checa tipo e tamanho, e salva com nome gerado por você, não o nome original?
Um caso que aparece muito em código gerado: montar caminho de arquivo com input do usuário.
// inseguro: ?nome=../../etc/passwd
const caminho = path.join(PASTA_UPLOADS, req.query.nome);
// melhor: resolver e garantir que continua dentro da pasta
const caminho = path.resolve(PASTA_UPLOADS, req.query.nome);
if (!caminho.startsWith(path.resolve(PASTA_UPLOADS) + path.sep)) {
return res.status(400).send('Arquivo inválido');
}
4. Autenticação e autorização
O modelo costuma lembrar de autenticação (“o usuário está logado?”), mas esquece a autorização (“esse usuário pode mexer nesse recurso?”).
- Toda rota que retorna ou altera dado checa se o recurso pertence ao usuário (ou se ele tem permissão)?
- IDs na URL podem ser trocados para acessar dado de outra pessoa?
- Rotas administrativas checam papel no servidor, e não só escondem o botão no front?
const pedido = await Pedido.findById(req.params.id);
if (!pedido || pedido.usuarioId !== req.user.id) {
return res.status(404).end(); // 404 para não confirmar que o recurso existe
}
5. Dependências
Esse ponto é específico de código gerado por IA. Modelos às vezes sugerem pacotes que não existem, ou com nome muito parecido com um pacote real. Já existe gente registrando esses nomes em repositórios públicos para distribuir código malicioso.
- O pacote sugerido existe de verdade? Confira no npm, PyPI ou equivalente.
- O nome está escrito exatamente igual ao pacote oficial?
- Ele é mantido? Quando foi a última versão? Quantos mantenedores?
- A versão sugerida tem vulnerabilidade conhecida?
- Você precisa mesmo dessa dependência, ou são dez linhas que dá para escrever?
# conferir antes de instalar
npm view nome-do-pacote
pip index versions nome-do-pacote
6. Criptografia e aleatoriedade
Código de exemplo antigo é cheio de criptografia ruim, e o modelo reproduz.
- Senha com MD5, SHA-1 ou SHA-256 puro? Troque por bcrypt, scrypt ou Argon2.
- Token de sessão, código de recuperação ou ID sensível gerado com
Math.random()ourandom? Use gerador criptográfico. - Comparação de token com
==? Use comparação de tempo constante. - Algoritmo inventado “para ofuscar”? Não existe criptografia caseira segura.
// inseguro para token
const token = Math.random().toString(36).slice(2);
// seguro
import { randomBytes } from 'node:crypto';
const token = randomBytes(32).toString('hex');
import secrets
token = secrets.token_urlsafe(32)
7. Tratamento de erro
- A resposta de erro para o cliente expõe stack trace, query SQL ou caminho de servidor?
- Tem
catchque engole o erro e devolve sucesso? - Em falha no meio da operação, o sistema fica num estado consistente?
Regra que eu sigo: detalhe completo no log interno, mensagem genérica para o usuário.
8. Logs e dados pessoais
- O código loga o request inteiro, incluindo senha, token ou documento?
- Dados pessoais (CPF, e-mail, telefone, endereço) aparecem em log ou em mensagem de erro?
- Você precisa mesmo guardar esse dado? Pela LGPD, a resposta precisa ter uma finalidade clara.
É muito comum o modelo adicionar um console.log(req.body) “para debug”. Em produção, isso vira vazamento de dado em arquivo de log.
9. Configuração
- CORS liberado com
*numa API que usa cookie ou token? - Cookie de sessão sem
HttpOnly,SecureeSameSite? - Modo debug ligado (
DEBUG=True, stack trace habilitado)? - Verificação de certificado TLS desligada (
verify=False,rejectUnauthorized: false) “para funcionar”? - Sem limite de requisição numa rota de login, recuperação de senha ou envio de e-mail?
Esse verify=False merece destaque. Quando dá erro de certificado, a sugestão mais rápida do modelo muitas vezes é desligar a verificação. Resolve o erro e abre a porta para ataque man-in-the-middle.
10. Requisições para URLs externas
Se o código busca uma URL que veio do usuário (preview de link, importação de imagem, webhook configurável), cuidado com SSRF: o atacante pode apontar para serviços internos da sua rede ou para o endpoint de metadados da nuvem.
- Use uma allowlist de domínios quando possível.
- Bloqueie IPs privados,
localhoste endereços de metadados. - Defina timeout e tamanho máximo de resposta.
As ferramentas que eu rodo antes do deploy
Checklist manual pega o que exige contexto. Ferramenta automática pega o que é repetitivo. Eu uso os dois. Estas são as que rodo localmente e deixo no CI:
# análise estática com regras de segurança
semgrep scan --config auto
# segredos no repositório e no histórico do git
gitleaks detect --source . -v
# vulnerabilidades conhecidas nas dependências
npm audit --omit=dev
pip-audit
osv-scanner scan -r .
Screenshot descrito: saída do Semgrep no terminal apontando um achado de “SQL injection” num arquivo de repositório, com o número da linha e o trecho com f-string destacado. Logo abaixo, o Gitleaks sem achados. É o tipo de resultado que eu quero ver antes de abrir o PR.
Nenhuma dessas ferramentas substitui a revisão. Elas reduzem o que chega até ela. Também não confie cegamente em zero achados: análise estática não entende sua regra de negócio nem sabe se a rota checa o dono do recurso.
Como eu peço código mais seguro desde o início
Dá para reduzir o retrabalho já no prompt. Eu incluo o contexto que o modelo não tem:
Escreva a rota em Express + TypeScript.
Contexto de segurança:
- O id vem da URL e é controlado pelo usuário.
- O usuário logado está em req.user; ele só pode acessar os próprios pedidos.
- Valide o input com Zod.
- Use queries parametrizadas.
- Não logue o body da requisição.
- Não adicione dependências novas.
No final, liste os riscos de segurança que você considerou e os que não tratou.
A última linha é a mais útil. Pedir para o modelo dizer o que ele não tratou costuma revelar exatamente onde eu preciso olhar.
Checklist rápido para copiar
- [ ] Nenhum segredo no código; nenhum segredo real colado no prompt
- [ ] Queries parametrizadas; nada de shell, eval ou HTML com input externo
- [ ] Todo input externo validado (tipo, tamanho, formato)
- [ ] Caminhos de arquivo não escapam da pasta esperada
- [ ] Autorização checada por recurso, no servidor
- [ ] Dependências existem, nome confere, sem vulnerabilidade conhecida
- [ ] Senha com bcrypt/scrypt/Argon2; tokens com gerador criptográfico
- [ ] Erros não vazam detalhes; nenhum catch silencioso
- [ ] Logs sem senha, token ou dado pessoal
- [ ] CORS, cookies, TLS e debug configurados para produção
- [ ] URLs externas com allowlist e timeout
- [ ] Semgrep, Gitleaks e auditoria de dependências rodados
Perguntas frequentes
Código do ChatGPT é mais inseguro que código humano?
Não dá para afirmar isso de forma geral, e eu não vou inventar número. O que eu vejo na prática é que ele repete padrões comuns, incluindo os inseguros, e que a gente revisa com menos atenção porque o código parece pronto. O risco maior é a confiança, não a ferramenta.
Posso colar meu código no ChatGPT para pedir ajuda?
Depende da política da sua empresa e do plano que você usa. Mesmo quando é permitido, eu removo segredos, dados de clientes e detalhes de infraestrutura antes. Se colou um segredo por engano, gire a credencial.
Rodar Semgrep e npm audit já basta?
Não. Essas ferramentas pegam padrões conhecidos. Falha de autorização, regra de negócio errada e vazamento de dado em log muitas vezes passam limpos. O checklist manual cobre essa parte.
Preciso fazer tudo isso para um script interno?
Para script que roda uma vez na sua máquina, o risco é menor. Mas “script interno” tem o hábito de virar serviço em produção. Segredos e dependências eu checo sempre, sem exceção.
Qual item eu nunca pulo?
Dependências. Instalar um pacote que não existe, ou com nome parecido, pode comprometer a máquina e o pipeline inteiro antes mesmo do código ir para produção.
Este checklist conversa com o que eu uso para revisar pull requests: como eu reviso PR gerado por IA sem virar borracha.