
GitHub 上のフェイクエンゲージメントを自動検出・追跡 — 日次 CI、インフラ不要
GitHub 上での偽エンゲージメントの自動検出と追跡
JS Labs プロジェクト —
AI Slop Intelligence イニシアチブの一部。
毎日実行。不審なアカウントをすべてスコアリング。協調的なボットキャンペーンを検出。
侵害されたリポジトリに直接 issue を登録し、メンテナーが対処できるようにします。
このプロジェクトを支援する
BTC 3QjWqhQbHdHgWeYHTpmorP8Pe1wgDjJy54
ETH 0x5851e6145F4773d1585b8686095FB16E368a4dA1
ZEC t1KSR5YkNPbjqRSCoLKo5AddFWdm9Kzxh1B
GitHub のスターは信頼のシグナルです。開発者はそれを頼りに、何を評価し、何に依存し、何を推奨するかを決めます。そのシグナルは、組織的に改ざんされています。
2024年から2026年にかけてのAIブームの最中に、低品質でしばしば悪意のあるリポジトリのために信頼性を製造するボットファーム産業が出現しました。48時間で800スターを獲得したプロジェクトは、検索結果をスキャンする開発者には正当なものに見えます。そこがポイントです。偽エンゲージメントの目的はスターそのものではなく、そのスターが生み出す社会的証明(ソーシャルプルーフ)と、その社会的証明が影響を与える下流の意思決定です。
そのパターンは識別可能です。同じ週に作成され、自己紹介(bio)がなく、フォロワーもおらず、オリジナルのリポジトリもないアカウント群が、2時間の間に同じ15のリポジトリにスターを付けています。1つのキャンペーンではなく、毎日、何千ものアカウントにわたって、数十のキャンペーンが同時に実行されています。データは、185人中185人のエンゲージャーがボットであるリポジトリを示しています。100%の偽装率です。トレンド入りした全体が、何の根拠もなく構築されています。
phantomstars は、この問題が解決可能(tractable)だからこそ構築されました。GitHub の公開 API の信号対雑音比は、今のところ、協調キャンペーンが明確な指紋を残すほど十分に高いままです。このプロジェクトはその指紋を読み取り、生データを公開し、影響を受けたリポジトリのメンテナーに直接通知します。
これは、JS Labs におけるより広範な AI Slop Intelligence の取り組みの一部であり、低品質のAI生成コンテンツが開発者エコシステムに氾濫することの仕組みと測定可能な影響に関する継続的な研究です。偽エンゲージメントは周辺的な問題ではありません。それは、スロップ(低品質AI生成コンテンツ)を実際のユーザーの目に届ける流通メカニズムなのです。
phantomstars は、毎日の GitHub Actions ジョブを実行します。そのジョブは以下を行います:
r/osinttools と r/coolgithubprojects の最近の Reddit 投稿から、直近 2日間 の GitHub リポジトリリンクを抽出して、追加の候補リポジトリをシードしますサーバー不要。データベース不要。インフラ費用なし。
はい。
リポジトリの偽装率が40%を超えるか、協調キャンペーンが検出された場合、phantomstars はそのリポジトリに直接 issue を開きます。issue には、完全な被疑者テーブル、キャンペーンへの参加状況、複合スコア、アカウント作成日が含まれます。メンテナーが調査して GitHub に報告するために必要なすべてが揃っています。
標的のリポジトリで issue が無効になっている場合、通知は静かにスキップされ、スキャンログに記録されます。
はい。
owner/repo 形式でリポジトリを提出し、対象を絞ったスキャンを実行します。なぜ分けているのか:
はい。自分のアカウントが data/suspects.jsonl に表示され、分類が正しくないと思われる場合は、提供されているテンプレートを使用して 誤検知の issue を開いて ください。報告は、許可リストへの追加前に手動でレビューされます。許可リストは data/allowlist.txt に保存されており、そこにリストされたアカウントは、将来のすべてのスキャンと被疑者台帳から除外されます。
キャンペーンID(例:c-a3f9b2e1)は、そのキャンペーンのメンバーログインのソート済みセットの SHA-256 ハッシュから導出される決定論的な8文字の16進数フィンガープリントです。同じアカウントグループは、独立したスキャン実行間で同じキャンペーンIDを生成するため、長期的な追跡が可能です。これはリポジトリ名でも、ユーザー名でも、外部識別子でもありません。
安定性: ID は、キャンペーンのメンバーセットが変わらない限り安定しています。スキャンの合間にボットが追加されたり停止されたりすると、メンバーシップが変わったため ID も変わります。これは想定どおりであり、ボットファーム構成の現実世界での変化を反映しています。
はい。すべてのアカウントの作成日は GitHub GraphQL API(createdAt フィールド)から取得され、各被疑者レコードに account_created_at として保存されます。また、これはアカウント年齢スコアへの主要な入力であり、偽アカウントにとって最も強力な単一シグナルです。エンゲージメントから2日以内に作成されたアカウントは、年齢だけでスコア1.0になります。
個々のスコアには、意味のある誤検知率が伴います。プロフィールが乏しい新しい開発者は、正当に0.75以上になることがあります。このツールは、issue を登録する前にキャンペーンレベルの証拠を要求することでこれを考慮しています。単一の不審なアカウントでは不十分です。同じ週に作成され、すべて0.75以上でスコアリングされ、90分以内にすべてエンゲージした40以上のアカウントの協調クラスタは、別物です。そこで確信が実行可能になります。
データは常に確率的です。issue の本文にもその旨が明示されています。目標は、メンテナーにシグナルと生の証拠を提供し、彼ら自身が判断できるようにすることです。
各アカウントは、4つのシグナルから複合的な不審度スコア(0.0 = クリーン、1.0 = 偽装の可能性が高い)を受け取ります:
分類のしきい値:
| スコア | 分類 |
|---|---|
| ≥ 0.75 | likely_fake |
| ≥ 0.45 | suspicious |
| < 0.45 | clean (保存されない) |
キャンペーンとは、3時間のウィンドウ内で同じリポジトリにすべてエンゲージした ≥ 4 個の不審なアカウントのグループです。アルゴリズムは union-find を使用して連結成分を構築します。ウィンドウ内で共にエンゲージしたアカウントは統合され、最小サイズを超える成分は協調キャンペーンとしてフラグ付けされます。
キャンペーンIDは、ソート済みメンバーセットの安定した SHA-256 フィンガープリントです。連続した日に検出された同じキャンペーンは、メンバーシップが変わらない限り同じ ID になります。
キャンペーンが本当のシグナルである理由: 個々のスコアには意味のある誤検知率があります。プロフィールが乏しい新しい開発者は、単独で0.80を獲得することがあります。同じ週に作成され、90分以内に同じリポジトリにスターを付け、すべて0.75以上でスコアリングされた40のアカウントは、偶然ではありません。キャンペーンシグナルこそが、データが実行可能になる場所、つまり不審なデータポイントと協調的な運用の証拠との違いです。
すべての調査結果は data/suspects.jsonl と data/repos.jsonl にコミットされます。各行に1つの JSON レコードがあり、追記専用です。GitHub Actions のジョブサマリー(各実行後に Actions UI で表示可能)は、スキャンごとの整形されたレポートを提供します。
suspects.jsonl — スキャンごとにフラグ付けされたアカウントごとに1レコード:```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** — スキャンごとに、対象リポジトリごとに1レコード:```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"
}
クエリ例:```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
---
## セットアップ
### 1. このリポジトリをフォークする
フォークがデータを所有します。結果は毎日の実行後に、フォーク上の `data/suspects.jsonl` と `data/repos.jsonl` にコミットされます。
### 2. GitHub PAT シークレットを追加する
以下のスコープを持つ **クラシック** Personal Access Token を作成します:
- `public_repo`: 公開リポジトリのイベントとスターを読み取り、公開リポジトリに issue を作成する
- `read:user`: GraphQL 経由でユーザープロファイルを取得する
**Settings → Secrets and variables → Actions → New repository secret** から、`GH_TOKEN` という名前で作成します。
> デフォルトの `GITHUB_TOKEN` はレート制限が制約されており、ユーザー GraphQL エンドポイントを最大容量で呼び出すことができません。PAT が必要です。
### 3. Actions を有効にする
フォーク上で **Actions → Enable GitHub Actions** を有効にします。ワークフローは `Europe/London` クロックを使用して、**毎日英国時間 07:00** に実行されます:
- **06:00 UTC**(英国夏時間中)
- **07:00 UTC**(グリニッジ標準時中)
追加のスケジュール環境変数は必要ありません。GitHub Actions の cron は UTC のみのため、ワークフローは両方の UTC 時刻にトリガーされ、ロンドンの現地時刻が 07:00 の場合にのみ実行されます。手動トリガーは **Actions → Daily Phantom Stars Scan → Run workflow** から利用できます。
各実行後、フォーマットされたスキャンレポートは **Actions → [run] → Summary** で確認できます。
### 4. ローカルで実行する
---```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
セットアップ後のアドホックなローカル実行の場合:```bash GH_TOKEN=ghp_your_token python -m phantomstars.main
通常のディスカバリセットの代わりに、1つのリポジトリをスキャンするには:```bash
PHANTOMSTARS_TARGET_REPO=owner/repo GH_TOKEN=ghp_your_token python -m phantomstars.main
ユーザーは以下の2つの方法で単発のリポジトリチェックをリクエストできます。
Repo Check Request issue テンプレートを開き、対象リポジトリと要求する深度を指定します。target_repo: owner/reporequest_depth: recent または lifetime-request現在の動作:
recent: 対象となる最近のエンゲージメントスキャンを直ちに実行します。lifetime-request: そのリポジトリのみの過去のスターとフォーク全体に対して、対象を絞った全期間スキャンを実行します。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
---
## 制限事項と既知の障害モード
- **Events API の上限:** リポジトリごとに最大300件の直近イベント。1日に数千スターを獲得するリポジトリではカバレッジが部分的になります。
- **カバレッジフラグ:** 300イベントの上限に達したリポジトリは、レポートとダッシュボードで `capped` とマークされます。これらのリポジトリの比率は控えめなサンプルであり、終日のカウントではありません。
- **検索インデックスの遅延:** GitHubの検索インデックスは結果整合性です。スキャン境界の数秒前に作成されたリポジトリは見逃される可能性があります。
- **ヒューリスティックのドリフト:** ボット運営者は適応してきます。スコアの重みは定期的な調整が必要になる場合があります。`config.py` の定数を調整してください。
- **個別の誤検知:** プロフィールが疎らな新規開発者は単独で0.75以上のスコアになります。キャンペーンへの参加が高信頼性のシグナルです。
- **キャンペーンIDのドリフト:** スキャン間でボットファームのメンバーシップが変更された場合(ボットの停止、新しいボットの追加)、キャンペーンIDが変更されます。これはバグではなく、実際のキャンペーンの進化を反映しています。
- **レート制限:** 認証済みPATで1時間あたり5,000件のAPIリクエスト。標準的なトレンドページサイズでは制限内に十分収まります。
- **Issues 無効:** 一部の対象リポジトリはIssuesを無効にしています。これらのリポジトリへの通知は静かにスキップされます。
---
## 誤検知のプロセス
あなたのアカウントが `data/suspects.jsonl` に表示され、誤って分類されていると思われる場合:
1. エントリを検索: `jq 'select(.login == "YOUR_LOGIN")' data/suspects.jsonl`
2. ログイン、分類、スキャン日、説明を添えて[誤検知のIssueを開く](https://raw.githubusercontent.com/tg12/issues/new?template=false_positive.yml)
3. レポートは手動でレビューされます。確認された誤検知は `data/allowlist.txt` に追加され、今後のすべてのスキャン、リポジトリ比率、およびIssue通知から除外されます。
注: Issueを開いても既存のデータは変更または削除されません。容疑者台帳は追記専用です。許可リストは将来のスキャンにのみ影響します。
---
## コントリビューション```bash
pip install -e ".[dev]"
python -m black .
python -m ruff check .
python -m mypy src
python -m pytest
All four must pass before a PR.
このツールは、公式 GitHub API を使用して公開 GitHub データの読み取り専用分析を実行します。対象リポジトリに issue が提出される場合、それらには確率的な調査結果が含まれ、自動生成であることが明確にラベル付けされます。調査結果は指標であり、非難ではありません。誤検知は存在し、想定されています。
このツールは、AI が一因となって生じたエコシステムの問題に対応するため、コーディングパートナーとしての AI とともに構築されました。
Apache 2.0。LICENSE を参照してください。
tg12 によって構築 · GitHub
JS Labs プロジェクト · AI Slop Intelligence Dashboards
| 日付 | スキャン数 | 偽装の可能性 | 不審 | キャンペーン | 新規偽装 (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 |
| リポジトリ | エンゲージャー | 偽装の可能性 | 既知の偽装率 | 偽装率 | キャンペーン | カバレッジ | ソース |
|---|
| 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 |
| シグナル | 重み | 測定基準 |
|---|
| アカウントの年齢 | 35% | < 2 days → 1.00 · < 7 days → 0.90 · < 30 days → 0.55 · < 90 days → 0.20 · それより古い → 0.00 |
| プロフィールの充実度 | 30% | ポイント付与:自己紹介なし (+0.25)、場所なし (+0.15)、会社なし (+0.10)、フォロワーゼロ (+0.30)、フォローゼロ (+0.10)、ボットパターンのユーザー名 (+0.20) |
| リポジトリのパターン | 25% | リポジトリゼロ → 0.90 · すべてのリポジトリがフォーク → 0.80 · フォーク比率 >85% → 0.55 |
| 活動履歴 | 10% | 作成から14日以上経過し、リポジトリゼロ + ソーシャルグラフゼロ → 0.80(ゴーストアカウント)。リポジトリゼロのみ → 0.60。すべてフォーク + ソーシャルグラフなし → 0.50 |