
Detecção e rastreamento automatizados de engajamento falso no GitHub — CI diário, infraestrutura zero
Detecção e rastreamento automatizados de engajamento falso no GitHub
Um projeto do JS Labs —
parte da iniciativa AI Slop Intelligence.
Executa todos os dias. Pontua todas as contas suspeitas. Detecta campanhas coordenadas de bots.
Abre issues diretamente em repositórios comprometidos para que mantenedores possam agir.
Apoie este projeto
BTC 3QjWqhQbHdHgWeYHTpmorP8Pe1wgDjJy54
ETH 0x5851e6145F4773d1585b8686095FB16E368a4dA1
ZEC t1KSR5YkNPbjqRSCoLKo5AddFWdm9Kzxh1B
As estrelas do GitHub são um sinal de confiança. São como desenvolvedores decidem o que avaliar, do que depender e o que recomendar. Esse sinal está sendo sistematicamente corrompido.
Durante o boom de IA de 2024-2026, surgiu uma indústria de fazendas de bots para fabricar credibilidade para repositórios de baixa qualidade, muitas vezes maliciosos. Um projeto com 800 estrelas em 48 horas parece legítimo para um desenvolvedor examinando resultados de pesquisa. Esse é o ponto. O objetivo do engajamento falso não são as estrelas em si; é a prova social que essas estrelas produzem e as decisões a jusante que essa prova social influencia.
O padrão é identificável. Contas criadas na mesma semana, sem biografia, sem seguidores, sem repositórios originais, estrelando os mesmos 15 repositórios dentro de uma janela de 2 horas. Não uma campanha, mas dezenas executadas simultaneamente, todos os dias, em milhares de contas. Os dados mostram repositórios onde 185 de 185 engajadores são bots. Uma taxa de falsidade de 100%. Colocações inteiras de trending construídas sobre nada.
phantomstars foi criado porque esse problema é tratável. A relação sinal-ruído na API pública do GitHub ainda é, por enquanto, alta o suficiente para que campanhas coordenadas deixem impressões digitais claras. Este projeto lê essas impressões, publica os dados brutos e notifica os mantenedores dos repositórios afetados diretamente.
Isso faz parte do trabalho mais amplo do AI Slop Intelligence no JS Labs, uma pesquisa contínua sobre os mecanismos e efeitos mensuráveis do conteúdo de baixa qualidade gerado por IA inundando ecossistemas de desenvolvedores. Engajamento falso não é um problema periférico. É o mecanismo de distribuição que coloca o "slop" na frente de usuários reais.
O phantomstars executa um job diário do GitHub Actions que:
r/osinttools e r/coolgithubprojects, extraindo links de repositórios GitHub dos últimos 2 diasSem servidores. Sem bancos de dados. Sem custo de infraestrutura.
Sim. Quando a taxa de falsidade de um repositório excede 40% ou uma campanha coordenada é detectada, o phantomstars abre uma issue diretamente naquele repositório. A issue contém a tabela completa de suspeitos, associação à campanha, pontuações compostas e datas de criação das contas: tudo que um mantenedor precisa para investigar e reportar ao GitHub.
Se as issues estiverem desabilitadas no repositório alvo, a notificação é ignorada silenciosamente e registrada no log de varredura.
Sim.
proprietario/repo e execute uma varredura direcionada.Por que a separação:
Sim. Se sua conta aparecer em data/suspects.jsonl e você acreditar que a classificação está incorreta, abra uma issue de falso positivo usando o template fornecido. Os relatos são revisados manualmente antes de qualquer adição à lista de permissões. A lista de permissões está armazenada em data/allowlist.txt; as contas listadas lá são excluídas de todas as varreduras futuras e do ledger de suspeitos.
Um ID de campanha (ex.: c-a3f9b2e1) é uma impressão digital hexadecimal determinística de 8 caracteres derivada do hash SHA-256 do conjunto ordenado de logins dos membros daquela campanha. O mesmo grupo de contas produzirá o mesmo ID de campanha em execuções de varredura independentes, permitindo rastreamento longitudinal. Não é um nome de repositório, um nome de usuário ou qualquer identificador externo.
Estabilidade: o ID é estável enquanto o conjunto de membros da campanha permanecer inalterado. Se bots forem adicionados ou suspensos entre varreduras, o ID muda porque a associação mudou. Isso é esperado e reflete a deriva real na composição da fazenda de bots.
Sim. A data de criação de cada conta é obtida da API GraphQL do GitHub (campo createdAt) e armazenada em cada registro de suspeito como account_created_at. Também é a entrada principal da pontuação de idade da conta, o sinal único mais forte para contas falsas. Contas criadas em até 2 dias do engajamento pontuam 1,0 apenas na idade.
Pontuações individuais carregam taxas de falso positivo significativas. Um novo desenvolvedor com perfil esparso legitima-se a uma pontuação de 0,75+. A ferramenta lida com isso exigindo evidências em nível de campanha antes de abrir issues; uma única conta suspeita não é suficiente. Um cluster coordenado de 40+ contas, todas criadas na mesma semana, todas com pontuação 0,75+, todas engajando dentro de 90 minutos, é outra história. É aí que a confiança se torna acionável.
Os dados são sempre probabilísticos. Os corpos das issues dizem isso explicitamente. O objetivo é dar aos mantenedores o sinal e as evidências brutas para que tomem seu próprio julgamento.
Cada conta recebe uma pontuação composta de suspeita (0,0 = limpa, 1,0 = provavelmente falsa) a partir de quatro sinais:
Limiares de classificação:
| Pontuação | Classificação |
|---|---|
| ≥ 0,75 | likely_fake |
| ≥ 0,45 | suspicious |
| < 0,45 | clean (não armazenado) |
Uma campanha é um grupo de ≥ 4 contas suspeitas que engajaram no mesmo repositório dentro de uma janela de 3 horas. O algoritmo usa union-find para construir componentes conectados; contas que co-engajaram dentro da janela são mescladas, e qualquer componente acima do tamanho mínimo é sinalizado como campanha coordenada.
Os IDs de campanha são impressões digitais SHA-256 estáveis do conjunto ordenado de membros. A mesma campanha detectada em dias consecutivos terá o mesmo ID desde que a associação permaneça inalterada.
Por que as campanhas são o sinal real: Pontuações individuais têm taxas de falso positivo significativas. Um novo desenvolvedor com perfil esparso pode atingir 0,80 sozinho. Quarenta contas todas com pontuação 0,75+, criadas na mesma semana, todas estrelando o mesmo repositório em 90 minutos, não é coincidência. O sinal da campanha é onde os dados se tornam acionáveis: a diferença entre um ponto de dado suspeito e evidência de uma operação coordenada.
Todas as descobertas são commitadas em data/suspects.jsonl e data/repos.jsonl, um registro JSON por linha, somente de acréscimo. O resumo do job do GitHub Actions (visível na interface do Actions após cada execução) fornece um relatório formatado por varredura.
suspects.jsonl — um registro por conta sinalizada por varredura:```json { "login": "user98432", "account_age_score": 0.9, "profile_score": 0.8, "repo_pattern_score": 0.8, "activity_score": 0.85, "composite": 0.842, "classification": "likely_fake", "campaign_id": "c-a3f9b2e1", "scan_date": "2026-05-17", "account_created_at": "2026-05-15", "target_repos": ["owner/repo-a", "owner/repo-b"] }
**repos.jsonl** — um registro por repositório alvo por varredura:```json
{
"full_name": "owner/suspicious-repo",
"total_scanned": 87,
"likely_fake": 62,
"suspicious": 18,
"known_likely_fake": 27,
"known_likely_fake_ratio": 0.310,
"repeat_offenders": 11,
"allowlisted_excluded": 3,
"fakeness_ratio": 0.713,
"classification": "likely_fake",
"campaign_count": 3,
"discovery_sources": ["github_search_recent", "reddit_osinttools"],
"event_sample_complete": false,
"scan_date": "2026-05-17"
}
Exemplos de consulta:```bash
jq 'select(.scan_date == "2026-05-17" and .classification == "likely_fake") | .login' data/suspects.jsonl
jq 'select(.account_created_at >= "2026-05-14") | [.login, .account_created_at, .classification] | @tsv' -r data/suspects.jsonl
jq 'select(.scan_date == "2026-05-17") | [.full_name, .fakeness_ratio, .likely_fake] | @tsv' -r data/repos.jsonl | sort -t$'''\t''' -k2 -rn
jq 'select(.scan_date == "2026-05-17") | [.full_name, .known_likely_fake_ratio, .repeat_offenders] | @tsv' -r data/repos.jsonl | sort -t$'''\t''' -k2 -rn
jq 'select(.campaign_id == "c-a3f9b2e1") | [.login, .account_created_at, .composite] | @tsv' -r data/suspects.jsonl
jq 'select(.login == "user98432") | .target_repos[]' data/suspects.jsonl
jq 'select(.fakeness_ratio >= 0.6) | [.full_name, .fakeness_ratio, .campaign_count] | @tsv' -r data/repos.jsonl | sort -t$'\t' -k2 -rn
---
## Configuração
### 1. Faça um fork deste repositório
O seu fork é dono dos dados. Os resultados são submetidos de volta para `data/suspects.jsonl` e `data/repos.jsonl` no seu fork após cada execução diária.
### 2. Adicione um segredo PAT do GitHub
Crie um **classic** Personal Access Token com escopos:
- `public_repo`: ler eventos de repositórios públicos e stargazers, criar issues em repositórios públicos
- `read:user`: buscar perfis de usuário via GraphQL
**Settings → Secrets and variables → Actions → New repository secret** → nomeie-o como `GH_TOKEN`.
> O `GITHUB_TOKEN` padrão tem limites de taxa restritos e não pode chamar o endpoint GraphQL de usuário em capacidade total. Um PAT é necessário.
### 3. Habilite Actions
**Actions → Enable GitHub Actions** no seu fork. O fluxo de trabalho é executado às **07:00 hora do Reino Unido diariamente** usando o relógio `Europe/London`:
- **06:00 UTC** durante o Horário de Verão Britânico
- **07:00 UTC** durante o Horário de Greenwich
Nenhuma variável de ambiente de agendamento extra é necessária. O cron do GitHub Actions é apenas UTC, então o fluxo de trabalho é acionado em ambas as horas UTC e só prossegue quando a hora local em Londres é 07:00. Acionamento manual disponível via **Actions → Daily Phantom Stars Scan → Run workflow**.
Após cada execução, o relatório formatado da varredura fica visível em **Actions → [run] → Summary**.
### 4. Executar localmente```bash
git clone https://github.com/YOUR_USERNAME/phantomstars.git
cd phantomstars
python -m venv venv && source venv/bin/activate
pip install -e .
GH_TOKEN=ghp_your_token python -m phantomstars.main
Para uma execução local ad hoc após a configuração:```bash GH_TOKEN=ghp_your_token python -m phantomstars.main
Para escanear um repositório em vez do conjunto de descoberta normal:```bash
PHANTOMSTARS_TARGET_REPO=owner/repo GH_TOKEN=ghp_your_token python -m phantomstars.main
Os usuários podem solicitar uma verificação de repositório única de duas maneiras:
Repo Check Request e forneça o repositório alvo mais a profundidade solicitada.target_repo: owner/reporequest_depth: recent ou lifetime-requestComportamento atual:
recent: executa a varredura direcionada de engajamento recente imediatamente.lifetime-request: executa uma varredura direcionada vitalícia das estrelas e forks históricos apenas para aquele repositório.Diretrizes de segurança para o modo vitalício:
phantomstars/ ├── .github/ │ ├── workflows/daily-scan.yml # Runs daily at 07:00 Europe/London │ └── ISSUE_TEMPLATE/false_positive.yml ├── src/phantomstars/ │ ├── config.py # All constants, no argparse, no env parsing │ ├── models.py # Frozen dataclasses │ ├── github_client.py # REST + GraphQL, tenacity retries, rate-limit aware │ ├── heuristics.py # Per-user composite scoring engine │ ├── campaigns.py # Timestamp clustering + union-find │ ├── storage.py # JSONL append + query helpers │ ├── reporter.py # README dashboard injector │ ├── notifier.py # GitHub Issues notifier (files on targeted repos) │ └── main.py # Orchestration entry point ├── tests/ │ ├── conftest.py │ ├── test_heuristics.py │ └── test_campaigns.py ├── data/ │ ├── suspects.jsonl # Append-only account findings ledger │ ├── repos.jsonl # Append-only per-repo intelligence │ └── allowlist.txt # Accounts excluded from future scans └── pyproject.toml
---
## Limitações e modos de falha conhecidos
- **Limite da API de Eventos:** máximo de 300 eventos recentes por repositório. Repositórios com milhares de estrelas em um dia têm cobertura parcial.
- **Sinalizador de cobertura:** repositórios que atingem o limite de 300 eventos são marcados como `capped` em relatórios e painéis; as proporções nesses repositórios são amostras conservadoras, não contagens de dia inteiro.
- **Atraso no índice de pesquisa:** o índice de pesquisa do GitHub é eventualmente consistente. Repositórios criados segundos antes do limite da verificação podem ser perdidos.
- **Desvio heurístico:** Operadores de bots se adaptam. Os pesos das pontuações podem exigir ajustes periódicos; ajuste as constantes em `config.py`.
- **Falsos positivos individuais:** Um novo desenvolvedor com um perfil esparso pontua 0,75+ isoladamente. A associação a campanhas é o sinal de alta confiança.
- **Deriva do ID de campanha:** Se a associação de uma granja de bots muda entre varreduras (bots suspensos, novos bots adicionados), o ID da campanha muda. Isso reflete a evolução real da campanha, não um bug.
- **Limites de taxa:** 5.000 requisições de API/hora em um PAT autenticado. Bem dentro dos limites para tamanhos padrão de páginas de tendências.
- **Issues desativadas:** Alguns repositórios alvo desativam issues. Notificações para esses repositórios são ignoradas silenciosamente.
---
## Processo de falso positivo
Se sua conta aparecer em `data/suspects.jsonl` e você acreditar que foi classificada incorretamente:
1. Encontre sua entrada: `jq 'select(.login == "YOUR_LOGIN")' data/suspects.jsonl`
2. [Abra uma issue de falso positivo](https://raw.githubusercontent.com/tg12/issues/new?template=false_positive.yml) com seu login, classificação, data da varredura e explicação
3. Os relatórios são revisados manualmente. Falsos positivos verificados são adicionados a `data/allowlist.txt` e excluídos de todas as varreduras futuras, proporções de repositórios e notificações de issues.
Nota: abrir uma issue não modifica ou remove nenhum dado existente. O registro de suspeitos é somente para anexação. A lista de permissões afeta apenas varreduras futuras.
---
## Contribuindo```bash
pip install -e ".[dev]"
python -m black .
python -m ruff check .
python -m mypy src
python -m pytest
Todos os quatro devem ser aprovados antes de um PR.
Esta ferramenta realiza análise somente de leitura de dados públicos do GitHub utilizando a API oficial do GitHub. Quando issues são abertas em repositórios-alvo, elas contêm achados probabilísticos e são claramente rotuladas como automatizadas. Os achados são indicadores, não acusações. Falsos positivos existem e são esperados.
Construído com IA como parceira de codificação, em resposta a um problema do ecossistema criado em parte pela IA.
Apache 2.0. Veja LICENSE
Construído por tg12 · GitHub
Um projeto JS Labs · AI Slop Intelligence Dashboards
| Data | Digitalizados | Provavelmente Falsos | Suspeitos | Campanhas | Novos Falsos (24h) |
|---|
| 2026-06-16 | 1846 | 221 | 1625 | 56 | 190 |
| 2026-06-15 | 2274 | 418 | 1856 | 61 | 397 |
| 2026-06-14 | 1953 | 355 | 1598 | 44 | 310 |
| 2026-06-13 | 2012 | 301 | 1711 | 47 | 251 |
| 2026-06-12 | 2298 | 336 | 1962 | 57 | 300 |
| 2026-06-11 | 1957 | 385 | 1572 | 42 | 356 |
| 2026-06-10 | 2043 | 687 | 1356 | 50 | 665 |
| 2026-06-09 | 2199 | 690 | 1509 | 44 | 632 |
| 2026-06-08 | 1913 | 450 | 1463 | 50 | 424 |
| 2026-06-07 | 1797 | 658 | 1139 | 30 | 618 |
| 2026-06-06 | 2625 | 712 | 1913 | 40 | 620 |
| 2026-06-05 | 2403 | 673 | 1730 | 53 | 617 |
| 2026-06-04 | 2237 | 441 | 1796 | 41 | 367 |
| 2026-06-03 | 2331 | 488 | 1843 | 53 | 431 |
| 2026-06-02 | 2795 | 773 | 2022 | 37 | 616 |
| 2026-06-01 | 2490 | 533 | 1957 | 39 | 355 |
| 2026-05-31 | 2302 | 458 | 1844 | 32 | 280 |
| 2026-05-30 | 2576 | 530 | 2046 | 20 | 356 |
| 2026-05-29 | 2838 | 733 | 2105 | 42 | 369 |
| 2026-05-28 | 2748 | 694 | 2054 | 39 | 396 |
| 2026-05-27 | 2193 | 560 | 1633 | 32 | 491 |
| 2026-05-26 | 1930 | 236 | 1694 | 43 | 190 |
| 2026-05-25 | 1526 | 214 | 1312 | 32 | 158 |
| 2026-05-24 | 2170 | 358 | 1812 | 39 | 265 |
| 2026-05-23 | 2548 | 426 | 2122 | 43 | 317 |
| 2026-05-22 | 2318 | 340 | 1978 | 47 | 247 |
| 2026-05-21 | 1981 | 348 | 1633 | 25 | 277 |
| 2026-05-20 | 1613 | 268 | 1345 | 23 | 163 |
| 2026-05-19 | 5463 | 630 | 4121 | 67 | 442 |
| 2026-05-18 | 8838 | 670 | 7950 | 128 | 340 |
| Repositório | Engajadores | Provavelmente Falsos | % Falsos Conhecidos | % Falsidade | Campanhas | Cobertura | Fontes |
|---|
| freeCodeCamp/freeCodeCamp | 264 | 26 | 0.0% | 9.8% | 1 | complete | github_trending |
| Lolner95/use-kimi-on-cursor | 116 | 17 | 26.7% | 14.7% | 1 | complete | github_search_recent |
| zmustafa/AzureSupportAgent | 33 | 16 | 36.4% | 48.5% | 1 | complete | github_search_recent |
| Free-TV/IPTV | 291 | 14 | 0.3% | 4.8% | 1 | complete | github_trending |
| Panniantong/Agent-Reach | 292 | 14 | 0.3% | 4.8% | 1 | capped | github_trending |
| jwasham/coding-interview-university | 288 | 12 | 0.3% | 4.2% | 1 | capped | github_trending |
| Alex-Shayo/bakkes-mod-install | 37 | 10 | 0.0% | 27.0% | 1 | complete | github_search_recent |
| Timgt86/yt-downloader-savetube | 37 | 10 | 0.0% | 27.0% | 1 | complete | github_search_recent |
| devassisthub/Zelda-TP-PC-Port | 35 | 9 | 0.0% | 25.7% | 1 | complete | github_search_recent |
| imohammedyasin/steam-tools | 35 | 9 | 0.0% | 25.7% | 1 | complete | github_search_recent |
| lol-toolkit/ltk-manager-lol | 35 | 9 | 0.0% | 25.7% | 1 | complete | github_search_recent |
| tor-browser-download/tor-browser | 36 | 9 | 0.0% | 25.0% | 1 | complete | github_search_recent |
| claude-code-ai-anthropic/free-claude-code-ai-desktop-app | 38 | 9 | 0.0% | 23.7% | 1 | complete | github_search_recent |
| vitaliikapliuk/modelharness | 60 | 9 | 31.7% | 15.0% | 1 | complete | github_search_recent |
| shiyu-coder/Kronos | 282 | 9 | 1.1% | 3.2% | 1 | complete | github_trending |
| snanas/Forza-Horizon-Spotify-Radio | 34 | 8 | 0.0% | 23.5% | 1 | complete | github_search_recent |
| bingook/bingo | 45 | 8 | 2.2% | 17.8% | 2 | complete | github_search_recent |
| darricke/claude-fable-5-desktop-free | 49 | 8 | 0.0% | 16.3% | 1 | complete | github_search_recent |
| Ponzuu84/MaaNTE | 32 | 7 | 0.0% | 21.9% | 1 | complete | github_search_recent |
| iDesignStudioz/yellowkey-bitlocker | 33 | 7 | 0.0% | 21.2% | 1 | complete | github_search_recent |
| chatwoot/chatwoot | 230 | 7 | 0.0% | 3.0% | 1 | complete | github_trending |
| Open-Builders/pumpfun-bundler-pump.fun-bundler-solana-token-bundler-bot | 18 | 6 | 33.3% | 33.3% | 1 | complete | github_search_recent |
| taisly/agent | 23 | 6 | 17.4% | 26.1% | 2 | complete | github_search_recent |
| iptv-org/iptv | 193 | 6 | 0.0% | 3.1% | 1 | complete | github_trending |
| itsfatduck/optimizerDuck | 293 | 6 | 0.3% | 2.0% | 1 | complete | github_trending |
| Sinal | Peso | Medição |
|---|
| Idade da conta | 35% | < 2 dias → 1,00 · < 7 dias → 0,90 · < 30 dias → 0,55 · < 90 dias → 0,20 · mais velho → 0,00 |
| Completude do perfil | 30% | Pontuações para: sem biografia (+0,25), sem localização (+0,15), sem empresa (+0,10), zero seguidores (+0,30), zero seguindo (+0,10), nome de usuário com padrão de bot (+0,20) |
| Padrão de repositório | 25% | Zero repositórios → 0,90 · todos os repositórios são forks → 0,80 · >85% de fork ratio → 0,55 |
| Histórico de atividade | 10% | Contas com mais de 14 dias, zero repositórios + zero grafo social → 0,80 (contas fantasmas). Apenas zero repositórios → 0,60. Todos forks + sem grafo social → 0,50 |