KeyHunter parser v10 + 18 scanners + tests (synthetic fixtures only, no keys/db/tokens)
This commit is contained in:
171
pypi_eternal.py
Normal file
171
pypi_eternal.py
Normal file
@@ -0,0 +1,171 @@
|
||||
"""ETERNAL PyPI hunt: RSS updates.xml (fresh 100 uploads every cycle) +
|
||||
filtered package download (sdist/wheel) + grep. Logs pypi_eternal.log."""
|
||||
import os
|
||||
KF_BASE = os.path.dirname(os.path.abspath(__file__))
|
||||
os.makedirs(os.path.join(KF_BASE, "data"), exist_ok=True)
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import re
|
||||
import sqlite3
|
||||
import time
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import xml.etree.ElementTree as ET
|
||||
import zipfile
|
||||
|
||||
DB = os.environ.get("KF_DB") or os.path.join(KF_BASE, "data/keys.db")
|
||||
LOG = os.path.join(KF_BASE, "pypi_eternal.log")
|
||||
UA = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"}
|
||||
|
||||
# name filter: AI-ish packages likely to embed keys
|
||||
NAME_HINTS = ("openai", "anthropic", "claude", "gpt", "llm", "ai", "bot",
|
||||
"agent", "chat", "dashscope", "zhipu", "glm", "kimi", "moonshot",
|
||||
"deepseek", "groq", "openrouter", "together", "fireworks",
|
||||
"replicate", "nvidia", "silicon", "eleven", "voice", "tts",
|
||||
"telegram", "discord", "slack", "mcp", "rag", "embed", "vector",
|
||||
"langchain", "llama", "diffus", "stable")
|
||||
|
||||
SCAN_EXT = (".py", ".json", ".md", ".txt", ".env", ".yml", ".yaml", ".cfg",
|
||||
".toml", ".ini", ".js", ".ts")
|
||||
|
||||
from key_patterns import PATTERNS
|
||||
|
||||
GARBAGE = ("xxxx", "your", "EXAMPLE", "example", "n0tr3al", "INSERT", "FAKE",
|
||||
"fake", "Fake", "test", "Test", "TEST", "demo", "Demo", "aaaa",
|
||||
"aBcDeF", "abcdef", "1234567890", "abcdefghij", "placehol",
|
||||
"changeme", "deadbeef", "fffffff", "0000000")
|
||||
|
||||
|
||||
def log(msg):
|
||||
line = f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {msg}\n"
|
||||
print(line, flush=True)
|
||||
with open(LOG, "a", encoding="utf-8", errors="replace") as f:
|
||||
f.write(line)
|
||||
|
||||
|
||||
def db_add(cur, seen, key, prov, repo):
|
||||
if isinstance(key, tuple):
|
||||
key = key[0]
|
||||
if key in seen or any(g in key for g in GARBAGE) or len(key) < 28:
|
||||
return 0
|
||||
seen.add(key)
|
||||
cur.execute(
|
||||
"INSERT INTO keys (hash, val, prov, status, found, repo) "
|
||||
"VALUES (?,?,?,?,?,?) "
|
||||
"ON CONFLICT(hash) DO UPDATE SET status='NEW', found=excluded.found, repo=excluded.repo "
|
||||
"WHERE keys.status IN ('DEAD','ERR')",
|
||||
(hashlib.sha1(key.encode()).hexdigest()[:16], key, prov, "NEW",
|
||||
"pypi", repo))
|
||||
return 1
|
||||
|
||||
|
||||
def grep_blob(blob, cur, seen, pkg, fname):
|
||||
n = 0
|
||||
try:
|
||||
data = blob.decode(errors="replace")
|
||||
except Exception:
|
||||
return 0
|
||||
if len(data) > 400_000:
|
||||
return 0
|
||||
for prov, rx in PATTERNS:
|
||||
for key in rx.findall(data):
|
||||
hit = db_add(cur, seen, key, prov, f"pypi:{pkg}")
|
||||
if hit:
|
||||
n += 1
|
||||
log(f" + {prov} {key[:24]}... pypi:{pkg}/{fname}")
|
||||
return n
|
||||
|
||||
|
||||
def scan_pkg(name, cur, seen):
|
||||
n = 0
|
||||
try:
|
||||
req = urllib.request.Request(f"https://pypi.org/pypi/{name}/json", headers=UA)
|
||||
with urllib.request.urlopen(req, timeout=15) as r:
|
||||
meta = json.loads(r.read())
|
||||
except Exception as e:
|
||||
log(f" meta {name} err: {str(e)[:50]}")
|
||||
return 0
|
||||
urls = (meta.get("urls") or [])[:3]
|
||||
for u in urls:
|
||||
url = u.get("url", "")
|
||||
kind = u.get("packagetype", "")
|
||||
if not url:
|
||||
continue
|
||||
try:
|
||||
req = urllib.request.Request(url, headers=UA)
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
blob = r.read()
|
||||
except Exception:
|
||||
continue
|
||||
if len(blob) > 3_000_000:
|
||||
continue
|
||||
if kind == "bdist_wheel" and url.endswith(".whl"):
|
||||
try:
|
||||
zf = zipfile.ZipFile(io.BytesIO(blob))
|
||||
for m in zf.namelist():
|
||||
if not m.lower().endswith(SCAN_EXT):
|
||||
continue
|
||||
n += grep_blob(zf.read(m), cur, seen, name, m)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
import tarfile
|
||||
try:
|
||||
tf = tarfile.open(fileobj=io.BytesIO(blob))
|
||||
for m in tf.getmembers():
|
||||
if m.size > 400_000 or not m.isfile():
|
||||
continue
|
||||
if not m.name.lower().endswith(SCAN_EXT):
|
||||
continue
|
||||
fdata = tf.extractfile(m)
|
||||
if fdata is None:
|
||||
continue
|
||||
n += grep_blob(fdata.read(), cur, seen, name, m.name)
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(0.5)
|
||||
return n
|
||||
|
||||
|
||||
def cycle(db):
|
||||
cur = db.cursor()
|
||||
cur.execute("SELECT val FROM keys")
|
||||
seen = {r[0] for r in cur.fetchall()}
|
||||
total = 0
|
||||
try:
|
||||
req = urllib.request.Request("https://pypi.org/rss/updates.xml", headers=UA)
|
||||
with urllib.request.urlopen(req, timeout=20) as r:
|
||||
tree = ET.fromstring(r.read())
|
||||
titles = [i.findtext("title", "") for i in tree.findall(".//item")]
|
||||
cand = []
|
||||
for t in titles:
|
||||
name = t.split()[0] if t else ""
|
||||
name = name.rsplit(" ", 1)[0].strip()
|
||||
if name and any(h in name.lower() for h in NAME_HINTS) and len(name) < 60:
|
||||
cand.append(name)
|
||||
cand = list(dict.fromkeys(cand))[:25]
|
||||
log(f"rss: {len(titles)} updates, {len(cand)} candidates")
|
||||
for name in cand:
|
||||
total += scan_pkg(name, cur, seen)
|
||||
time.sleep(1)
|
||||
except Exception as e:
|
||||
log(f"cycle err: {e}")
|
||||
db.commit()
|
||||
return total
|
||||
|
||||
|
||||
def main():
|
||||
log(f"=== pypi eternal START pid={__import__('os').getpid()} ===")
|
||||
db = sqlite3.connect(DB, timeout=120, check_same_thread=False, isolation_level=None)
|
||||
while True:
|
||||
try:
|
||||
n = cycle(db)
|
||||
log(f"CYCLE done: +{n}; resting 10min")
|
||||
except Exception as e:
|
||||
log(f"cycle error (continuing): {e}")
|
||||
time.sleep(600)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user