
Rilevamento e monitoraggio automatici di coinvolgimento falso su GitHub — CI giornaliero, infrastruttura zero
Rilevamento e tracciamento automatico di interazioni fasulle su GitHub
Un progetto JS Labs —
parte dell'iniziativa AI Slop Intelligence.
Viene eseguito ogni giorno. Valuta ogni account sospetto. Rileva campagne bot coordinate.
Apre issue direttamente sui repos compromessi in modo che i manutentori possano agire.
Sostieni questo progetto
BTC 3QjWqhQbHdHgWeYHTpmorP8Pe1wgDjJy54
ETH 0x5851e6145F4773d1585b8686095FB16E368a4dA1
ZEC t1KSR5YkNPbjqRSCoLKo5AddFWdm9Kzxh1B
Le stelle di GitHub sono un segnale di fiducia. Sono il modo in cui gli sviluppatori decidono cosa valutare, da cosa dipendere e cosa raccomandare. Quel segnale viene sistematicamente corrotto.
Durante il boom dell'AI del 2024-2026 è emersa un'industria di farm di bot per fabbricare credibilità per repository di bassa qualità, spesso malevoli. Un progetto con 800 stelle in 48 ore appare legittimo a uno sviluppatore che scansiona i risultati di ricerca. Questo è il punto. L'obiettivo delle interazioni fasulle non sono le stelle in sé, ma la prova sociale che quelle stelle producono e le decisioni a valle che quella prova sociale influenza.
Lo schema è identificabile. Account creati la stessa settimana, nessuna biografia, nessun follower, nessun repository originale, che mettono stella agli stessi 15 repo in una finestra di 2 ore. Non una campagna, ma dozzine eseguite simultaneamente, ogni giorno, su migliaia di account. I dati mostrano repo in cui 185 su 185 interagenti sono bot. Un rapporto di falsità del 100%. Intere posizioni in tendenza costruite sul nulla.
phantomstars è stato costruito perché questo problema è trattabile. Il rapporto segnale-rumore nell'API pubblica di GitHub è, per ora, ancora sufficientemente alto da far sì che le campagne coordinate lascino impronte chiare. Questo progetto legge quelle impronte, pubblica i dati grezzi e notifica direttamente i manutentori dei repository interessati.
Questo fa parte del più ampio lavoro AI Slop Intelligence presso JS Labs, una ricerca in corso sulla meccanica e gli effetti misurabili dei contenuti di bassa qualità generati dall'AI che inondano gli ecosistemi degli sviluppatori. Le interazioni fasulle non sono un problema periferico. È il meccanismo di distribuzione che porta la spazzatura davanti agli utenti reali.
phantomstars esegue un lavoro giornaliero su GitHub Actions che:
r/osinttools e r/coolgithubprojects estraendo collegamenti a repo GitHub degli ultimi 2 giorniNiente server. Niente database. Niente bolletta infrastrutturale.
Sì. Quando il rapporto di falsità di un repo supera il 40% o viene rilevata una campagna coordinata, phantomstars apre un'issue direttamente su quel repository. L'issue contiene la tabella completa dei sospetti, l'appartenenza alla campagna, i punteggi compositi e le date di creazione degli account: tutto ciò di cui un manutentore ha bisogno per indagare e segnalare a GitHub.
Se le issue sono disabilitate su un repo target, la notifica viene saltata silenziosamente e registrata nel log di scansione.
Sì.
owner/repo ed esegui una scansione mirata.Perché questa suddivisione:
Sì. Se il tuo account appare in data/suspects.jsonl e ritieni che la classificazione sia errata, apri un'issue di falso positivo usando il modello fornito. I rapporti vengono esaminati manualmente prima di qualsiasi aggiunta alla lista di esclusione. La lista di esclusione è archiviata in data/allowlist.txt; gli account elencati sono esclusi da tutte le scansioni future e dal registro dei sospetti.
Un ID campagna (ad es. c-a3f9b2e1) è un'impronta esadecimale deterministica di 8 caratteri derivata dall'hash SHA-256 dell'insieme ordinato dei login dei membri di quella campagna. Lo stesso gruppo di account produrrà lo stesso ID campagna in esecuzioni di scansione indipendenti, consentendo il monitoraggio longitudinale. Non è un nome di repo, un nome utente o qualsiasi identificatore esterno.
Stabilità: l'ID è stabile finché l'insieme dei membri della campagna rimane invariato. Se i bot vengono aggiunti o sospesi tra le scansioni, l'ID cambia perché la membership è cambiata. Questo è previsto e riflette la deriva reale nella composizione delle farm di bot.
Sì. La data di creazione di ogni account viene recuperata dall'API GraphQL di GitHub (campo createdAt) e archiviata in ogni record sospetto come account_created_at. È anche l'input principale per il punteggio di età dell'account, il singolo segnale più forte per account falsi. Gli account creati entro 2 giorni dall'interazione ottengono un punteggio di 1.0 solo per l'età.
I punteggi individuali comportano tassi di falsi positivi significativi. Un nuovo sviluppatore con un profilo scarno ottiene legittimamente un punteggio di 0.75+. Lo strumento tiene conto di ciò richiedendo prove a livello di campagna prima di aprire issue; un singolo account sospetto non è sufficiente. Un cluster coordinato di 40+ account, tutti creati la stessa settimana, tutti con punteggio 0.75+, tutti interagenti entro 90 minuti, è un'altra storia. È lì che la fiducia diventa attuabile.
I dati sono sempre probabilistici. I corpi delle issue lo dicono esplicitamente. L'obiettivo è dare ai manutentori il segnale e le prove grezze per formulare il proprio giudizio.
Ogni account riceve un punteggio di sospetto composto (0.0 = pulito, 1.0 = probabilmente falso) basato su quattro segnali:
Soglie di classificazione:
| Punteggio | Classificazione |
|---|---|
| ≥ 0.75 | likely_fake (probabilmente falso) |
| ≥ 0.45 | suspicious (sospetto) |
| < 0.45 | clean (pulito, non memorizzato) |
Una campagna è un gruppo di ≥ 4 account sospetti che hanno tutti interagito con lo stesso repo in una finestra di 3 ore. L'algoritmo usa union-find per costruire componenti connesse; gli account che hanno co-interagito all'interno della finestra vengono uniti, e qualsiasi componente al di sopra della dimensione minima viene segnalata come campagna coordinata.
Gli ID campagna sono impronte SHA-256 stabili dell'insieme ordinato dei membri. La stessa campagna rilevata in giorni consecutivi avrà lo stesso ID finché la membership rimane invariata.
Perché le campagne sono il vero segnale: I punteggi individuali hanno tassi di falsi positivi significativi. Un nuovo sviluppatore con un profilo scarno può raggiungere un punteggio di 0.80 da solo. Quaranta account che ottengono tutti punteggi 0.75+, creati nella stessa settimana e che mettono stella allo stesso repo entro 90 minuti, non è una coincidenza. Il segnale della campagna è il punto in cui i dati diventano attuabili: la differenza tra un dato sospetto e la prova di un'operazione coordinata.
Tutti i risultati vengono commitati in data/suspects.jsonl e data/repos.jsonl, un record JSON per riga, append-only. Il riepilogo del job di GitHub Actions (visibile nell'interfaccia Actions dopo ogni esecuzione) fornisce un rapporto di scansione formattato per scansione.
suspects.jsonl — un record per account segnalato per scansione:```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** — un record per repo mirato per scansione:```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"
}
Esempi di query:```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
---
## Configurazione
### 1. Fork di questo repository
Il tuo fork possiede i dati. I risultati vengono salvati su `data/suspects.jsonl` e `data/repos.jsonl` sul tuo fork dopo ogni esecuzione giornaliera.
### 2. Aggiungi un segreto PAT di GitHub
Crea un **classic** Personal Access Token con ambiti:
- `public_repo`: leggere eventi e stargazer di repository pubblici, creare issue su repository pubblici
- `read:user`: recuperare profili utente tramite GraphQL
**Settings → Secrets and variables → Actions → New repository secret** → denominalo `GH_TOKEN`.
> Il `GITHUB_TOKEN` predefinito ha limiti di velocità ristretti e non può chiamare l'endpoint GraphQL utente a piena capacità. È richiesto un PAT.
### 3. Abilita Actions
**Actions → Enable GitHub Actions** sul tuo fork. Il workflow viene eseguito alle **07:00 ora del Regno Unito ogni giorno** utilizzando l'orologio `Europe/London`:
- **06:00 UTC** durante l'ora legale britannica
- **07:00 UTC** durante il Greenwich Mean Time
Non è necessaria alcuna variabile d'ambiente aggiuntiva per la pianificazione. Il cron di GitHub Actions è solo UTC, quindi il workflow si attiva a entrambe le ore UTC e procede solo quando l'ora locale di Londra è 07:00. Attivazione manuale disponibile tramite **Actions → Daily Phantom Stars Scan → Run workflow**.
Dopo ogni esecuzione, il report di scansione formattato è visibile in **Actions → [run] → Summary**.
### 4. Esecuzione locale```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
Per una esecuzione locale ad hoc dopo la configurazione:```bash GH_TOKEN=ghp_your_token python -m phantomstars.main
Per scansionare un singolo repository invece del set di scoperta normale:```bash
PHANTOMSTARS_TARGET_REPO=owner/repo GH_TOKEN=ghp_your_token python -m phantomstars.main
Gli utenti possono richiedere una verifica una tantum del repository in due modi:
Repo Check Request e fornisci il repository target insieme alla profondità richiesta.target_repo: owner/reporequest_depth: recent o lifetime-requestComportamento attuale:
recent: esegue immediatamente la scansione mirata degli engagement recenti.lifetime-request: esegue una scansione mirata per tutto il ciclo di vita su stelle e fork storici solo per quel repository.Limiti per la modalità lifetime:
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
---
## Limitazioni e modalità di errore note
- **Cap dell'API Eventi:** massimo 300 eventi recenti per repository. I repository con migliaia di stelle in un giorno hanno copertura parziale.
- **Flag di copertura:** i repository che raggiungono il limite di 300 eventi vengono contrassegnati come `capped` nei report e nelle dashboard; i rapporti su quei repository sono campioni conservativi, non conteggi giornalieri completi.
- **Ritardo dell'indice di ricerca:** l'indice di ricerca di GitHub è eventualmente coerente. I repository creati secondi prima del confine della scansione potrebbero essere persi.
- **Deriva euristica:** gli operatori di bot si adattano. I punteggi dei pesi potrebbero richiedere una regolazione periodica; regola le costanti in `config.py`.
- **Falsi positivi individuali:** uno sviluppatore nuovo con un profilo sparso ottiene un punteggio di 0,75+ in isolamento. L'appartenenza a una campagna è il segnale ad alta affidabilità.
- **Deriva dell'ID campagna:** se la composizione di una farm di bot cambia tra scansioni (bot sospesi, nuovi bot aggiunti), l'ID campagna cambia. Questo riflette l'effettiva evoluzione della campagna, non un bug.
- **Limiti di velocità:** 5.000 richieste API/ora su un PAT autenticato. Ben entro i limiti per le dimensioni standard delle pagine di tendenza.
- **Issue disabilitate:** alcuni repository target disabilitano le issue. Le notifiche per quei repository vengono saltate silenziosamente.
---
## Processo per falsi positivi
Se il tuo account appare in `data/suspects.jsonl` e ritieni che sia stato classificato in modo errato:
1. Trova la tua voce: `jq 'select(.login == "YOUR_LOGIN")' data/suspects.jsonl`
2. [Apri un'issue di falso positivo](https://raw.githubusercontent.com/tg12/issues/new?template=false_positive.yml) con il tuo login, classificazione, data di scansione e spiegazione
3. I report vengono esaminati manualmente. I falsi positivi verificati vengono aggiunti a `data/allowlist.txt` ed esclusi da tutte le scansioni future, dai rapporti sui repository e dalle notifiche delle issue.
Nota: aprire un'issue non modifica né rimuove alcun dato esistente. Il registro dei sospetti è solo in aggiunta. La lista bianca influisce solo sulle scansioni future.
---
## Contribuire```bash
pip install -e ".[dev]"
python -m black .
python -m ruff check .
python -m mypy src
python -m pytest
Tutti e quattro devono essere superati prima di una PR.
Questo strumento esegue un'analisi in sola lettura dei dati pubblici di GitHub utilizzando l'API ufficiale di GitHub. Dove vengono aperti issue sui repository target, contengono risultati probabilistici e sono chiaramente etichettati come automatici. I risultati sono indicatori, non accuse. I falsi positivi esistono e sono previsti.
Costruito con l'AI come partner di codifica, in risposta a un problema dell'ecosistema creato in parte dall'AI.
Apache 2.0. Vedi LICENSE
Realizzato da tg12 · GitHub
Un progetto JS Labs · AI Slop Intelligence Dashboards
| Data | Scansionati | Probabilmente Falsi | Sospetti | Campagne | Nuovi Falsi (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 |
| Repo | Interagenti | Probabilmente Falsi | % Falsi Noti | % Falsità | Campagne | Copertura | Fonti |
|---|
| freeCodeCamp/freeCodeCamp | 264 | 26 | 0.0% | 9.8% | 1 | completa | github_trending |
| Lolner95/use-kimi-on-cursor | 116 | 17 | 26.7% | 14.7% | 1 | completa | github_search_recent |
| zmustafa/AzureSupportAgent | 33 | 16 | 36.4% | 48.5% | 1 | completa | github_search_recent |
| Free-TV/IPTV | 291 | 14 | 0.3% | 4.8% | 1 | completa | github_trending |
| Panniantong/Agent-Reach | 292 | 14 | 0.3% | 4.8% | 1 | limitata | github_trending |
| jwasham/coding-interview-university | 288 | 12 | 0.3% | 4.2% | 1 | limitata | github_trending |
| Alex-Shayo/bakkes-mod-install | 37 | 10 | 0.0% | 27.0% | 1 | completa | github_search_recent |
| Timgt86/yt-downloader-savetube | 37 | 10 | 0.0% | 27.0% | 1 | completa | github_search_recent |
| devassisthub/Zelda-TP-PC-Port | 35 | 9 | 0.0% | 25.7% | 1 | completa | github_search_recent |
| imohammedyasin/steam-tools | 35 | 9 | 0.0% | 25.7% | 1 | completa | github_search_recent |
| lol-toolkit/ltk-manager-lol | 35 | 9 | 0.0% | 25.7% | 1 | completa | github_search_recent |
| tor-browser-download/tor-browser | 36 | 9 | 0.0% | 25.0% | 1 | completa | github_search_recent |
| claude-code-ai-anthropic/free-claude-code-ai-desktop-app | 38 | 9 | 0.0% | 23.7% | 1 | completa | github_search_recent |
| vitaliikapliuk/modelharness | 60 | 9 | 31.7% | 15.0% | 1 | completa | github_search_recent |
| shiyu-coder/Kronos | 282 | 9 | 1.1% | 3.2% | 1 | completa | github_trending |
| snanas/Forza-Horizon-Spotify-Radio | 34 | 8 | 0.0% | 23.5% | 1 | completa | github_search_recent |
| bingook/bingo | 45 | 8 | 2.2% | 17.8% | 2 | completa | github_search_recent |
| darricke/claude-fable-5-desktop-free | 49 | 8 | 0.0% | 16.3% | 1 | completa | github_search_recent |
| Ponzuu84/MaaNTE | 32 | 7 | 0.0% | 21.9% | 1 | completa | github_search_recent |
| iDesignStudioz/yellowkey-bitlocker | 33 | 7 | 0.0% | 21.2% | 1 | completa | github_search_recent |
| chatwoot/chatwoot | 230 | 7 | 0.0% | 3.0% | 1 | completa | github_trending |
| Open-Builders/pumpfun-bundler-pump.fun-bundler-solana-token-bundler-bot | 18 | 6 | 33.3% | 33.3% | 1 | completa | github_search_recent |
| taisly/agent | 23 | 6 | 17.4% | 26.1% | 2 | completa | github_search_recent |
| iptv-org/iptv | 193 | 6 | 0.0% | 3.1% | 1 | completa | github_trending |
| itsfatduck/optimizerDuck | 293 | 6 | 0.3% | 2.0% | 1 | completa | github_trending |
| Segnale | Peso | Misurazione |
|---|
| Età dell'account | 35% | < 2 giorni → 1.00 · < 7 giorni → 0.90 · < 30 giorni → 0.55 · < 90 giorni → 0.20 · più vecchio → 0.00 |
| Completezza del profilo | 30% | Punti per: nessuna biografia (+0.25), nessuna località (+0.15), nessuna azienda (+0.10), zero follower (+0.30), zero following (+0.10), nome utente con pattern da bot (+0.20) |
| Pattern dei repository | 25% | Zero repo → 0.90 · tutti i repo sono fork → 0.80 · >85% rapporto fork → 0.55 |
| Cronologia attività | 10% | Account vecchi >14 giorni con zero repo + zero grafo sociale → 0.80 (account fantasma). Solo zero repo → 0.60. Tutti fork + nessun grafo sociale → 0.50 |