import sys sys.dont_write_bytecode = True import argparse import json import os import re from urllib.parse import urlparse import requests sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from keycheck_common import ( combined_provider_routing_hint, commit_status_transaction, default_input_file, default_proxy_file, ensure_output_files, iter_findings, keycheck_input_mode, load_checked_statuses, load_known_keys, load_proxies, mask_secret, provider_routing_database_failed, read_plain_keys, record_validation_result, recover_status_transaction, require_provider_authority, service_output_dir, should_skip_key, write_keycheck_event, ) from keycheckers.provider_resolution import ( AMBIGUOUS_GENERIC_SK_HINT, AMBIGUOUS_QWEN_DEEPSEEK_HINT, resolve_provider_key, ) SERVICE = "zai" DETECTOR = "ZaiGLM" OUTPUT_DIR = os.getenv("KEYCHECK_OUTPUT_DIR") or service_output_dir(SERVICE) INPUT_FILE = os.getenv("KEYCHECK_INPUT_FILE") or default_input_file() PROXY_FILE = os.getenv("KEYCHECK_PROXY_FILE") or default_proxy_file() CHECKED_FILE = os.path.join(OUTPUT_DIR, "zaiChecked.txt") RESULTS_FILE = os.path.join(OUTPUT_DIR, "zaiResults.jsonl") STATUS_FILES = { "VALID": os.path.join(OUTPUT_DIR, "zaiAlive.txt"), "NO_BALANCE": os.path.join(OUTPUT_DIR, "zaiNoBalance.txt"), "DEAD": os.path.join(OUTPUT_DIR, "zaiDead.txt"), "RESTRICTED": os.path.join(OUTPUT_DIR, "zaiRestricted.txt"), "LIMITED": os.path.join(OUTPUT_DIR, "zaiLimited.txt"), "NETWORK": os.path.join(OUTPUT_DIR, "zaiNetwork.txt"), "UNKNOWN": os.path.join(OUTPUT_DIR, "zaiUnknown.txt"), } DEFAULT_BASE_URLS = ( "https://api.z.ai/api/paas/v4", "https://open.bigmodel.cn/api/paas/v4", ) ZAI_KEY_REGEX = re.compile( r"(? 512: return "candidate exceeds the 512-byte key limit" if value.startswith(FOREIGN_KEY_PREFIXES): return "candidate has a foreign provider prefix" if not ZAI_KEY_REGEX.fullmatch(value): return "candidate does not match a bounded ZAI key format" return "" def finding_detector_names(finding): if not isinstance(finding, dict): return set() extra = finding.get("ExtraData") if isinstance(finding.get("ExtraData"), dict) else {} return { name for name in ( str(finding.get("DetectorName") or finding.get("DetectorType") or "").strip().lower(), str(extra.get("name") or "").strip().lower(), ) if name } def finding_provider_routing_hint(finding, key=""): if not isinstance(finding, dict): return "zai" if ZAI_DOTTED_KEY_REGEX.fullmatch(str(key or "")) else "" context = finding.get("ScannerContext") if isinstance(finding.get("ScannerContext"), dict) else {} persisted = str(context.get("provider_hint") or "").strip().lower() if persisted: return persisted if "zaiglm" in finding_detector_names(finding) or ZAI_DOTTED_KEY_REGEX.fullmatch(str(key or "")): return "zai" text = "\n".join(str(context.get(name) or "") for name in ("nearby", "file")) return "zai" if ZAI_CONTEXT_REGEX.search(text) else "" def iter_candidate_decisions(input_file, plain_files, trusted_retry_files=None): detectors = [ "ZaiGLM", "zaiglm", "CustomRegex", "QwenDashScope", "Qwen_DashScope", "Qwen", "DashScope", "DeepSeek", "DeepSeekApiKey", "DeepSeek_API_Key", "KimiMoonshot", "MoonshotAI", "Moonshot", "Kimi", ] seen = set() for item in iter_findings(input_file, detectors): finding = item.get("finding") or {} key = item.get("credential_secret_text") or key_from_text( item.get("raw"), item.get("raw_v2"), finding.get("Raw"), finding.get("RawV2"), ) if not key or key_rejection_reason(key): continue local_hint = finding_provider_routing_hint(finding, key) hint = combined_provider_routing_hint(key, local_hint) if provider_routing_database_failed(): raise RuntimeError("provider routing evidence lookup failed closed") seen.add(key) yield key, item.get("source") or input_file, finding, hint owned_retry_paths = { os.path.normcase(os.path.abspath(path)) for path in STATUS_FILES.values() } retry_paths = [ path for path in trusted_retry_files or () if os.path.normcase(os.path.abspath(path)) in owned_retry_paths ] for item in read_plain_keys([*plain_files, *retry_paths], ZAI_KEY_REGEX): key = item["key"] if key in seen or key_rejection_reason(key): continue yield key, item["source"], {}, "zai" def redact_text(value, key): text = str(value or "")[:1000] if key: text = text.replace(key, "***REDACTED***") return ZAI_KEY_REGEX.sub("***REDACTED***", text) def parse_error(response, key): try: payload = response.json() except ValueError: payload = {} error = payload.get("error") if isinstance(payload, dict) else {} if not isinstance(error, dict): error = {} return { "http_status": int(response.status_code), "code": str(error.get("code") or (payload.get("code") if isinstance(payload, dict) else "") or ""), "message": redact_text( error.get("message") or error.get("msg") or ( payload.get("message") or payload.get("msg") if isinstance(payload, dict) else "" ) or response.text[:500], key, ), } def classify_error(error): http_status = int(error.get("http_status") or 0) code = str(error.get("code") or "") message = str(error.get("message") or "").lower() if http_status == 402 or any(marker in message for marker in ( "insufficient balance", "balance is insufficient", "no balance", "account balance", "recharge", "payment required", "billing arrears", "credit balance", )): return "NO_BALANCE", True if code in AUTHENTICATED_NO_BALANCE_CODES: return "NO_BALANCE", True if any(marker in message for marker in ( "quota", "rate limit", "rate-limit", "too many requests", "resource exhausted", "concurrency limit", "usage limit", )): return "LIMITED", True if code in AUTHENTICATED_LIMIT_CODES: return "LIMITED", True if any(marker in message for marker in ( "permission denied", "access denied", "not authorized for", "model access", "forbidden", )): return "RESTRICTED", True if code in AUTHENTICATED_RESTRICTED_CODES: return "RESTRICTED", True if http_status == 401 or code in AUTH_FAILURE_CODES: return "DEAD", False if 500 <= http_status <= 599 or code in {"1200", "1230", "1234", "1305"}: return "NETWORK", False if http_status == 429: return "LIMITED", False if http_status == 403: return "RESTRICTED", False return "UNKNOWN", False def probe_chat_completion(key, base_url, model, proxy, timeout, debug=False): if not model: return { "status": "UNKNOWN", "model": "", "message": "no chat-capable model returned by /models", } url = f"{normalize_base_url(base_url)}/chat/completions" headers = {"Authorization": f"Bearer {key}", "Content-Type": "application/json"} payload = { "model": model, "messages": [{"role": "user", "content": "ping"}], "max_tokens": 1, "stream": False, } try: response = requests.post(url, headers=headers, json=payload, proxies=proxy, timeout=timeout) except requests.RequestException as exc: return { "status": "NETWORK", "model": model, "message": redact_text(exc, key), } if debug: print( f" DEBUG {endpoint_label(base_url)} chat probe {model}: HTTP {response.status_code}: " f"{redact_text(response.text[:500], key)}" ) if response.status_code == 200: try: response_payload = response.json() except ValueError as exc: return { "status": "UNKNOWN", "model": model, "http_status": response.status_code, "message": f"invalid chat completion response: {exc}", } if isinstance(response_payload, dict) and response_payload.get("choices"): return { "status": "GENERATION_OK", "model": model, "http_status": response.status_code, "message": "chat completion accepted", } error = parse_error(response, key) status, authenticated = classify_error(error) return { "status": status, "authenticated": authenticated, "model": model, "http_status": response.status_code, "business_code": error.get("code") or "", "error": error, "message": error.get("message") or "", } def check_base_url(key, base_url, proxy, timeout, debug=False): url = f"{normalize_base_url(base_url)}/models" headers = {"Authorization": f"Bearer {key}", "Accept": "application/json"} try: response = requests.get(url, headers=headers, proxies=proxy, timeout=timeout) except requests.RequestException as exc: return { "status": "NETWORK", "base_url": base_url, "region": endpoint_label(base_url), "message": redact_text(exc, key), } if debug: print( f" DEBUG {endpoint_label(base_url)} /models: HTTP {response.status_code}: " f"{redact_text(response.text[:500], key)}" ) if response.status_code == 200: try: payload = response.json() data = payload.get("data") if isinstance(payload, dict) else None if not isinstance(data, list): raise ValueError("missing data model list") models = sorted({ str(item.get("id") or item.get("name") or "") for item in data if isinstance(item, dict) and (item.get("id") or item.get("name")) }) except (TypeError, ValueError, json.JSONDecodeError) as exc: return { "status": "UNKNOWN", "base_url": base_url, "region": endpoint_label(base_url), "message": f"invalid models response: {exc}", } probe_model = PROBE_MODEL probe = probe_chat_completion(key, base_url, probe_model, proxy, timeout, debug) probe_status = probe.get("status") or "UNKNOWN" if probe_status == "GENERATION_OK": status = "VALID" elif probe_status in {"NO_BALANCE", "LIMITED", "RESTRICTED", "NETWORK"}: status = probe_status elif probe_status == "DEAD": status = "RESTRICTED" else: status = "UNKNOWN" probe_message = probe.get("message") or json.dumps(probe.get("error") or {}, ensure_ascii=False) message = ( f"chat probe ok; model={probe_model}; models={len(data)}" if status == "VALID" else f"models authenticated; generation_probe={probe_status}; model={probe_model}; " f"models={len(data)}; {probe_message}" ) return { "status": status, "authenticated": True, "base_url": base_url, "region": endpoint_label(base_url), "model_count": len(data), "models": models[:30], "llm_probe_status": probe_status, "llm_probe_model": probe.get("model") or probe_model, "llm_probe_http_status": probe.get("http_status"), "business_code": probe.get("business_code") or "", "probe": probe, "error": probe.get("error") or {}, "message": message[:1000], } error = parse_error(response, key) status, authenticated = classify_error(error) return { "status": status, "authenticated": authenticated, "base_url": base_url, "region": endpoint_label(base_url), "http_status": response.status_code, "business_code": error.get("code") or "", "error": error, "message": error.get("message") or "", } def check_key(key, base_urls, proxy, timeout, debug=False): rejection = key_rejection_reason(key) if rejection: return {"status": "UNKNOWN", "message": rejection, "candidate_rejected": True} attempts = [] for base_url in base_urls: result = check_base_url(key, base_url, proxy, timeout, debug) attempts.append(result) if result.get("authenticated"): return {**result, "attempts": attempts} statuses = [attempt.get("status") for attempt in attempts] status = next( (candidate for candidate in ("NETWORK", "LIMITED", "RESTRICTED", "UNKNOWN", "DEAD") if candidate in statuses), "UNKNOWN", ) selected = next((attempt for attempt in attempts if attempt.get("status") == status), {}) return {**selected, "status": status, "attempts": attempts} def ensure_files(): ensure_output_files([CHECKED_FILE, RESULTS_FILE, *STATUS_FILES.values()]) recover_status_transaction(CHECKED_FILE, STATUS_FILES) def write_result(key, result, source, finding): status = result.get("status") or "UNKNOWN" write_keycheck_event(SERVICE, RESULTS_FILE, key, result, source, finding, DETECTOR) commit_status_transaction( CHECKED_FILE, STATUS_FILES, key, status, result.get("message", ""), result.get("region") or source, ) record_validation_result(SERVICE, key, result, source, finding, DETECTOR) def retry_statuses_from_args(args): statuses = set() for enabled, status in ( (args.retry_network, "NETWORK"), (args.retry_limited, "LIMITED"), (args.retry_unknown, "UNKNOWN"), (args.retry_restricted, "RESTRICTED"), (args.retry_no_balance, "NO_BALANCE"), (args.retry_valid, "VALID"), ): if enabled: statuses.add(status) return statuses def retry_input_files_from_args(args): if args.recheck_all: statuses = list(STATUS_FILES) else: statuses = list(retry_statuses_from_args(args)) return [STATUS_FILES[status] for status in statuses] def parse_args(): parser = argparse.ArgumentParser(description="ZAI / Zhipu GLM key checker") parser.add_argument("--input", default=INPUT_FILE) parser.add_argument("--plain", action="append", default=[]) parser.add_argument("--proxy-file", default=PROXY_FILE) parser.add_argument("--timeout", type=int, default=15) parser.add_argument("--max-keys", type=int, default=0) parser.add_argument("--base-url", action="append", default=[]) parser.add_argument("--no-default-base-urls", action="store_true") parser.add_argument("--retry-network", action="store_true") parser.add_argument("--retry-limited", action="store_true") parser.add_argument("--retry-unknown", action="store_true") parser.add_argument("--retry-restricted", action="store_true") parser.add_argument("--retry-no-balance", action="store_true") parser.add_argument("--retry-valid", action="store_true") parser.add_argument("--recheck-all", action="store_true") parser.add_argument("--debug", action="store_true") return parser.parse_args() def main(): require_provider_authority(SERVICE) args = parse_args() ensure_files() proxy_cycler = load_proxies(args.proxy_file) checked = load_checked_statuses(CHECKED_FILE) known = load_known_keys(CHECKED_FILE, STATUS_FILES) retry_statuses = retry_statuses_from_args(args) retry_files = retry_input_files_from_args(args) base_urls = base_urls_from_environment(args.base_url, not args.no_default_base_urls) if not base_urls: raise SystemExit("No ZAI base URLs configured") processed = 0 skipped = 0 postgres_mode = keycheck_input_mode() == "postgres" for key, source, finding, routing_hint in iter_candidate_decisions(args.input, args.plain, retry_files): is_ambiguous = routing_hint in AMBIGUOUS_HINTS if routing_hint != "zai" and not (postgres_mode and is_ambiguous): skipped += 1 continue if should_skip_key( key, checked, known, args, retry_statuses, service=SERVICE, source=source, finding=finding, detector=DETECTOR, ): skipped += 1 continue if args.max_keys and processed >= args.max_keys: break processed += 1 print(f"\n[{processed}] ZAI candidate {mask_secret(key)} from {source}") proxy = next(proxy_cycler) if proxy_cycler else None if is_ambiguous: result = resolve_provider_key( key, finding, proxy, args.timeout, args.debug, hint=routing_hint, origin_service=SERVICE, ) else: result = check_key(key, base_urls, proxy, args.timeout, args.debug) print(f" STATUS: {result['status']} | {result.get('message', '')[:200]}") write_result(key, result, source, finding) known.add(key) checked[key] = result["status"] print(f"\nDone. Processed={processed}, skipped={skipped}, results={RESULTS_FILE}") if __name__ == "__main__": main()