First Step on Battlefield — Building Go-Powered OSINT Tool for Passive Recon

André Ataíde
23 min read1 hour ago

--

Imagine o seguinte cenário: você é júnior de uma equipa de Red Team em uma empresa de consultoria de segurança cibernética, que foi contratada para executar um teste de intrusão (penetration testing) para uma organização qualquer. O espoco do engagement prevê e autoriza, entre outras coisas, ações de engenharia social para avaliar potenciais riscos de segurança causados por informações divulgadas por membros da equipe de segurança do cliente. Você foi designado para auxiliar na fase de reconhecimento do teste de intrusão, e precisa verificar se informações sensíveis, como e-mails corporativos vazados, nomes de usuários críticos estão expostos nos resultados do Google.

Você acompanha relatórios de agências especializadas, sabe dos riscos associados à exposição digital e sabe que esta etapa é crucial. Portanto, rigo é imprescindível. Você sabe que Agentes de Ameaça podem facilmente coletar PIIs ao conduzir buscas simples em redes sociais, sites oficiais do governo, etc., e mapear correlações entre entidades (pessoas, e-mails, domínios, etc.) a partir de fontes públicas e relacioná-las a um alvo. É este modus operandi que você precisa mimetizar, mas por onde começar? Bom, uma breve pesquisa pode ajudar a entender como este tipo de atuação impacta no mundo real.

Dados do Verizon 2024 Data Breach Investigations Report revelam que 68% das violações de segurança começam com ataques de engenharia social, muitos deles alimentados por informações acidentalmente publicadas em redes sociais e credenciais roubadas. Um estudo da KnowBe4 indica que organizações sem treinamento de conscientização em segurança têm uma taxa média de 34,3% de funcionários suscetíveis a ataques de phishing (Phish-Prone Percentage — PPP). Após 90 dias de treinamento, essa taxa cai para 18,9%, e após um ano de treinamento contínuo, cai para 4,6%. Esses dados não são apenas estatísticas abstratas: em 2023, o FBI IC3 registrou 21.489 queixas de crimes cibernéticos relacionados com comprometimento de e-mail comercial (BEC), sendo esse o segundo tipo de crime mais custoso, totalizando US$ 2,9 bilhões em perdas relatadas. Muitos desses ataques facilitados por informações expostas inadvertidamente pelas próprias vítimas.

Nesse contexto, a capacidade de identificar rapidamente quais dados sensíveis estão visíveis em mecanismos de busca torna-se uma habilidade crítica para profissionais de segurança. Este artigo explora como eu desenvolvi o MVP de uma ferramenta de OSINT (Open Source Intelligence) em Go, projetada para mapear a exposição digital de usuários e organizações nos resultados do Google. Mais do que um exercício técnico, trata-se de uma contramedida proativa contra vetores de ataque que exploram a ingenuidade humana — um desafio que, segundo o CrowdStrike 2024 Global Threat Report, ainda segue sendo o centro das atenções, à medida que os adversários se concentram em ataques de engenharia social que ignoram a autenticação multifator.

Dados de Referência:

- Verizon 2024 DBIR: 68% das violações de segurança envolvem erro humano, incluindo engenharia social, credenciais roubadas e ações acidentais.
- KnowBe4 2024: Organizações sem treinamento têm 34,3% de funcionários suscetíveis a phishing (PPP). Após 90 dias de treinamento, a taxa cai para 18,9%, e após um ano, para 4,6%.
- FBI IC3 2023: 21.489 queixas de BEC (Comprometimento de E-mail Comercial) foram registradas, com perdas de US$ 2,9 bilhões, sendo o segundo crime cibernético mais custoso.
- CrowdStrike 2024 Global Threat Report: Ataques de engenharia social continuam sendo o foco principal, com adversários explorando técnicas que ignoram a autenticação multifator.

Disclaimer: Responsabilidade no Ethical Hacking e Porque estou compartilhando isso

Antes de falar qualquer coisa a respeito do desenvolvimento do NameSniper, é importante esclarecer que este artigo e a ferramenta descrita são destinados exclusivamente a fins educacionais, éticos e legais. O ethical hacking é uma prática que deve ser conduzida apenas com permissão explícita do proprietário dos sistemas ou dados em questão. Qualquer uso não autorizado do NameSniper ou de técnicas similares para acessar, coletar ou manipular dados sem consentimento é ilegal e pode resultar em sérias consequências legais, financeiras e éticas.

Estou compartilhando o projeto como parte do meu aprendizado e transição para uma carreira em segurança cibernética, com o objetivo de contribuir para a comunidade OSINT, inspirar outros aprendizes e promover boas práticas na fase de reconhecimento. Este projeto é um MVP (Minimum Viable Product) aberto para colaboração, mas reforço: use-o apenas em ambientes autorizados e com responsabilidade. Meu foco é capacitar profissionais de segurança, não facilitar atividades maliciosas.

Por que monitorar a exposição no Google?

O motor de busca da Google ainda é, em 2025, o principal ponto de entrada para informações na internet, processando bilhões de consultas diariamente e servindo como espelho do que está acessível publicamente. Mas por que isso importa? A resposta está na quantidade de dados sensíveis que podem ser encontrados com os operadores de consulta certos. Por exemplo, a busca `site:pastebin.com “senha”` retorna resultados na faixa de centenas de milhares, com muitos contendo credenciais vazadas, como senhas e logins. Já consultas como `filetype:pdf contrato confidencial` podem expor documentos corporativos sensíveis indexados inadvertidamente, embora a sensibilidade exata varie.

Escolher o Google Search como ponto de partida do protótipo do NameSniper não foi mera curiosidade técnica — é uma necessidade prática.

Foram realizadas buscas utilizando operadores de busca avançada, como site: para limitar o escopo a Pastebin e filetype: para restringir a PDFs. Os resultados foram analisados para verificar a quantidade aproximada de resultados e a presença de informações sensíveis, como credenciais vazadas e documentos corporativos confidenciais. A busca `site:pastebin.com “senha”` foi executada para identificar páginas no Pastebin que contêm a palavra “senha”. Os resultados indicam uma quantidade significativa de entradas, com base em amostras que mostram conteúdos como listas de e-mails, senhas e outros dados de login. Exemplos de conteúdos encontrados incluem, mas não estão limitados a, listas de usernames e senhas, postagens relacionadas a coleções de senhas, como “Coletania de senhas vazadas de usuários na internet”, etc.

É um tipo de tarefa repetitiva, tediosa, com imensos pormenores e que demanda um tempo que poderia ser dedicado a outras tarefas igualmente importantes e que não podem ser delegadas a uma ferramenta, como Análise de Contexto e Inteligência Humana (HUMINT), mapear profundo de redes sociais do alvo, desenvolver pretextos convincentes para simulações de phishing e spearphishing, interpretar de dados ambíguos ou incompletos, e assim por diante. Ferramentas como o NameSniper automatizam a detecção desses padrões, permitindo que equipes de segurança:

  1. Identifiquem vazamentos de dados pessoais ou institucionais antes que sejam explorados por atores maliciosos;
  2. Mitiguem riscos de pretexting, como ataques que usam detalhes de cargos ou projetos públicos para imitar executivos;
  3. Monitorem a exposição de ativos digitais em conformidade com regulamentações como LGPD e GDPR;
  4. Reduzam o tempo gasto em tarefas repetitivas, liberando profissionais para focar em análises estratégicas e respostas a incidentes; e
  5. Escalem operações de reconhecimento, cobrindo grandes volumes de dados sem sacrificar a precisão ou a eficiência.

Há um número considerável de ferramentas OSINT disponíveis para este fim, e você pode estar a se perguntar por que simplesmente não usá-las? Primeiro, numa perspectiva muito pessoal, não é divertido. Mas, para além disso, há razões práticas e estratégicas para investir tempo em criar uma ferramenta como essa, especialmente quando se está aprendendo e transicionando de carreira ao mesmo tempo e com uma certa idade (digo eu com trinta e tantos anos).

Construir uma ferramenta como esta do zero permite um aprendizado profundo da tecnologia, forçando você a entender não apenas como as APIs funcionam, mas também como lidar com rate limits, autenticação, parsing de dados e otimização de código. Esse conhecimento é valioso e transferível para outras áreas da segurança cibernética. Também, quando se está começando na área, você pode se deparar com ferramentas prontas que nem sempre atendem a requisitos específicos de um projeto pequeno. É como matar uma mosca com um canhão. Criar sua própria solução permite adaptar funcionalidades, como filtros personalizados para tipos de dados ou integração com outras ferramentas internas.

Durante o processo, você enfrentará desafios como lidar com bugs (muitos), bloqueios de IP, otimização de consultas ou gerenciamente grandes volumes de dados. Essas experiências são fundamentais para construir uma mentalidade de troubleshooting, e desenvolver muita, muita paciência e resiliência à frustração. Para quem está em transição de carreira, especialmente mais tarde na vida, demonstrar iniciativa e capacidade de criar ferramentas práticas pode destacar seu perfil. Um projeto como este no portfólio mostra proatividade, maturidade e habilidades técnicas sólidas, diferenciando você em um mercado competitivo.

Outro ponto importante é que em um pentest ou resposta a incidentes, nem sempre você terá acesso às suas ferramentas preferidas. Saber como criar soluções rápidas e eficientes em um determinado contexto pode ser a diferença entre sucesso e fracasso de uma campanha.

Há, também, pelo menos para mim, uma satisfação pessoal e a motivação que vem com a criação de algo funcional. Ver uma ferramenta que você construiu entregando resultados é incrivelmente gratificante, e pode ser um grande motivador durante uma transição de carreira. Compartilhar seu projeto em plataformas como GitHub ou LinkedIn, ou mesmo grupos de estudo, pode atrair a atenção de outros profissionais da área, abrindo portas para colaborações, mentorias ou até oportunidades de emprego.

Enquanto ferramentas prontas podem resolver problemas imediatos, investir tempo em criar sua própria solução oferece benefícios de longo prazo — tanto para o seu crescimento profissional quanto para a qualidade do trabalho que você entrega.

Por que Go?

Recentemente, participei de um bootcamp de programação em C. Já falei sobre isso em outro post, onde detalhei a criação de um MVP para uma ferramenta de enumeração de subdomínios em naquela linguagem.

Foi uma experiência valiosa, que me deu um maior entendimento sobre controle de memória, desempenho bruto e a construção de ferramentas de baixo nível. Entretanto, para este projeto, optei por deixar o C um pouco de lado e abraçar o Go. Essa decisão não foi arbitrária (para além dos meus gostos pessoais) — ela reflete as prioridades e os desafios específicos deste projeto, além de uma visão prática sobre como entregar valor rapidamente sem sacrificar robustez.

O C é, sem dúvida, uma linguagem poderosa, especialmente para projetos que exigem otimização extrema ou integração direta com hardware. Durante o bootcamp, aprendi a apreciar sua precisão e o controle que oferece, mas também senti o peso de gerenciar manualmente detalhes como alocação de memória e manipulação de strings — tarefas que podem consumir tempo precioso em um MVP.

No caso do NameSniper, a missão era criar uma ferramenta leve e funcional que fizesse requisições a APIs externas, como a Google Custom Search, e processasse respostas JSON de forma eficiente. A biblioteca padrão do Go já traz suporte nativo para HTTP e parsing de JSON, eliminando a necessidade de bibliotecas externas como `libcurl` ou parsers manuais que seriam inevitáveis em C. Isso me permitiu focar na lógica do projeto — buscar dados públicos a partir de um nome — em vez de lutar com detalhes de infraestrutura.

Go também oferece uma vantagem significativa em simplicidade e velocidade de desenvolvimento. Enquanto em C eu teria que configurar threads ou forks para lidar com múltiplas requisições simultâneas (um plano futuro para o NameSnipe), o Go traz goroutines, que tornam a concorrência trivial e eficiente. Para uma ferramenta OSINT, onde consultar várias fontes em paralelo pode ser um diferencial, essa capacidade é um trunfo que o C só entrega com mais esforço. A gestão automática de memória também foi um alívio — em C, eu precisaria ficar atento a possíveis buffer overflows ou memory leaks, algo que, em um projeto inicial como este, poderia desviar o foco do objetivo principal.

Outro ponto que pesou na escolha foi a portabilidade e a facilidade de distribuição. O Go compila para binários únicos e independentes, o que significa que o NameSniper pode rodar em qualquer sistema sem preocupações com dependências externas. Para uma ferramenta de segurança voltada para estudantes, pesquisadores e pentesters, essa simplicidade é ouro. E, embora o C tenha um legado forte em projetos de cibersegurança como Nmap e Wireshark, o Go tem ganhado espaço em ferramentas modernas, como Amass e Gitleaks, mostrando que é uma aposta segura para o que eu queria construir.

Eu não sou desenvolvedor de software (e nem pretendo ser), mas aprender a programar é essencial para seguir em segurança cibernética. Não “coma a corda” que os vendedores de curso on-line jogam na sua boca para fazer dinheiro fácil. Aprenda uma linguagem de programação com a qual você se dê bem e siga com ela, todo dia. É difícil, mas fica mais fácil. Tem que fazer todo dia e essa é parte difícil. Mas fica mais fácil. :)

Bojack Horseman

C me ensinou a pensar nos detalhes, enquanto Go me permitiu transformar ideias em código funcional rapidamente. Para um MVP focado em coletar informações públicas com o mínimo de atrito, essa troca valeu a pena — e o resultado é uma ferramenta que já funciona, mas com muito espaço para crescer.

Entretanto, este não é um guia teórico. Vou desmontar o código-fonte de uma ferramenta prática e falar um pouco das decisões técnicas por detrás das features.

Diagrama do projeto

Não vamos pensar em código no começo. Na verdade, o código é a última coisa que devemos fazer. Não se começa um projeto de software jogando letras e números direto no editor — é preciso entender o problema, esboçar soluções e definir o que ela precisa fazer.

Antes de escrever uma única linha de código para o projeto passei um tempo considerável desenhando o caminho que queria seguir. O código que veremos aqui é o resultado final de um processo que começou com rabiscos, ideias soltas, muito café e pesquisa, e alguma reflexão.

Vamos quebrar isso em duas partes: primeiro, como o projeto tomou forma no papel, e depois, como as decisões técnicas se traduziram no código Go que implementei.

Estou estudando para tirar a certificação em Ethical Hacking e, para além das ferramentas-padrão mais famosas, cada plataforma traz uma infinidade de outras ferramentas. Quando você menos espera, tem um sem-número instaladas na sua estação de trabalho. Certamente você tem ferramentas mofando no seu HD e links no seus bookmarks que você nem lembra mais. Eu sofro muito com isso, então decidi fazer algo a respeito. Literalmente.

Eu tenho particular interesse em engenharia social e técnicas de coleta de inteligência, comuns na fase de reconhecimento. Então queria uma ferramenta que pegasse algo básico — como um primeiro nome, talvez um sobrenome — e devolvesse dados públicos de forma rápida e confiável. Peguei um caderno e comecei a rabiscar muito despretensiosamente.

Primeiro, fiz um diagrama básico do fluxo: entrada do usuário, consulta a uma fonte externa, parsing dos resultados e saída formatada. Isso me ajudou a visualizar os componentes essenciais. Pensei em funcionalidades básicas, como suportar nomes como input, buscar em uma API pública (escolhi a Google Custom Search por ser acessível, após uma busca… no Google :p), respeitar limites de uso e mostrar resultados legíveis. Mas logo percebi que precisava de um detalhe que, para alguns, é uma funcionalidade extra, mas para mim, como pessoa distraída que sou, é imprescindível: um contador persistente para gerenciar as 100 consultas diárias gratuitas da API, algo que não vi em muitas ferramentas iniciantes, e que poderia me salvar de receber um boleto indesejado.

TheHarvester, uma ferramenta OSINT clássica que coleta e-mails e subdomínios, foi minha referência top-of-mind. Já havia trabalhado com ela antes e foi quase automático pesquisar a respeito. Logo lembrei que ela estava mofando no meu HD :):. Queria algo tão simples quanto isso, mas ainda mais simples, focado em nomes (para começar), mas com potencial para crescer como ele. Então, listei o que o NameSniper precisava:

  • Ler credenciais de forma segura (nada de chaves hardcoded, meu bem).
  • Fazer requisições confiáveis, com tratamento de erros.
  • Controlar o limite de consultas diárias.
  • Persistir o estado entre execuções.

Ou seja, o objetivo principal do NameSniper é apresentar dados estruturados obtidos a partir da API do Google.

Com isso em mente, decidi que o Go seria ideal — já expliquei por que deixei o C de lado lá em cima (volte uma casa), mas aqui o foco foi na rapidez de desenvolvimento e nas bibliotecas nativas. O esboço final tinha blocos claros: configuração, entrada, limite, busca e saída. Só então abri o editor para codificar.

Deixei um código Mermaid para poder visualizar o esquema.

graph TD
A[main.go] → B[Configurações Globais]
A → C[Estruturas de Dados]
A → D[Funções Principais]
B → E[Variáveis de Ambiente<br>.env]
C → F[GoogleSearchResponse]
C → G[Contador]
D → H[buscarGoogle]
D → I[podeFazerConsulta]
D → J[atualizarContador]
D → K[carregarContador]
D → L[salvarContador]
H → M[API do Google]
I → N[consultas.json]
J → N
K → N
L → N

Visão Geral do Código

Agora que o plano estava traçado, o código veio como a tradução natural dessas ideias. O código do NameSniper está estruturado de forma modular, refletindo as necessidades básicas para uma ferramenta busca básica: carregar configurações, processar entrada do usuário, gerenciar limites de API, fazer requisições externas e persistir estado. Eu o dividi em blocos lógicos que refletem o esboço inicial:

  1. Imports e Configurações Globais: define as bibliotecas usadas e as variáveis globais que sustentam tudo, como chaves de API e limites.
  2. Estruturas de Dados: modela os resultados da API do Google e o contador de consultas.
  3. Função Main: o coração do programa, responsável por coordenar a lógica principal (carregar configurações, validar entrada, verificar limites, buscar dados e atualizar o estado).
  4. Requisição à API do Google: faz a chamada à Google Custom Search API para buscar dados externos, lidando com timeouts e erros.
  5. Gerenciamento de Limites: controla o uso da API dentro do limite gratuito de 100 consultas diárias, com persistência em arquivo.
  6. Persistência do Contador: lê e grava o estado das consultas em um arquivo JSON, garantindo continuidade entre execuções.

1. Imports e Configurações Globais

Bom, com o diagrama esboçado eu precisei começar por algum lugar, então tratei de montar o “esqueleto” da ferramenta:

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"time"
"github.com/joho/godotenv"
)

var (
googleAPIKey string
googleCX string
googleURL string
limiteDiario = 100
contadorFile = "consultas.json"
timeoutAPI = 15 * time.Second
)

No esboço, sabia que precisaria de bibliotecas para HTTP, JSON e arquivos, então usei o toolkit padrão do Go, evitando dependências pesadas. O `godotenv` entrou porque decidi manter as credenciais em um `.env` — uma escolha de segurança para não expor chaves no código ou no GitHub. A decisão de usar a biblioteca padrão onde possível reflete uma vantagem do Go: reduzir dependências externas. O `godotenv`, embora externo, é uma escolha comum para manter credenciais sensíveis fora do código, como a chave da API do Google.

As variáveis globais `googleAPIKey`, `googleCX` e `googleURL` são strings vazias que serão preenchidas do `.env` e definem o estado compartilhado da ferramenta, uma abordagem segura para evitar expor credenciais no repositório público. Elas são carregadas dinamicamente, com isso eu tenho flexibilidade na configuração para o futuro. `limiteDiario` é fixado em 100, refletindo o limite gratuito da Google Custom Search API, enquanto `contadorFile` aponta para um arquivo JSON que rastreia consultas. O `timeoutAPI` de 15 segundos veio de uma reflexão sobre APIs externas: elas podem falhar, e eu queria garantir que o NameSniper não ficasse preso esperando.

2. Estruturas de Dados

type GoogleSearchResponse struct {
Items []struct {
Title string `json:"title"`
Link string `json:"link"`
Snippet string `json:"snippet"`
} `json:"items"`
}

type Contador struct {
Data string `json:"data"`
Consultas int `json:"consultas"`
}

No diagrama, desenhei caixas para os dados da API e o contador. A struct `GoogleSearchResponse` espelha a resposta JSON do Google, focando em `Title`, `Link` e `Snippet` porque eram os campos mais úteis para recon passivo no MVP. Poderia ter mapeado mais, mas optei por simplicidade. O `Contador` foi uma decisão direta: precisava de uma data e um número de consultas para gerenciar o limite, e usar JSON para persistência era a forma mais prática de guardar isso entre execuções.

3. Função `main()`

func main() {
if err := godotenv.Load(); err != nil {
fmt.Println("Erro ao carregar .env:", err)
os.Exit(1)
}
googleAPIKey = os.Getenv("GOOGLE_API_KEY")
googleCX = os.Getenv("GOOGLE_CX")
googleURL = os.Getenv("GOOGLE_URL")
if googleAPIKey == "" || googleCX == "" || googleURL == "" {
fmt.Println("Erro: Variáveis de ambiente GOOGLE_API_KEY, GOOGLE_CX e GOOGLE_URL são obrigatórias.")
os.Exit(1)
}
if len(os.Args) < 2 {
fmt.Println("Uso: namesnipe [primeiro_nome] [sobrenome]")
os.Exit(1)
}
nome := os.Args[1]
sobrenome := ""
if len(os.Args) > 2 {
sobrenome = os.Args[2]
}
query := url.QueryEscape(nome)
if sobrenome != "" {
query = url.QueryEscape(fmt.Sprintf("%s %s", nome, sobrenome))
}
fmt.Printf("Buscando por: %s %s\n", nome, sobrenome)
if !podeFazerConsulta() {
fmt.Println("Limite diário de 100 consultas gratuitas atingido. Tente novamente amanhã.")
os.Exit(1)
}
resultados := buscarGoogle(query)
if len(resultados) > 0 {
fmt.Println("Resultados encontrados: ")
for i, resultado := range resultados {
fmt.Printf("%d. %s\n URL: %s\n Snippet: %s\n", i+1, resultado.Title, resultado.Link, resultado.Snippet)
}
} else {
fmt.Println("Nenhum resultado encontrado.")
}
atualizarContador()
}

Ok, vamos por partes. A função `main()` vai validar entrada, checar limites, buscar e exibir. O uso do `.env` veio da ideia de segurança no esboço, enquanto `url.QueryEscape` resolveu o problema de caracteres especiais que anotei como um risco (abaixo). A validação rigorosa (parando se algo falta) foi uma escolha para garantir que o usuário configure tudo certo antes de prosseguir — um aprendizado do bootcamp em C, onde erros sutis custavam caro (normalmente meu tempo).

Primeiro, carrego o `.env` com `godotenv.Load()`. Se falhar, o programa para com um erro claro — uma escolha consciente para forçar configuração correta. As variáveis de ambiente são então atribuídas, e verifico se estão vazias, garantindo que o usuário forneça as credenciais necessárias.

A entrada do usuário vem via `os.Args`, com suporte para nome (sobrenome opcional). Usei `url.QueryEscape` para codificar a query, uma decisão para evitar problemas com caracteres especiais (ex.: espaços viram `%20`), tornando as requisições à API confiáveis. A lógica de controle de fluxo é direta: verifico o limite de consultas diárias com `podeFazerConsulta()`, busco resultados com `buscarGoogle()`, exibo-os formatados e atualizo o `contador`.

4. Requisição à API — `buscarGoogle()`

func buscarGoogle(query string) []struct {
Title string `json:"title"`
Link string `json:"link"`
Snippet string `json:"snippet"`
} {
url := fmt.Sprintf("%s?key=%s&cx=%s&q=%s", googleURL, googleAPIKey, googleCX, query)
fmt.Println("URL da requisição: ", url)
client := &http.Client{Timeout: timeoutAPI}
resp, err := client.Get(url)
if err != nil {
fmt.Println("Erro na requisição:", err)
return nil
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
fmt.Printf("Erro na API: %s\n", resp.Status)
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println("Resposta bruta:", string(body))
return nil
}
var searchResponse GoogleSearchResponse
if err := json.NewDecoder(resp.Body).Decode(&searchResponse); err != nil {
fmt.Println("Erro ao decodificar resposta: ", err)
return nil
}
return searchResponse.Items
}

A função `buscarGoogle()` faz o trabalho pesado. Ela recebe um argumento `query string` e retorna slice de structs (wth???).

Explico: em Go, um array tem tamanho fixo (ex.: `[3]string`). Não é o caso aqui. Um slice é uma estrutura dinâmica, como uma lista, que pode crescer ou encolher (ex.: `[ ]string`). Aqui, o retorno é um slice. O tipo retornado não é apenas strings, mas uma struct anônima com três campos (`Title`, `Link`, `Snippet`), todos do tipo `string`. Ou seja, o retorno dessa função é um slice de structs, onde cada struct contém três campos do tipo string.

Como assim? Por exemplo? No código, a função `buscarGoogle()` é chamada assim em main:

resultados := buscarGoogle(query)
if len(resultados) > 0 {
for i, resultado := range resultados {
fmt.Printf("%d. %s\n URL: %s\n Snippet: %s\n", i+1, resultado.Title, resultado.Link, resultado.Snippet)
}
}
  • `query`: uma string codificada (ex.: `”Pedro%20Lu%C3%ADs”`).
  • `resultados`: um slice de structs retornado por `buscarGoogle()`. Cada item no slice é uma struct com `Title`, `Link` e `Snippet`.
  • Uso: o loop acessa os campos da struct (`resultado.Title`, etc.), mostrando que o retorno é mais complexo que um simples array de strings.

Por exemplo, se a API retornasse dois resultados, o valor de resultados poderia ser algo assim:

[
{Title: "Pedro Luís - LinkedIn", Link: "https://linkedin.com/in/pedroluis", Snippet: "Perfil profissional..."},
{Title: "Pedro Luís - Facebook", Link: "https://facebook.com/pedro.luis", Snippet: "Página pessoal..."}
]

Por que isso importa?
A decisão de retornar um slice de structs em vez de um array ou slice de strings simples reflete o objetivo do NameSniper de fornecer dados estruturados da API do Google. Um slice de strings (ex.: `[ ]string`) limitaria o retorno a apenas um campo (ex.: só URLs), perdendo informações valiosas como títulos e snippets, que são cruciais para um passive recon. O slice, por ser dinâmico, também se adapta ao número variável de resultados retornados pela API, enquanto a struct organiza os dados de forma clara.

A URL é montada com `fmt.Sprintf`, combinando as variáveis globais e a query. O uso do `http.Client` com `Timeout` (15 segundos) foi uma decisão crítica para lidar com a eventualidade falhas da API e proteger contra travamentos. O `defer resp.Body.Close()` garante que o recurso seja liberado, uma boa prática em Go.

A lógica de erros é um componente importantíssimo (ma minha opinião), pois me permite saber exatamente o que há de errado ou se algo foi esquecido por acidente. Aqui eu trato falhas na requisição, códigos de status não-OK (ex.: 403, 429) com mensagens detalhadas, e erros de parsing JSON. Retornar `nil` em caso de falha simplifica o controle no `main()`, mantendo o código previsível.

5. Gerenciamento de Limites — `podeFazerConsulta()` e `atualizarContador()`

func podeFazerConsulta() bool {
contador := carregarContador()
hoje := time.Now().UTC().Format("2006-01-02")
if contador.Data != hoje {
contador.Data = hoje
contador.Consultas = 0
salvarContador(contador)
}
if contador.Consultas >= limiteDiario {
return false
}
if contador.Consultas == limiteDiario-1 {
fmt.Println("Atenção: Esta é a última consulta gratuita do dia!")
}
return true
}

func atualizarContador() {
contador := carregarContador()
contador.Consultas++
salvarContador(contador)
fmt.Printf("Consultas usadas hoje: %d/%d\n", contador.Consultas, limiteDiario)
}

O limite de 100 consultas foi outro ponto importante no meu esboço, então eu precisava de uma função booleanda que retornasse um “sim” (verdadeiro, `true`) ou “não” (falso, `false`) para indicar se ainda havia consultas disponíveis naquele dia. A função `podeFazerConsulta()` vai até até o arquivo `consultas.json` e pega um registro que guarda informações sobre quantas consultas já fizemos hoje. Esse registro é chamado de `contador` e tem dois pedaços de informação: a data de hoje (no formato “ano-mês-dia”, como “2025–02–25”) e o número de consultas já realizadas.

Nota: essa etapa é feita chamando uma função auxiliar chamada `carregarContador()` (próxima função), que lê o arquivo e traz essas informações para dentro do programa.

Usar `time.Now().UTC()` garante consistência global — pensei nisso imaginando o progama rodando em diferentes fusos. A lógica de reset diário e o aviso na última consulta vieram da anotação sobre usabilidade: o usuário precisa saber onde está pisando. Eu não sou das pessoas mais atentas do mundo, portanto eu preciso compensar de alguma forma.

A função compara a data que está no contador com a data de hoje. Se as datas não forem iguais — ou seja, se o contador está de ontem ou de outro dia — , significa que começamos um novo dia. Nesse caso, ela atualiza o `contador` para refletir o dia atual, zera o número de consultas (porque começamos do zero hoje) e salva essas mudanças de volta no arquivo `consultas.json` com outra função auxiliar, chamada `salvarContador()`. Isso garante que o contador “reinicia” automaticamente a cada novo dia.

Depois dessa verificação, a função checa se já usamos muitas consultas hoje. Ela olha o número de consultas no contador e vê se ele é igual ou maior que 100, que é o limite diário que definimos (chamado `limiteDiario`). Se já chegamos ou passamos das 100 consultas, a função retorna `false, equanto a `main()` simplesmente diz “Limite diário de 100 consultas gratuitas atingido. Tente novamente amanhã.” e encerra a execução (os.Exit(1)), bloqueando qualquer nova busca para respeitar o limite gratuito da API.

Se ainda temos consultas disponíveis (ou seja, o número de consultas é menor que 100), a função dá uma olhada final: ela verifica se estamos na penúltima consulta do dia — ou seja, se o número de consultas é 99 (porque o limite é 100, e 99 é a última consulta antes de atingir o limite). Se for o caso, ela exibe uma mensagem no terminal dizendo “Atenção: Esta é a última consulta gratuita do dia!” para avisar o usuário que estamos quase no limite. Isso é só um toque de gentileza para ajudar quem está usando a ferramenta.

Por fim, se tudo estiver OK — ou seja, não ultrapassamos o limite e não é a última consulta — , a função simplesmente retorna `true` e segue fazendo o seu trabalho, permitindo que o programa continue e execute a busca.

A função `atualizarContador()`, por sua vez, é como um contador automático que registra cada vez que fazemos uma nova busca com o NameSnipe, garantindo que o sistema acompanhe quantas consultas já usamos no dia. O funcionamento é semelhante a função anterior, ela vai ate o JSON com registro das consultas, verifica o número de consultas feitas no dia e atualiza esse valor, ou seja, ela incrementa o número de consultas em 1. Isso é como dizer: “mais uma busca foi executada, então adicionamos mais uma marcação ao nosso contador”. No código, isso é feito com `contador.Consultas++`, que simplesmente aumenta o valor de `Consultas` em 1. Em seguida, a função salva esse contador atualizado de volta no arquivo `consultas.json`, usando outra função `salvarContador()`, e exibe uma mensagem no terminal dizendo quantas consultas já foram usadas hoje, em relação ao limite total permitido, por exemplo, “Consultas usadas hoje: 3/100”.

No Go, as goroutines poderiam até rodar essas funções em paralelo no futuro, mas por agora, a clareza e a modularidade já são suficientes para um MVP. Separar a verificação (`podeFazerConsulta()`) da atualização (`atualizarContador()`) mantém as responsabilidades claras, facilitando testes e futuras expansões.

6. Persistência do Contador — `carregarContador()` e `salvarContador()`

func carregarContador() Contador {
data, err := ioutil.ReadFile(contadorFile)
if err != nil {
return Contador{Data: time.Now().UTC().Format("2006-01-02"), Consultas: 0}
}
var contador Contador
if err := json.Unmarshal(data, &contador); err != nil {
return Contador{Data: time.Now().UTC().Format("2006-01-02"), Consultas: 0}
}
return contador
}

func salvarContador(contador Contador) {
data, err := json.Marshal(contador)
if err != nil {
fmt.Println("Erro ao salvar contador: ", err)
return
}
if err := ioutil.WriteFile(contadorFile, data, 0644); err != nil {
fmt.Println("Erro ao escrever arquivo: ", err)
}
}

Por fim, a função `carregarContador()` é como um arquivista que vai até um armário (neste caso, um arquivo no disco chamado consultas.json) para buscar as informações sobre quantas consultas já fizemos com o NameSnipe. Ela retorna o `contador` — o registro que guarda a data atual e o número de consultas feitas hoje.

Primeiro, ela tenta abrir e ler o arquivo `consultas.json` no disco, usando uma ferramenta do Go chamada `ioutil.ReadFile()`. Se algo der errado — por exemplo, se o arquivo não existir, estiver corrompido ou não puder ser lido — , ela não encerra o programa, mas cria um novo contador padrão: define a data como o dia atual, usando `time.Now().UTC().Format(“2006–01–02”)` para garantir o formato correto, como “2025–02–25”, e o número de consultas como `0`.

Se o arquivo for lido com sucesso, ela pega os dados brutos em JSON e tenta transformá-lo em um “contador” que o programa entende, usando `json.Unmarshal()`. Esse processo converte o JSON em uma estrutura do Go (a struct `Contador` que definimos antes, com campos `Data` e `Consultas`). Se houver algum erro nesse processo — por exemplo, o JSON estiver mal formado — , ela novamente retorna um contador zerado, com a data de hoje, mas o programa continua funcionando. Se estiver tudo ok, a função simplesmente retorna o contador carregado do arquivo, com a data e o número de consultas já registrados.

Os erros são logados mas não interrompem a execução, priorizando continuidade sobre perfeição em um MVP.

Por Que Usar Persistência em JSON?

O NameSnipe precisa lembrar quantas vezes você usou a API do Google hoje, mesmo depois de fechar o programa. No esboço inicial, anotei “persistir contador” como essencial para respeitar o limite diário de 100 consultas. Para isso, precisava de algo simples, leve e fácil de usar, tanto para mim (ao desenvolver) quanto para o usuário (que não precisa instalar bancos de dados pesados). E, honestamente, eu ainda não sei como fazer isso sem gastar imenso tempo resolvendo bugs.

O Go tem suporte nativo para JSON (encoding/json), o que torna a leitura e escrita absurdamente fáceis com `json.Unmarshal()` e `json.Marshal()`. No esboço, anotei que queria evitar bibliotecas externas para o núcleo do projeto, e JSON me permitiu isso. Comparado a outras opções, como salvar em CSV ou texto puro, JSON é mais estruturado e menos propenso a erros de parsing. Tanto `carregarContador()` quanto `salvarContador()` lidam com erros de forma graciosa. Se o arquivo não existir ou estiver corrompido, o programa retorna um contador zerado ou loga o erro sem parar. :)

É um formato de texto fácil de ler e escrever, tanto para humanos quanto para máquinas. Um arquivo como o `consultas.json` com { “data”: “2025–02–25”, “consultas”: 42 } é direto, e eu posso abrir no editor para debug, se necessário. É portável, funciona em qualquer sistema (Linux, macOS, Windows) sem dependências extras, desde que o Go esteja instalado.

JSON me permitiu atingir isso com mínimo esforço, mas já pensando no futuro — se o contador crescer para múltiplas fontes (como o Xwitter, por exemplo), posso ajustar o schema JSON sem reescrever tudo.

Se você chegou até aqui, obrigado por acompanhar este artigo! O NameSniper é um projeto em evolução, e estou curioso para ver como ele se sai em aderência e como pode crescer com contribuições da comunidade. Se você gostou, compartilhe suas ideias, sugestões ou até mesmo pull requests no GitHub. Fique à vontade para me seguir aqui no Medium e no GitHub para mais insights sobre cibersegurança, ethical hacking e aprendizado em Go.

--

--

André Ataíde
André Ataíde

Written by André Ataíde

0 Followers

Systems Analyst

No responses yet