Este artigo faz parte da série “Plugin do Zero ao WP.org com IA”, onde documento a jornada real de criação do PAP Afiliados Pro — com acertos, erros e aprendizados.
Eu estava confiante. O plugin funcionava. O painel admin estava bonito. Os cards de produto renderizavam direitinho. O tracking de cliques operava. Submeti ao WordPress.org, sentei e esperei a aprovação.
O email que voltou não era uma aprovação. Era uma lista de problemas.
Não vou mentir: a primeira reação foi frustração. Não raiva — eu sabia que os revisores do WordPress.org são voluntários e que estão protegendo milhões de sites. Mas aquela sensação de “eu achei que estava pronto” doeu. Porque não era uma questão de funcionalidade — tudo funcionava. Era uma questão de segurança. E segurança, no mundo WordPress, não é opcional.
Esse artigo é sobre as 3 lições que aprendi com as rejeições. Se você está desenvolvendo um plugin e pretende submeter ao WordPress.org, isso aqui pode te economizar semanas.
Contexto: como funciona a revisão do WordPress.org
Antes de entrar nas lições, preciso explicar como o processo funciona — porque muita gente acha que é só fazer upload de um ZIP.
Quando você submete um plugin ao diretório oficial, ele entra numa fila de revisão. Revisores voluntários — pessoas que dedicam tempo livre para manter a qualidade do ecossistema — analisam seu código manualmente. Não é um robô. É alguém lendo seu PHP, seu JavaScript, seus handlers AJAX, suas queries SQL.
Eles procuram vulnerabilidades. E são bons nisso. Se você tem um formulário sem nonce, eles acham. Se tem um output sem escaping, eles acham. Se tem uma query SQL concatenando variáveis direto, eles acham. E quando acham, o plugin volta com uma lista de correções obrigatórias.
O tom é profissional e direto. Não é grosseiro, mas também não é pedagógico. Eles apontam o problema e esperam que você resolva. Não te dizem como resolver — te dizem o que está errado. E isso é justo, porque quem está publicando software para milhões de sites precisa entender o que está fazendo.
O meu plugin voltou mais de uma vez. E cada vez, aprendi algo que nenhum tutorial tinha me ensinado.
Lição 1: Nonce não é opcional — e verificar sem usar o retorno é a mesma coisa que não verificar
Essa foi a que mais doeu, porque eu achei que estava fazendo certo.
Para quem não é do mundo WordPress: um nonce é um token de segurança que garante que uma requisição veio de quem deveria ter vindo. Quando alguém clica num botão no painel admin do seu plugin, o nonce prova que foi realmente aquele usuário autenticado que fez a ação — e não um script malicioso tentando executar algo em nome dele. É a proteção contra CSRF (Cross-Site Request Forgery), um dos ataques mais comuns na web.
O WordPress tem funções prontas para isso: wp_nonce_field() cria o token no formulário, wp_verify_nonce() verifica se o token é válido, check_ajax_referer() faz o mesmo para requisições AJAX.
Na minha primeira submissão, eu tinha nonces. Eles estavam lá. Mas o meu código fazia algo assim: verificava se o nonce existia com isset(), chamava wp_verify_nonce() — e não usava o retorno da verificação. Era como trancar a porta e deixar a chave do lado de fora. Tecnicamente, o mecanismo de segurança estava presente. Na prática, era bypassável.
O padrão correto é o que chamam de “bail-out”: se o nonce não é válido, para tudo. Retorna. Não processa nada. Não é “verifica e continua independente do resultado” — é “verifica e só continua se passou.”
Parece óbvio quando alguém explica. Mas quando você está no meio do desenvolvimento, com 20 arquivos abertos e a IA gerando código que funciona visualmente, é fácil deixar passar. A IA produz código que funciona. Funcionar e ser seguro são coisas diferentes.
Essa lição mudou a forma como eu estruturo prompts. Agora, toda implementação que envolve processamento de dados tem uma seção obrigatória de conformidade de segurança, com uma regra explícita: “wp_verify_nonce() com retorno verificado — pattern bail-out obrigatório.” Não confio na memória. Não confio que a IA vai lembrar. Está escrito no contrato, aparece no prompt, é validado na entrega.
Lição 2: Sanitização e escaping não são a mesma coisa — e você precisa dos dois
Essa confusão é mais comum do que parece, especialmente para quem não é programador de formação.
Sanitização é o que você faz com o dado que entra. Quando alguém preenche um campo no seu formulário — nome de produto, URL, preço — você limpa esse dado antes de fazer qualquer coisa com ele. Remove HTML malicioso, caracteres perigosos, scripts que alguém possa ter tentado injetar. O WordPress tem funções específicas: sanitize_text_field() para texto, sanitize_url() para URLs, absint() para números inteiros.
Escaping é o que você faz com o dado que sai. Quando você exibe algo na tela — o nome de um produto, um link, um atributo HTML — você escapa esse dado para garantir que nada perigoso seja executado no navegador do visitante. As funções: esc_html() para texto em HTML, esc_attr() para atributos, esc_url() para links.
São dois momentos diferentes. Entrada e saída. E os dois são obrigatórios. Não é ou — é e.
No meu caso, eu tinha sanitização na maioria dos inputs. Mas escapava de forma inconsistente nos outputs. Alguns sim, outros não. E o revisor, com razão, apontou: se tem um echo mostrando uma variável sem esc_html(), é uma potencial vulnerabilidade de XSS (Cross-Site Scripting). Não importa se o dado foi sanitizado na entrada — se na saída ele está cru, alguém pode explorar.
A regra que adotei é simples e não tem exceção: todo dado que entra passa por sanitização. Todo dado que sai passa por escaping. Todo. Sem exceção. Sem “ah, mas esse campo sou eu que preencho, não precisa.” Precisa. Porque segurança não é sobre confiar nas pessoas — é sobre proteger o sistema independente de quem opera.
Quando eu monto um prompt para o CCW agora, a seção de conformidade tem duas linhas explícitas: “TODOS os inputs sanitizados” e “TODOS os outputs escapados.” Em letras maiúsculas, no prompt. Porque a IA, quando não é instruída sobre isso, tende a gerar código que funciona mas não escapa. Funcionar e ser seguro — de novo, coisas diferentes.
Lição 3: SQL preparado não é sugestão — é a única forma aceitável
Essa é a mais perigosa de ignorar. SQL injection é um dos ataques mais antigos da web e continua sendo um dos mais eficazes. O conceito é simples: se o seu código monta uma query SQL concatenando variáveis do usuário direto na string, alguém pode manipular essas variáveis para executar comandos no seu banco de dados. Deletar tabelas, extrair dados, criar administradores falsos.
O WordPress oferece $wpdb->prepare() — uma função que usa placeholders (%s para string, %d para inteiro) e monta a query de forma segura, escapando automaticamente os valores. É a diferença entre:
Errado: montar a query colando o valor da variável direto no texto SQL.
Certo: usar prepare com placeholders e deixar o WordPress tratar os valores.
Na minha primeira versão, eu tinha queries que usavam prepare. Mas não todas. Algumas queries mais simples — “buscar produtos do usuário atual”, por exemplo — eu tinha deixado sem prepare porque pareciam inofensivas. Afinal, o ID do usuário vem de uma função do WordPress, não de um input externo.
O revisor discordou. E com razão. A regra não é “use prepare quando o input é perigoso.” A regra é “use prepare em toda query que tenha variável.” Sem julgamento sobre a origem da variável. Porque hoje o dado vem de uma função segura do WordPress, amanhã alguém refatora o código e muda a origem, e a vulnerabilidade está lá, silenciosa.
Essa lição me ensinou algo mais amplo sobre segurança: não é sobre avaliar risco caso a caso. É sobre aplicar padrões universais que eliminam categorias inteiras de vulnerabilidade. Quando você decide “toda query usa prepare”, não precisa mais pensar “será que essa precisa?” A resposta é sempre sim.
O que essas 3 lições têm em comum
Olhando em retrospectiva, as três lições apontam para o mesmo princípio: segurança não é uma feature que você adiciona no final. É uma prática que permeia todo o código, desde a primeira linha.
Eu cometi o erro de tratar segurança como uma etapa — “primeiro faço funcionar, depois deixo seguro.” Isso não funciona. Porque quando o código já está funcionando, a tentação de não mexer é enorme. “Está funcionando, vai que eu quebro.” E aí as vulnerabilidades ficam, enterradas sob camadas de código que ninguém quer tocar.
As 3 lições que contei aqui são as que me pegaram de jeito, mas não são as únicas regras. No total, o WordPress.org exige conformidade em pelo menos 8 frentes: nonces, capabilities (verificar se o usuário tem permissão para aquela ação), sanitização, escaping, SQL preparado, prefixos únicos (para não conflitar com outros plugins), internacionalização (todas as strings prontas para tradução), e verificação de retorno do nonce (que é justamente o que eu errei na Lição 1). São 8 regras que não negociam. Falhar em uma já é motivo de rejeição.
Parece muito? No início parecia. Mas quando você internaliza a lógica — proteger entrada, proteger saída, proteger banco, proteger identidade, proteger namespace — vira segunda natureza. É como dirigir: no começo você pensa em cada pedal, cada marcha, cada espelho. Depois de um tempo, faz tudo junto sem pensar.
O que mudou depois das rejeições: segurança virou o primeiro item de qualquer implementação. Antes de pensar na funcionalidade, eu defino quais validações serão necessárias. Nonces, capabilities, sanitização, escaping, SQL preparado — tudo decidido antes da primeira linha de código ser escrita. Tudo documentado no contrato técnico. Tudo presente no prompt para a IA implementadora.
E criei algo que chamo internamente de “Guardian de Conformidade” — um conjunto de regras e checklists que toda implementação precisa passar antes de ser considerada completa. Não é sofisticado. É disciplina. Mas transformou o processo de “espero que esteja seguro” para “eu sei que está seguro, porque verifiquei.”
Isso é assunto para o próximo artigo.
Para quem vai submeter ao WordPress.org
Se você está desenvolvendo um plugin e pretende submeter ao diretório oficial, deixa eu ser direto com algumas coisas que gostaria que alguém tivesse me dito:
Os revisores não estão contra você. Estão protegendo um ecossistema que alimenta 40%+ da web. Quando eles apontam um problema, não estão sendo chatos — estão evitando que seu código exponha milhões de sites a vulnerabilidades.
Leia as guidelines inteiras antes de submeter. Não por cima, inteiras. Especialmente a parte de segurança. Eu sei que é tentador pular para o “como submeter” e ignorar o “o que é obrigatório.” Não faça isso.
Teste com o Plugin Check do WordPress. É uma ferramenta oficial que roda verificações automatizadas no seu código. Não pega tudo que um revisor humano pega, mas pega o básico. Se o Plugin Check aponta erros, nem submeta — resolva primeiro.
E respeite o tempo dos revisores. São voluntários. Se seu plugin volta com correções, resolva todas — não algumas. Submeter de novo com problemas pendentes é desrespeitar o tempo de alguém que está fazendo isso de graça.
As rejeições que recebi foram, em retrospectiva, a melhor coisa que aconteceu com o projeto. Não porque eu goste de ser corrigido — ninguém gosta. Mas porque me forçaram a entender segurança de verdade, não de forma superficial. E o plugin que saiu desse processo é incomparavelmente mais sólido do que o que eu teria publicado se tivesse sido aprovado de primeira.
Uma reflexão final, especialmente para quem está usando IA no desenvolvimento: a IA gera código que funciona. Funciona visualmente, funciona logicamente, passa nos testes básicos. Mas segurança não é sobre funcionar — é sobre não quebrar quando alguém tenta quebrar. E esse tipo de pensamento adversarial não é o forte das IAs, pelo menos não por padrão. Elas otimizam para “faz o que o usuário pediu.” Se o usuário não pediu segurança explicitamente, ela não vem inclusa.
Por isso a documentação e os checklists são tão importantes nesse modelo de desenvolvimento. Você precisa colocar a segurança no prompt porque a IA não vai colocar sozinha. E precisa verificar na entrega porque confiar sem validar é a receita para um email de rejeição.
Às vezes, o “não” te leva mais longe que o “sim”.
Próximo artigo da série: Como transformei rejeição em sistema: o Guardian de Conformidade que não deixa erro passar
Navegação da série:
◀ Artigo anterior: Como organizei múltiplos AIs para desenvolver software profissional sem escrever código
▶ Próximo artigo: Como transformei rejeição em sistema: o Guardian de Conformidade que não deixa erro passar
