162 lines
4.8 KiB
Python
162 lines
4.8 KiB
Python
"""ETERNAL Packagist harvest: search.json + p2 metadata dist.zip scan.
|
|
Lane: packagist. Polite sleep, 25 packages per cycle."""
|
|
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 sys
|
|
import time
|
|
import urllib.parse
|
|
import urllib.request
|
|
import zipfile
|
|
|
|
DB = os.environ.get("KF_DB") or os.path.join(KF_BASE, "data/keys.db")
|
|
LOG = os.path.join(KF_BASE, "packagist_eternal.log")
|
|
|
|
QUERIES = ["openai", "anthropic", "claude", "llm", "chatgpt", "gemini",
|
|
"mistral", "groq", "deepseek", "openrouter", "api",
|
|
"ai", "gpt", "sdk", "client", "secrets"]
|
|
|
|
SCAN_EXT = (".php", ".env", ".json", ".yml", ".yaml", ".md", ".xml",
|
|
".dist", ".txt", ".js", ".ts", ".py")
|
|
MAX_FILE = 2 * 1024 * 1024
|
|
UA = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
|
|
|
|
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")
|
|
|
|
seen = set()
|
|
|
|
|
|
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, 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",
|
|
"packagist", repo))
|
|
return 1
|
|
|
|
|
|
def fetch(url, binary=False):
|
|
req = urllib.request.Request(url, headers=UA)
|
|
with urllib.request.urlopen(req, timeout=60) as r:
|
|
blob = r.read()
|
|
return blob if binary else blob.decode("utf-8", "replace")
|
|
|
|
|
|
def scan_zip(blob, cur, pkg):
|
|
n = 0
|
|
try:
|
|
zf = zipfile.ZipFile(io.BytesIO(blob))
|
|
except Exception:
|
|
return 0
|
|
for info in zf.infolist():
|
|
name = info.filename.lower()
|
|
if info.file_size > MAX_FILE:
|
|
continue
|
|
if not (name.endswith(SCAN_EXT) or ".env" in name):
|
|
continue
|
|
try:
|
|
text = zf.read(info).decode("utf-8", "replace")
|
|
except Exception:
|
|
continue
|
|
for prov, rx in PATTERNS:
|
|
for m in rx.findall(text):
|
|
n += db_add(cur, m, prov, f"{pkg}:{info.filename}")
|
|
return n
|
|
|
|
|
|
def scan_package(pkg, cur):
|
|
try:
|
|
meta = json.loads(fetch(
|
|
"https://repo.packagist.org/p2/" + pkg + ".json"))
|
|
except Exception as e:
|
|
log(f" meta {pkg} err: {str(e)[:60]}")
|
|
return 0
|
|
versions = meta.get("packages", {}).get(pkg, [])
|
|
if not versions:
|
|
return 0
|
|
dist = versions[0].get("dist", {})
|
|
url = dist.get("url")
|
|
if not url or dist.get("type") != "zip":
|
|
return 0
|
|
try:
|
|
blob = fetch(url, binary=True)
|
|
except Exception as e:
|
|
log(f" zip {pkg} err: {str(e)[:60]}")
|
|
return 0
|
|
return scan_zip(blob, cur, pkg)
|
|
|
|
|
|
def cycle(db):
|
|
cur = db.cursor()
|
|
total = 0
|
|
for q in QUERIES:
|
|
n = 0
|
|
url = ("https://packagist.org/search.json?q=" +
|
|
urllib.parse.quote(q))
|
|
try:
|
|
data = json.loads(fetch(url))
|
|
except Exception as e:
|
|
log(f" search '{q}' err: {str(e)[:60]}")
|
|
continue
|
|
for p in data.get("results", [])[:25]:
|
|
n += scan_package(p.get("name", ""), cur)
|
|
db.commit()
|
|
time.sleep(1.0)
|
|
total += n
|
|
log(f" '{q}': +{n} new")
|
|
return total
|
|
|
|
|
|
def main():
|
|
log(f"=== packagist eternal START pid={__import__('os').getpid()} "
|
|
f"queries={len(QUERIES)}")
|
|
try:
|
|
con = sqlite3.connect(DB, timeout=120, check_same_thread=False, isolation_level=None)
|
|
cur = con.cursor()
|
|
cur.execute("SELECT val FROM keys WHERE found='packagist'")
|
|
for r in cur.fetchall():
|
|
seen.add(r[0])
|
|
con.close()
|
|
except Exception:
|
|
pass
|
|
db = sqlite3.connect(DB, timeout=120, check_same_thread=False, isolation_level=None)
|
|
while True:
|
|
try:
|
|
n = cycle(db)
|
|
log(f"CYCLE done +{n}")
|
|
except Exception as e:
|
|
log(f"cycle err: {repr(e)[:100]}")
|
|
time.sleep(30)
|
|
time.sleep(180)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
main()
|
|
except KeyboardInterrupt:
|
|
sys.exit(0)
|