reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env python3
|
||||
"""评估用户手动成功验证码样本是否足够形成成功基线。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
def configure_utf8_stdio() -> None:
|
||||
for stream in (sys.stdout, sys.stderr):
|
||||
if hasattr(stream, "reconfigure"):
|
||||
stream.reconfigure(encoding="utf-8")
|
||||
|
||||
|
||||
configure_utf8_stdio()
|
||||
|
||||
|
||||
MIN_TOTAL_SUCCESS_SAMPLES = 5
|
||||
MIN_SUCCESS_SAMPLES_PER_TYPE = 2
|
||||
|
||||
|
||||
def load_json_source(value: str) -> Any:
|
||||
stripped = value.lstrip()
|
||||
if stripped.startswith("{") or stripped.startswith("["):
|
||||
return json.loads(value)
|
||||
path = Path(value)
|
||||
try:
|
||||
if path.exists() and path.is_file():
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
except OSError:
|
||||
pass
|
||||
return json.loads(value)
|
||||
|
||||
|
||||
def get_first(mapping: dict[str, Any], names: list[str], default: Any = None) -> Any:
|
||||
for name in names:
|
||||
if name in mapping and mapping[name] not in (None, ""):
|
||||
return mapping[name]
|
||||
return default
|
||||
|
||||
|
||||
def normalize_samples(data: Any) -> tuple[list[dict[str, Any]], dict[str, Any]]:
|
||||
if isinstance(data, list):
|
||||
return [item for item in data if isinstance(item, dict)], {}
|
||||
if isinstance(data, dict):
|
||||
for key in ("success_samples", "samples", "manual_success_samples"):
|
||||
value = data.get(key)
|
||||
if isinstance(value, list):
|
||||
defaults = {name: item for name, item in data.items() if name != key}
|
||||
return [item for item in value if isinstance(item, dict)], defaults
|
||||
return [data], {}
|
||||
raise ValueError("success baseline JSON 必须是数组,或包含 success_samples/samples 数组的对象")
|
||||
|
||||
|
||||
def is_success(sample: dict[str, Any]) -> bool:
|
||||
if isinstance(sample.get("success"), bool):
|
||||
return bool(sample["success"])
|
||||
status = str(get_first(sample, ["status", "result", "outcome"], "")).strip().lower()
|
||||
return status in {"success", "succeeded", "passed", "pass", "ok", "成功", "通过"}
|
||||
|
||||
|
||||
def normalize_type(value: Any) -> str:
|
||||
text = str(value or "").strip()
|
||||
return text or "unknown-custom"
|
||||
|
||||
|
||||
def sample_context(sample: dict[str, Any], defaults: dict[str, Any]) -> dict[str, str]:
|
||||
return {
|
||||
"authorization_scope": str(get_first(sample, ["authorization_scope", "scope", "target"], get_first(defaults, ["authorization_scope", "scope", "target"], "未声明授权目标"))),
|
||||
"provider": str(get_first(sample, ["provider"], get_first(defaults, ["provider"], "custom-or-unknown"))),
|
||||
"captcha_type": normalize_type(get_first(sample, ["captcha_type", "type"], get_first(defaults, ["captcha_type", "type"], "unknown-custom"))),
|
||||
"captcha_variant": str(get_first(sample, ["captcha_variant", "variant"], get_first(defaults, ["captcha_variant", "variant"], ""))),
|
||||
}
|
||||
|
||||
|
||||
def evaluate(data: Any) -> dict[str, Any]:
|
||||
samples, defaults = normalize_samples(data)
|
||||
success_samples = [sample for sample in samples if is_success(sample)]
|
||||
contexts = [sample_context(sample, defaults) for sample in success_samples]
|
||||
|
||||
by_type: dict[str, int] = {}
|
||||
by_variant: dict[str, int] = {}
|
||||
scopes: dict[str, int] = {}
|
||||
providers: dict[str, int] = {}
|
||||
for context in contexts:
|
||||
captcha_type = context["captcha_type"]
|
||||
variant_key = captcha_type
|
||||
if context["captcha_variant"]:
|
||||
variant_key = f"{captcha_type}/{context['captcha_variant']}"
|
||||
by_type[captcha_type] = by_type.get(captcha_type, 0) + 1
|
||||
by_variant[variant_key] = by_variant.get(variant_key, 0) + 1
|
||||
scopes[context["authorization_scope"]] = scopes.get(context["authorization_scope"], 0) + 1
|
||||
providers[context["provider"]] = providers.get(context["provider"], 0) + 1
|
||||
|
||||
missing: list[dict[str, Any]] = []
|
||||
total_success = len(success_samples)
|
||||
if total_success < MIN_TOTAL_SUCCESS_SAMPLES:
|
||||
missing.append(
|
||||
{
|
||||
"scope": "total",
|
||||
"required": MIN_TOTAL_SUCCESS_SAMPLES,
|
||||
"actual": total_success,
|
||||
"missing": MIN_TOTAL_SUCCESS_SAMPLES - total_success,
|
||||
}
|
||||
)
|
||||
|
||||
for captcha_type, count in sorted(by_type.items()):
|
||||
if count < MIN_SUCCESS_SAMPLES_PER_TYPE:
|
||||
missing.append(
|
||||
{
|
||||
"scope": "captcha_type",
|
||||
"captcha_type": captcha_type,
|
||||
"required": MIN_SUCCESS_SAMPLES_PER_TYPE,
|
||||
"actual": count,
|
||||
"missing": MIN_SUCCESS_SAMPLES_PER_TYPE - count,
|
||||
}
|
||||
)
|
||||
|
||||
if success_samples and not by_type:
|
||||
missing.append(
|
||||
{
|
||||
"scope": "captcha_type",
|
||||
"required": MIN_SUCCESS_SAMPLES_PER_TYPE,
|
||||
"actual": 0,
|
||||
"missing": MIN_SUCCESS_SAMPLES_PER_TYPE,
|
||||
"reason": "成功样本缺少 captcha_type,无法判断动态验证码覆盖。",
|
||||
}
|
||||
)
|
||||
|
||||
status = "sufficient" if success_samples and not missing else "insufficient"
|
||||
dynamic_switch = len(by_type) > 1
|
||||
return {
|
||||
"success_baseline_status": status,
|
||||
"success_baseline_summary": {
|
||||
"total_samples": len(samples),
|
||||
"total_success_samples": total_success,
|
||||
"min_total_success_samples": MIN_TOTAL_SUCCESS_SAMPLES,
|
||||
"min_success_samples_per_type": MIN_SUCCESS_SAMPLES_PER_TYPE,
|
||||
"observed_captcha_types": sorted(by_type),
|
||||
"success_samples_by_type": dict(sorted(by_type.items())),
|
||||
"success_samples_by_type_variant": dict(sorted(by_variant.items())),
|
||||
"authorization_scopes": dict(sorted(scopes.items())),
|
||||
"providers": dict(sorted(providers.items())),
|
||||
"has_dynamic_type_switch": dynamic_switch,
|
||||
},
|
||||
"missing_success_samples": missing,
|
||||
"recommended_next_route": "ready-for-verification-flow" if status == "sufficient" else "collect-more-manual-success-samples",
|
||||
"collection_plan": {
|
||||
"role": "建立真实成功基线,用于对照本地方案、平台方案和失败复盘。",
|
||||
"default_target": "每个授权目标至少 5 次用户手动成功样本;出现新验证码类型时,该类型至少 2 次成功样本。",
|
||||
"required_evidence": [
|
||||
"成功前后截图或截图元信息",
|
||||
"验证码区域 HTML/DOM 摘要",
|
||||
"脚本/iframe URL",
|
||||
"关键请求摘要",
|
||||
"captcha_type/provider/captcha_variant",
|
||||
"成功 UI、callback、response token 或服务端成功状态",
|
||||
"challenge id、时间线和刷新/切题信息",
|
||||
],
|
||||
},
|
||||
"requires_user_confirmation": [
|
||||
"成功基线不足时,必须明确提示风险;用户确认后仍可继续离线分析或受控验证。",
|
||||
"采集真实网页成功样本前,必须确认授权目标和浏览器取证模式。",
|
||||
],
|
||||
"send_request": False,
|
||||
}
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description="评估用户手动成功验证码样本基线")
|
||||
parser.add_argument("--samples", required=True, help="成功样本 JSON 文件路径,或直接传 JSON 字符串")
|
||||
parser.add_argument("--pretty", action="store_true", help="以缩进格式输出 JSON")
|
||||
parser.add_argument("--json", action="store_true", help="兼容参数;脚本始终输出 JSON")
|
||||
return parser
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = build_parser().parse_args()
|
||||
report = evaluate(load_json_source(args.samples))
|
||||
print(json.dumps(report, ensure_ascii=False, indent=2 if args.pretty else None))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user