autoreg-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
140
skills/account-validation/scripts/aiand_check_keys_curl.py
Normal file
140
skills/account-validation/scripts/aiand_check_keys_curl.py
Normal file
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
aiand_check_keys_curl.py
|
||||
Validate aiand/OpenAI-compatible API keys using system curl instead of Python urllib/httpx.
|
||||
|
||||
Why curl: aiand's Cloudflare layer may return Error 1010 (browser signature blocked)
|
||||
for Python HTTP clients even when the key is valid. curl can succeed where urllib fails.
|
||||
|
||||
Input formats supported:
|
||||
email|sk-...|org_id
|
||||
label,email,sk-...,org_id
|
||||
sk-...
|
||||
|
||||
Output: CSV report with status classification; raw keys are never printed.
|
||||
"""
|
||||
import argparse
|
||||
import csv
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
DEFAULT_BASE_URL = "https://api.aiand.com/v1"
|
||||
DEFAULT_MODEL = "qwen/qwen3.6-27b"
|
||||
|
||||
|
||||
def load_rows(path: Path):
|
||||
rows = []
|
||||
for line_no, raw in enumerate(path.read_text(encoding="utf-8", errors="ignore").splitlines(), 1):
|
||||
line = raw.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
sep = "|" if "|" in line else "," if "," in line else None
|
||||
parts = [p.strip() for p in (line.split(sep) if sep else [line])]
|
||||
email = next((p for p in parts if "@" in p), f"line_{line_no}")
|
||||
key = next((p for p in parts if p.startswith("sk-")), "")
|
||||
org_id = next((p for p in parts if "-" in p and len(p) >= 25 and not p.startswith("sk-") and "@" not in p), "")
|
||||
rows.append({"line": line_no, "email": email, "key": key, "org_id": org_id})
|
||||
return rows
|
||||
|
||||
|
||||
def curl_chat(base_url: str, key: str, org_id: str, model: str, timeout: int):
|
||||
payload = json.dumps({
|
||||
"model": model,
|
||||
"messages": [{"role": "user", "content": "ping"}],
|
||||
"max_tokens": 1,
|
||||
"temperature": 0,
|
||||
})
|
||||
cmd = [
|
||||
"curl", "-sS", "--max-time", str(timeout),
|
||||
"-w", "\nHTTP_STATUS:%{http_code}",
|
||||
base_url.rstrip("/") + "/chat/completions",
|
||||
"-H", "Content-Type: application/json",
|
||||
"-H", "Accept: application/json",
|
||||
"-H", f"Authorization: Bearer {key}",
|
||||
]
|
||||
if org_id:
|
||||
cmd += ["-H", f"X-Org-ID: {org_id}"]
|
||||
cmd += ["--data-binary", payload]
|
||||
try:
|
||||
r = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout + 10)
|
||||
combined = (r.stdout or "") + (("\nSTDERR:" + r.stderr) if r.stderr else "")
|
||||
except Exception as exc:
|
||||
return 0, repr(exc)
|
||||
marker = "\nHTTP_STATUS:"
|
||||
if marker not in combined:
|
||||
return 0, combined.strip()
|
||||
body, code = combined.rsplit(marker, 1)
|
||||
try:
|
||||
return int(code.strip().splitlines()[0]), body.strip()
|
||||
except Exception:
|
||||
return 0, body.strip()
|
||||
|
||||
|
||||
def classify(status: int, body: str):
|
||||
b = body.lower()
|
||||
if status == 200:
|
||||
return "ok"
|
||||
if "insufficient" in b and "credit" in b:
|
||||
return "valid_no_credits"
|
||||
if "error 1010" in b or ("browser" in b and "signature" in b):
|
||||
return "cloudflare_block"
|
||||
if status in (401, 403) or "no access token" in b or "unauthorized" in b or "invalid token" in b:
|
||||
return "auth_failed"
|
||||
if status == 429:
|
||||
return "rate_limited"
|
||||
if "model" in b and any(x in b for x in ("not found", "invalid", "not exist")):
|
||||
return "valid_model_problem"
|
||||
if status == 0:
|
||||
return "network_error"
|
||||
return "unknown_error"
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--keys", required=True, help="Path to key file")
|
||||
ap.add_argument("--out", default="aiand_key_report.csv", help="CSV report path")
|
||||
ap.add_argument("--base-url", default=DEFAULT_BASE_URL)
|
||||
ap.add_argument("--model", default=DEFAULT_MODEL)
|
||||
ap.add_argument("--timeout", type=int, default=60)
|
||||
args = ap.parse_args()
|
||||
|
||||
key_path = Path(args.keys).expanduser()
|
||||
if not key_path.exists():
|
||||
print(f"ERROR: not found: {key_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
report = []
|
||||
counts = {}
|
||||
rows = load_rows(key_path)
|
||||
for i, row in enumerate(rows, 1):
|
||||
key = row["key"]
|
||||
if not key or "..." in key or len(key) < 50:
|
||||
status, http_status, snippet = "local_truncated_key", "", "key missing/truncated locally; not tested"
|
||||
else:
|
||||
http_status, body = curl_chat(args.base_url, key, row["org_id"], args.model, args.timeout)
|
||||
status = classify(http_status, body)
|
||||
snippet = body.replace("\n", " ")[:300]
|
||||
counts[status] = counts.get(status, 0) + 1
|
||||
report.append({
|
||||
"email": row["email"], "line": row["line"], "key_len": len(key),
|
||||
"has_org_id": bool(row["org_id"]), "status": status,
|
||||
"http_status": http_status, "response_snippet": snippet,
|
||||
})
|
||||
print(f"[{i:03d}/{len(rows):03d}] {row['email']} len={len(key)} org={bool(row['org_id'])} status={status} http={http_status}")
|
||||
|
||||
out = Path(args.out)
|
||||
with out.open("w", encoding="utf-8", newline="") as f:
|
||||
writer = csv.DictWriter(f, fieldnames=["email", "line", "key_len", "has_org_id", "status", "http_status", "response_snippet"])
|
||||
writer.writeheader()
|
||||
writer.writerows(report)
|
||||
|
||||
print("\nSummary:")
|
||||
for key, value in sorted(counts.items()):
|
||||
print(f" {key}: {value}")
|
||||
print(f"\nReport written: {out.resolve()}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user