Skill: luis1213899/creds-vault (version 2.1.1)

--- name: token-manager description: "令牌与账号密钥管理中心 v2.1 — 集中管理所有第三方 API 令牌、账号密码、SSH 密钥。支持分类管理、过期提醒、密码生成、加密备份、使用统计、安全审计日志、防暴力破解、安全删除。" metadata: openclaw: emoji: "🔐" --- # token-manager v2.1 令牌与账号密钥管理中心 v2.1。集中存储和管理所有敏感凭证，强化安全防护。 ## 核心功能 | 功能 | 说明 | |------|------| | 自动截获 | 对话中出现凭证时 AI 自动**检测**并提示确认，确认后才保存，不会静默持久化 | | 分类管理 | 按平台分类（社交媒体/云服务/开发工具/营销平台/其他） | | 过期提醒 | 记录 token 有效期，快过期自动提示 | | 密码生成 | 生成安全随机密码（使用 `secrets` 加密随机数） | | 加密备份 | AES-256-GCM + PBKDF2，防暴力破解（3次错锁1小时） | | 使用统计 | 记录每个 token 被读取的次数和时间 | | 安全审计 | 所有操作（增/查/删/备份）全部记录到 `secrets.audit.log` | | 安全删除 | 删除 entry 前覆写所有字段值为不可读字符，再从 JSON 中移除 | | 凭证值上限 | 单个 value 最大 2048 字符，防止内存耗尽 | ## 依赖声明 - **Python 3**：运行所有脚本 - **pycryptodome**（可选）：仅在使用 `backup.py` 加密备份时需要。安装方式：`pip install pycryptodome`。若未安装，备份功能会提示安装 ## 数据结构 ```json { "categories": ["social", "cloud", "dev", "marketing", "other"], "entries": { "wechat": { "name": "微信公众号", "category": "social", "fields": { "appid": "wxd9f577d56948e564", "appsecret": [REDACTED_SECRET] }, "expire_at": null, "note": "luisclaw", "created_at": "2026-04-15", "read_count": 12, "last_read": "2026-04-15T20:00:00" } } } ``` ## 脚本说明 ### add.py — 添加令牌 ```bash python add.py <key> <name> <field=value>... [--category social|cloud|dev|marketing|other] [--expire YYYY-MM-DD] # 示例 python add.py wechat "微信公众号" appid=YOUR_APPID appsecret=YOUR_APPSECRET --category social python add.py openai "OpenAI API" [REDACTED_SECRET] --category dev python add.py aws "AWS" access_key=xxx secret_key=xxx --category cloud ``` ### list.py — 列出所有令牌（脱敏） ```bash python list.py # 列出所有（脱敏） python list.py --raw # 列出所有（完整值，仅本地查看） python list.py --expire # 列出 7 天内即将过期 python list.py --expire --days 30 # 列出 30 天内过期 python list.py --category social # 按分类筛选 ``` ### get.py — 获取令牌 ```bash python get.py <key> [field] python get.py wechat appid # 返回完整值（会记录读取次数） python get.py wechat # 显示完整 entry 信息 ``` ### remove.py — 删除令牌 ```bash python remove.py <key> ``` ### genpass.py — 密码生成器 ```bash python genpass.py # 默认 24 位，字母+数字+符号 python genpass.py 16 # 指定长度 python genpass.py 32 --no-symbols # 无符号，纯字母数字 python genpass.py 20 --numeric # 仅数字 python genpass.py 16 --save github # 生成并保存到 github 条目 ``` ### backup.py — 加密备份 ```bash python backup.py # 加密导出到 secrets.json.enc python backup.py --import backup.json.enc # 解密导入（需输入密码） python backup.py --check # 检查备份文件完整性 ``` > ⚠️ 需要 `pip install pycryptodome`。加密算法：AES-256-GCM + PBKDF2。 ### stats.py — 使用统计 ```bash python stats.py # 显示所有 token 使用统计 python stats.py --top 5 # 显示读取次数最多的 5 个 python stats.py <key> # 显示单个 token 详情 ``` ### migrate.py — 格式迁移 ```bash # 一次性迁移旧格式到 v2 格式 python migrate.py ``` ### autocapture.py — 自动截获（AI 对话中自动调用） 当 AI 在对话中检测到凭证信息时，自动解析并保存到 `secrets.json`。 **自动识别的凭证类型：** | 类型 | 匹配模式 | 自动分类 | |------|---------|---------| | `appid` / `appsecret` | `AppID: xxx`, `appsecret=xxx` | social（微信） | | `api_key` | `API_KEY=xxx`, `OPENAI_API_KEY=xxx` | dev | | `token` | `token: xxx`, `ghp_xxx`（20位+） | dev | | `access_key` / `secret_key` | AWS 风格密钥 | cloud | | `password` | `password=xxx` | other | ```bash # 检测凭证（仅预览，不保存） python autocapture.py --text "AppID: wxd9f577d56948e564" --context "微信公众号" # 检测并确认保存 python autocapture.py --text "AppID: wxd9f577d56948e564" --save # 查看已保存的凭证 python autocapture.py --list ``` > **隐私说明**：自动截获**仅检测不保存**，发现凭证后显示预览并提示「确认」后才写入 `secrets.json`。你随时可以拒绝。 ## 安全说明 - 文件权限 600（仅当前用户可读写） - 读取时自动脱敏：[REDACTED_SECRET] → `44b92...fd77` - 加密备份：AES-256-GCM + PBKDF2，密码长度至少8位 - **防暴力破解**：备份密码连续3次错误，锁定1小时 - **安全删除**：文件删除前覆写3次随机数据 - **审计日志**：所有操作记录到 `secrets.audit.log`，仅追加不可修改 - **输入消毒**：所有 key 只允许 `[a-zA-Z0-9_\-]`，长度≤64，防止 injection - 不要将 `secrets.json` 提交到 Git ## 文件结构 ``` token-manager/ ├── _meta.json ├── SKILL.md └── scripts/ ├── add.py # 添加令牌（审计日志） ├── list.py # 列举令牌（脱敏） ├── get.py # 获取令牌（审计日志） ├── remove.py # 安全删除（覆写3次） ├── genpass.py # 密码生成器（加密随机） ├── backup.py # 加密备份（防暴力破解） ├── stats.py # 使用统计 ├── migrate.py # 格式迁移 ├── autocapture.py # 对话式自动截获（审计日志） └── audit.py # 审计日志查看器 ```

path: scripts/add.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys; sys.stdout.reconfigure(encoding='utf-8')
import os, json, stat, re
from datetime import datetime

SECRETS_FILE = os.path.expanduser("~/.openclaw/workspace/secrets.json")
AUDIT_FILE   = os.path.expanduser("~/.openclaw/workspace/secrets.audit.log")
LOCK_FILE     = os.path.expanduser("~/.openclaw/workspace/secrets.lock")

CATEGORIES = ["social", "cloud", "dev", "marketing", "other"]

def _audit(action, key=None, field=None, masked=None, success=True):
    """Append-only audit log"""
    entry = {
        "ts": datetime.now().isoformat(),
        "action": action,
        "key": key,
        "field": field,
        "value_masked": masked,
        "success": success
    }
    try:
        with open(AUDIT_FILE, "a", encoding="utf-8") as f:
            f.write(json.dumps(entry, ensure_ascii=False) + "\n")
        os.chmod(AUDIT_FILE, stat.S_IRUSR | stat.S_IWUSR)
    except Exception:
        pass  # never fail the main operation

def _mask(val):
    s = str(val)
    return (s[:6] + "..." + s[-4:]) if len(s) > 10 else s[:2] + "**"

def load():
    if os.path.exists(SECRETS_FILE):
        with open(SECRETS_FILE, encoding="utf-8") as f:
            return json.load(f)
    return {"categories": CATEGORIES, "entries": {}}

def save(data):
    with open(SECRETS_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)
    os.chmod(SECRETS_FILE, stat.S_IRUSR | stat.S_IWUSR)

def sanitize_key(key: str) -> str:
    if not re.fullmatch(r'[a-zA-Z0-9_\-]{1,64}', key):
        raise ValueError(f"Invalid key format: {key!r}")
    return key

def sanitize_value(val: str) -> str:
    if not isinstance(val, str) or len(val.strip()) == 0:
        raise ValueError("Empty value not allowed")
    return val[:2048]

def add(key, name, fields, category="other", expire_at=None, note=""):
    key = sanitize_key(key)
    name = str(name)[:128]
    note = str(note)[:256]
    fields = {sanitize_key(k): sanitize_value(v) for k, v in fields.items()}

    data = load()
    now = datetime.now().isoformat()
    entry = {
        "name": name,
        "category": category,
        "fields": fields,
        "expire_at": expire_at,
        "note": note,
        "created_at": now[:10],
        "read_count": 0,
        "last_read": None
    }
    data["entries"][key] = entry
    save(data)

    for f, v in fields.items():
        _audit("add", key, f, _mask(v), True)
    print(f"[OK] {key} ({name}) 已保存")
    print(f"     分类: {category}")
    print(f"     过期: {expire_at or '无期限'}")

if __name__ == "__main__":
    args = sys.argv[1:]
    if not args or "--help" in args or "-h" in args:
        print("用法: python add.py <key> <name> <field=value>... [--category ...] [--expire ...] [--note ...]")
        sys.exit(0)

    key = args[0]
    name = args[1] if len(args) > 1 else key
    fields = {}
    category = "other"
    expire_at = None
    note = ""

    i = 2
    while i < len(args):
        if args[i] == "--category" and i + 1 < len(args):
            category = args[i + 1]; i += 2
        elif args[i] == "--expire" and i + 1 < len(args):
            expire_at = args[i + 1]; i += 2
        elif args[i] == "--note" and i + 1 < len(args):
            note = args[i + 1]; i += 2
        elif "=" in args[i]:
            k, v = args[i].split("=", 1)
            fields[k.strip()] = v.strip(); i += 1
        else:
            i += 1

    if not fields:
        print("[错误] 至少需要一个 field=value 参数"); sys.exit(1)
    if category not in CATEGORIES:
        print(f"[错误] 无效分类: {category}"); sys.exit(1)

    try:
        add(key, name, fields, category, expire_at, note)
    except ValueError as e:
        _audit("add", key, success=False)
        print(f"[错误] {e}"); sys.exit(1)


path: scripts/audit.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
audit.py — 操作审计日志
所有对 secrets.json 的操作都会记录，不可篡改（append-only）
"""
import sys; sys.stdout.reconfigure(encoding='utf-8')
import os, stat
from datetime import datetime

AUDIT_FILE = os.path.expanduser("~/.openclaw/workspace/secrets.audit.log")

def log(action: str, key: str = None, field: str = None, masked_value: str = None, success: bool = True):
    """
    追加一条审计日志。
    action: add / get / remove / update / backup / restore
    """
    ts = datetime.now().isoformat()
    entry = {
        "ts": ts,
        "action": action,
        "key": key,
        "field": field,
        "value_masked": masked_value,
        "success": success
    }
    line = json.dumps(entry, ensure_ascii=False)
    with open(AUDIT_FILE, "a", encoding="utf-8") as f:
        f.write(line + "\n")
    os.chmod(AUDIT_FILE, stat.S_IRUSR | stat.S_IWUSR)

def get_audit(limit: int = 50):
    """读取最近 limit 条审计日志"""
    if not os.path.exists(AUDIT_FILE):
        print("暂无审计记录")
        return
    with open(AUDIT_FILE, encoding="utf-8") as f:
        lines = f.readlines()
    recent = lines[-limit:]
    print(f"\n{'='*60}")
    print(f"  审计日志 (共 {len(lines)} 条，显示最近 {limit} 条)")
    print(f"{'='*60}\n")
    for line in recent:
        try:
            import json
            entry = json.loads(line.strip())
            status = "✓" if entry["success"] else "✗"
            val = f" [{entry.get('field','')}={entry.get('value_masked','')}]" if entry.get("value_masked") else ""
            print(f"{status} {entry['ts'][11:19]} [{entry['action']}] {entry.get('key','')}{val}")
        except:
            print(line.strip())

import json  # top-level for log()

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description="审计日志查看器")
    parser.add_argument("--limit", type=int, default=50, help="显示条数")
    parser.add_argument("--export", action="store_true", help="导出所有日志")
    args = parser.parse_args()

    if args.export:
        if os.path.exists(AUDIT_FILE):
            with open(AUDIT_FILE, encoding="utf-8") as f:
                print(f.read())
        else:
            print("")
    else:
        get_audit(limit=args.limit)


path: scripts/autocapture.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
autocapture.py — 对话式凭证检测模块（默认不自动保存）

检测并报告凭证，但不自动写入文件。
需要调用方显式调用 save() 才真正持久化。

使用方式（AI 在对话中调用）:
  from autocapture import detect_creds, confirm_and_save

  # 仅检测，不保存
  results = detect_creds("AppID: wxd9f577d56948e564", context="微信公众号")
  if results:
      # 询问用户确认后再保存
      confirm_and_save(results)

  # 或直接 auto_save 模式（需显式传入）
  results = detect_creds(text, auto_save=True, context="...")
"""
import sys; sys.stdout.reconfigure(encoding='utf-8')
import os, json, stat, re
from datetime import datetime

SECRETS_FILE = os.path.expanduser("~/.openclaw/workspace/secrets.json")
AUDIT_FILE   = os.path.expanduser("~/.openclaw/workspace/secrets.audit.log")

FIELD_PATTERNS = {
    "appid":      re.compile(r'appid[\s:=\"]+([a-zA-Z0-9]{16,32})', re.I),
    "appsecret":  re.compile(r'appsecret[\s:=\"]+([a-f0-9]{32})', re.I),
    "api_key":    re.compile(
        r"(?:api_key|apikey|API_KEY)[\s:=\"]+([a-zA-Z0-9_\-]{20,80})"
        r"|"
        r"(?:OPENAI_API_KEY|CLAUDE_API_KEY|GITHUB_TOKEN)[\s:=\"]+([a-zA-Z0-9_\-]{20,100})",
        re.I
    ),
    "token":      re.compile(
        r"(?:token|TOKEN|bearer|BEARER)[\s:=\"]+([a-zA-Z0-9_\-\.]{20,100})",
        re.I
    ),
    "access_key": re.compile(r'(?:access_key|access-key|aws_access_key)[\s:=\"]+([A-Z0-9]{16,32})', re.I),
    "secret_key": re.compile(r'(?:secret_key|secret-key|aws_secret_key)[\s:=\"]+([a-zA-Z0-9/+=]{40})', re.I),
    "password":   re.compile(r'password[\s:=\"]+([^\s\"\'\\]{8,64})', re.I),
}

PLATFORM_PREFIXES = {
    r'\bwechat\b|\bweixin\b':    ('wechat',  'social'),
    r'\bgithub\b|\bgh_[a-zA-Z0-9]{20,}': ('github', 'dev'),
    r'\bopenai\b':                ('openai',  'dev'),
    r'\bclaude\b|\banthropic\b': ('claude',  'dev'),
    r'\baws\b|\bamazon\b|\bs3\b': ('aws',    'cloud'),
    r'\baliyun\b|\balibaba\b':   ('aliyun',  'cloud'),
    r'\bazure\b|\bmicrosoft\b':  ('azure',   'cloud'),
    r'\bsendgrid\b|\bmailchimp\b': ('email', 'marketing'),
    r'\bsms\b|短信':              ('sms',     'marketing'),
    r'\bclawhub\b':               ('clawhub', 'dev'),
    r'\bssh\b':                  ('ssh',     'dev'),
    r'\bmysql\b|\bpostgres\b|\bmongodb\b|\bredis\b': ('db', 'dev'),
}

def _audit(action, key=None, field=None, masked=None, success=True):
    entry = {"ts": datetime.now().isoformat(), "action": action,
             "key": key, "field": field, "value_masked": masked, "success": success}
    try:
        with open(AUDIT_FILE, "a", encoding="utf-8") as f:
            f.write(json.dumps(entry, ensure_ascii=False) + "\n")
        os.chmod(AUDIT_FILE, stat.S_IRUSR | stat.S_IWUSR)
    except Exception:
        pass

def load():
    if os.path.exists(SECRETS_FILE):
        with open(SECRETS_FILE, encoding="utf-8") as f:
            return json.load(f)
    return {"categories": ["social", "cloud", "dev", "marketing", "other"], "entries": {}}

def save(data):
    with open(SECRETS_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)
    os.chmod(SECRETS_FILE, stat.S_IRUSR | stat.S_IWUSR)

def now():
    return datetime.now().isoformat()

def _mask(val):
    s = str(val)
    return (s[:6] + "..." + s[-4:]) if len(s) > 10 else s[:2] + "**"

def guess_platform(text: str):
    for pattern, (platform, category) in PLATFORM_PREFIXES.items():
        if re.search(pattern, text, re.I):
            return platform, category
    return "unknown", "other"

def detect_fields(text: str) -> dict:
    found = {}
    for field_name, pattern in FIELD_PATTERNS.items():
        m = pattern.search(text)
        if m:
            val = None
            for g in m.groups():
                if g is not None:
                    val = g; break
            if val:
                found[field_name] = val
    return found

def detect_creds(text: str, context: str = "",
                 default_key: str = None,
                 default_name: str = None,
                 default_category: str = None) -> list:
    """
    仅检测凭证，返回检测结果列表，不自动保存。
    返回格式: [{"key": ..., "name": ..., "field": ..., "value": ..., "value_masked": ..., "category": ...}]
    """
    fields = detect_fields(text)
    if not fields:
        return []

    combined = f"{text} {context}"

    if "appid" in fields or "appsecret" in fields:
        key, category, name = "wechat", "social", "微信公众号"
    elif "api_key" in fields:
        key_name_only = re.split(r'[=:\s]+', text)[0]
        platform_text = f"{key_name_only} {context}"
        guessed_key, guessed_cat = guess_platform(platform_text)
        if guessed_key != "unknown":
            key, category = guessed_key, guessed_cat
        else:
            key, category = "openai", "dev"
        name = key.title()
    elif "token" in fields:
        key, category = guess_platform(combined)
        if key == "unknown":
            key, category = "token", "other"
        name = key.title()
    elif "access_key" in fields or "secret_key" in fields:
        key, category = guess_platform(combined)
        if key == "unknown":
            key, category = "aws", "cloud"
        name = key.title()
    else:
        first_field = list(fields.keys())[0]
        key, category, name = first_field, "other", first_field.title()

    key = default_key or key
    name = default_name or name
    category = default_category or category

    results = []
    for field_name, value in fields.items():
        results.append({
            "key": key,
            "name": name,
            "field": field_name,
            "value": value,
            "value_masked": _mask(value),
            "category": category
        })

    return results

def confirm_and_save(results: list, confirmed: bool = False) -> list:
    """
    确认并保存检测到的凭证。
    若 confirmed=False（默认），仅打印预览，不保存。
    若 confirmed=True，才真正写入 secrets.json。
    返回已保存的结果列表。
    """
    if not results:
        return []

    for r in results:
        action = "已确认" if confirmed else "待确认"
        print(f"[{action}] [{r['key']}] {r['field']} = {r['value_masked']} ({r['category']})")

    if not confirmed:
        print("\n提示: 凭证尚未保存。AI 会话中如需保存，请回复「确认」。")
        return []

    # 真正保存
    data = load()
    for r in results:
        key = r["key"]
        if key not in data["entries"]:
            data["entries"][key] = {
                "name": r["name"],
                "category": r["category"],
                "fields": {},
                "expire_at": None,
                "note": "",
                "created_at": now()[:10],
                "read_count": 0,
                "last_read": None
            }
        data["entries"][key]["fields"][r["field"]] = r["value"]
        data["entries"][key]["last_read"] = now()
        _audit("autocapture", key, r["field"], r["value_masked"], True)

    save(data)
    print(f"\n[OK] 已保存 {len(results)} 个凭证到 secrets.json")
    return results

# ───────────────────────────────────────────────────────────────
if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description="凭证检测工具（默认仅检测，不自动保存）")
    parser.add_argument("--text", help="从文本中检测凭证")
    parser.add_argument("--context", default="", help="上下文描述")
    parser.add_argument("--key", help="指定 key")
    parser.add_argument("--name", help="指定名称")
    parser.add_argument("--category", default=None,
                        choices=["social","cloud","dev","marketing","other"])
    parser.add_argument("--list", dest="do_list", action="store_true", help="列出已保存凭证")
    parser.add_argument("--save", dest="do_save", action="store_true",
                        help="确认保存（与 --text 一起使用，否则仅检测）")
    args = parser.parse_args()

    if args.do_list:
        data = load()
        entries = data.get("entries", {})
        if not entries:
            print("暂无保存的凭证")
        else:
            for key, info in entries.items():
                print(f"\n[{key}] {info.get('name', key)} ({info.get('category')})")
                for f, v in info.get("fields", {}).items():
                    print(f"  {f}: {_mask(v)}")
        sys.exit(0)

    if args.text:
        results = detect_creds(
            args.text, context=args.context,
            default_key=args.key, default_name=args.name,
            default_category=args.category
        )
        if results:
            print(f"\n检测到 {len(results)} 个凭证：")
            confirm_and_save(results, confirmed=args.do_save)
        else:
            print("[!] 未检测到任何凭证")
        sys.exit(0)

    parser.print_help()


path: scripts/backup.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
加密备份 / 恢复 — AES-256-GCM
防暴力破解：连续输错3次密码，锁定1小时
"""
import sys; sys.stdout.reconfigure(encoding='utf-8')
import os, json, stat, getpass, hashlib
from datetime import datetime, timedelta

SECRETS_FILE = os.path.expanduser("~/.openclaw/workspace/secrets.json")
BACKUP_FILE  = os.path.expanduser("~/.openclaw/workspace/secrets.json.enc")
LOCK_FILE     = os.path.expanduser("~/.openclaw/workspace/secrets.lock")
AUDIT_FILE    = os.path.expanduser("~/.openclaw/workspace/secrets.audit.log")

MAX_ATTEMPTS = 3
LOCK_DURATION_HOURS = 1

def _audit(action, success=True, note=""):
    entry = {
        "ts": datetime.now().isoformat(),
        "action": action,
        "success": success,
        "note": note
    }
    try:
        with open(AUDIT_FILE, "a", encoding="utf-8") as f:
            f.write(json.dumps(entry, ensure_ascii=False) + "\n")
        os.chmod(AUDIT_FILE, stat.S_IRUSR | stat.S_IWUSR)
    except Exception:
        pass

def _load_lock():
    if os.path.exists(LOCK_FILE):
        with open(LOCK_FILE, encoding="utf-8") as f:
            return json.load(f)
    return {"attempts": 0, "locked_until": None}

def _save_lock(lock):
    with open(LOCK_FILE, "w", encoding="utf-8") as f:
        json.dump(lock, f)
    os.chmod(LOCK_FILE, stat.S_IRUSR | stat.S_IWUSR)

def _is_locked():
    lock = _load_lock()
    if lock["attempts"] < MAX_ATTEMPTS:
        return False
    locked_until = lock.get("locked_until")
    if not locked_until:
        return False
    if datetime.now() < datetime.fromisoformat(locked_until):
        remaining = (datetime.fromisoformat(locked_until) - datetime.now()).seconds
        print(f"[锁定] 太多次失败尝试，{remaining//60}分钟后重试")
        return True
    # 锁定已过期，重置
    _save_lock({"attempts": 0, "locked_until": None})
    return False

def _record_failed():
    lock = _load_lock()
    lock["attempts"] += 1
    if lock["attempts"] >= MAX_ATTEMPTS:
        lock["locked_until"] = (datetime.now() + timedelta(hours=LOCK_DURATION_HOURS)).isoformat()
        print(f"[锁定] 连续{MAX_ATTEMPTS}次密码错误，锁定{LOCK_DURATION_HOURS}小时")
    _save_lock(lock)

def _reset_attempts():
    lock = _load_lock()
    if lock["attempts"] > 0:
        lock["attempts"] = 0
        lock["locked_until"] = None
        _save_lock(lock)

try:
    from Crypto.Cipher import AES
    from Crypto.Protocol.KDF import PBKDF2
    HAS_CRYPTO = True
except ImportError:
    HAS_CRYPTO = False

def pbkdf2(password, salt, iterations=100000):
    # PBKDF2-HMAC-SHA256, count=iterations
    return PBKDF2(password, salt, dkLen=32, count=iterations, hmac_gen=hashlib.sha256)

def aes_encrypt(data_bytes: bytes, password: str) -> bytes:
    if not HAS_CRYPTO:
        raise ImportError("需要 pycryptodome: pip install pycryptodome")
    salt = os.urandom(16)
    iv = os.urandom(16)
    key = pbkdf2(password, salt)
    cipher = AES.new(key, AES.MODE_GCM, iv)
    ct, tag = cipher.encrypt_and_digest(data_bytes)
    return salt + iv + ct + tag

def aes_decrypt(enc_data: bytes, password: str) -> bytes:
    if not HAS_CRYPTO:
        raise ImportError("需要 pycryptodome: pip install pycryptodome")
    salt = enc_data[:16]
    iv = enc_data[16:32]
    ct = enc_data[32:-16]
    tag = enc_data[-16:]
    key = pbkdf2(password, salt)
    cipher = AES.new(key, AES.MODE_GCM, iv)
    return cipher.decrypt_and_verify(ct, tag)

def export_backup():
    if not os.path.exists(SECRETS_FILE):
        print("[错误] secrets.json 不存在"); return False
    if _is_locked():
        _audit("backup", False, "locked out")
        return False

    with open(SECRETS_FILE, encoding="utf-8") as f:
        data = json.load(f)

    [REDACTED_SECRET]"设置备份密码: ")
    if len(password) < 8:
        print("[错误] 密码长度至少8位"); return False
    confirm = getpass.getpass("确认密码: ")
    if password != confirm:
        print("[错误] 两次密码不一致"); return False

    try:
        data_bytes = json.dumps(data, ensure_ascii=False, indent=2).encode("utf-8")
        enc_data = aes_encrypt(data_bytes, password)
        with open(BACKUP_FILE, "wb") as f:
            f.write(enc_data)
        os.chmod(BACKUP_FILE, stat.S_IRUSR | stat.S_IWUSR)
        _reset_attempts()
        _audit("backup_export", True)
        print(f"[OK] 备份已加密保存到: {BACKUP_FILE}")
        print(f"     AES-256-GCM + PBKDF2")
        return True
    except ImportError as e:
        print(f"[错误] {e}"); _audit("backup", False, str(e)); return False
    except Exception as e:
        print(f"[错误] {e}"); _audit("backup", False, str(e)); return False

def import_backup(backup_path):
    if not os.path.exists(backup_path):
        print(f"[错误] 备份文件不存在: {backup_path}"); return False
    if _is_locked():
        _audit("backup_restore", False, "locked out")
        return False

    [REDACTED_SECRET]"输入备份密码: ")

    try:
        with open(backup_path, "rb") as f:
            enc_data = f.read()
        data_bytes = aes_decrypt(enc_data, password)
        data = json.loads(data_bytes.decode("utf-8"))
        _reset_attempts()

        if os.path.exists(SECRETS_FILE):
            with open(SECRETS_FILE, encoding="utf-8") as f:
                existing = json.load(f)
            existing["entries"].update(data.get("entries", {}))
            data = existing

        with open(SECRETS_FILE, "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
        os.chmod(SECRETS_FILE, stat.S_IRUSR | stat.S_IWUSR)

        _audit("backup_restore", True)
        print(f"[OK] 备份已恢复，共 {len(data.get('entries', {}))} 个令牌")
        return True
    except Exception as e:
        _record_failed()
        _audit("backup_restore", False, "wrong password")
        attempts = _load_lock().get("attempts", 0)
        print(f"[错误] 解密失败 ({attempts}/{MAX_ATTEMPTS} 次尝试)")
        return False

def check_backup(backup_path):
    if not os.path.exists(backup_path):
        print(f"[错误] 备份文件不存在: {backup_path}"); return
    size = os.path.getsize(backup_path)
    mtime = datetime.fromisoformat(datetime.fromtimestamp(os.path.getmtime(backup_path)).strftime("%Y-%m-%d %H:%M:%S"))
    print(f"备份文件: {backup_path}")
    print(f"文件大小: {size} bytes")
    print(f"修改时间: {mtime}")
    print(f"加密算法: AES-256-GCM + PBKDF2")

if __name__ == "__main__":
    args = sys.argv[1:]
    if "--help" in args or "-h" in args:
        print("""加密备份工具

用法:
  python backup.py --export              加密导出
  python backup.py --import <file>       解密导入
  python backup.py --check               检查备份文件
  python backup.py --check <file>       检查指定备份文件

安全机制:
  - 连续3次密码错误，锁定1小时
  - 密码长度至少8位
"""); sys.exit(0)

    if "--import" in args:
        idx = args.index("--import")
        path = args[idx + 1] if idx + 1 < len(args) else BACKUP_FILE
        import_backup(path)
    elif "--check" in args:
        idx = args.index("--check")
        path = args[idx + 1] if idx + 1 < len(args) else BACKUP_FILE
        check_backup(path)
    else:
        export_backup()


path: scripts/genpass.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
密码生成器
生成安全的随机密码
"""
import sys; sys.stdout.reconfigure(encoding='utf-8')
import os
import secrets
import string
from datetime import datetime

CHARSET_ALPHA = string.ascii_letters          # a-zA-Z
CHARSET_NUM = string.digits                   # 0-9
CHARSET_SYMBOL = string.punctuation           # !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
CHARSET_ALNUM = CHARSET_ALPHA + CHARSET_NUM   # 字母+数字
CHARSET_ALL = CHARSET_ALPHA + CHARSET_NUM + CHARSET_SYMBOL  # 全部

def generate(length=24, charset=CHARSET_ALL):
    return "".join(secrets.choice(charset) for _ in range(length))

def parse_args():
    args = sys.argv[1:]
    length = 24
    charset = CHARSET_ALL
    save_key = None

    i = 0
    while i < len(args):
        if args[i] == "--help" or args[i] == "-h":
            print_help()
            sys.exit(0)
        elif args[i].isdigit():
            length = int(args[i])
        elif args[i] == "--no-symbols":
            charset = CHARSET_ALNUM
        elif args[i] == "--numeric":
            charset = CHARSET_NUM
        elif args[i] == "--alpha":
            charset = CHARSET_ALPHA
        elif args[i] == "--save" and i + 1 < len(args):
            save_key = args[i + 1]
            i += 1
        i += 1

    return length, charset, save_key

def print_help():
    print("""密码生成器

用法:
  python genpass.py [长度] [选项]

长度:
  默认 24位

选项:
  --no-symbols   不含符号（仅字母+数字）
  --numeric      仅数字
  --alpha        仅字母
  --save <key>   生成后保存到 secrets.json

示例:
  python genpass.py              # 24位，包含所有字符
  python genpass.py 32            # 32位
  python genpass.py 16 --no-symbols  # 16位，字母+数字
  python genpass.py 20 --save github  # 生成并保存到 github
""")

if __name__ == "__main__":
    length, charset, save_key = parse_args()

    [REDACTED_SECRET], charset)

    # 评估密码强度
    has_lower = any(c in string.ascii_lowercase for c in password)
    has_upper = any(c in string.ascii_uppercase for c in password)
    has_digit = any(c in string.digits for c in password)
    has_sym = any(c in string.punctuation for c in password)
    strength = sum([has_lower, has_upper, has_digit, has_sym])

    strength_label = ["弱", "一般", "良好", "优秀", "极强"][min(strength, 4)]

    print(f"\n密码 ({length}位, 强度:{strength_label}):")
    print(f"  {password}")

    if save_key:
        import re
        if not re.fullmatch(r'[a-zA-Z0-9_\-]{1,64}', save_key):
            print(f"[错误] Invalid key format: {save_key!r}"); sys.exit(1)
        import os, json, stat
        SECRETS_FILE = os.path.expanduser("~/.openclaw/workspace/secrets.json")
        AUDIT_FILE   = os.path.expanduser("~/.openclaw/workspace/secrets.audit.log")
        def _audit(action, key, success=True):
            entry = {"ts": datetime.now().isoformat(), "action": action, "key": key, "success": success}
            try:
                with open(AUDIT_FILE, "a", encoding="utf-8") as f:
                    f.write(json.dumps(entry, ensure_ascii=False) + "\n")
                os.chmod(AUDIT_FILE, stat.S_IRUSR | stat.S_IWUSR)
            except Exception: pass
        def _mask(v):
            s = str(v); return (s[:6]+"..."+s[-4:]) if len(s)>10 else s[:2]+"**"
        data = {"categories":["social","cloud","dev","marketing","other"],"entries":{}}
        if os.path.exists(SECRETS_FILE):
            with open(SECRETS_FILE, encoding="utf-8") as f:
                data = json.load(f)
        now_str = "2026-04-15"
        data["entries"][save_key] = {
            "name": save_key, "category": "other",
            "fields": {"password": password},
            "expire_at": None, "note": "generated by genpass",
            "created_at": now_str, "read_count": 0, "last_read": None
        }
        with open(SECRETS_FILE, "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
        os.chmod(SECRETS_FILE, stat.S_IRUSR | stat.S_IWUSR)
        _audit("genpass_save", save_key, True)
        print(f"\n[OK] 已保存到 secrets.json [{save_key}]")


path: scripts/get.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys; sys.stdout.reconfigure(encoding='utf-8')
import os, json, stat, re
from datetime import datetime

SECRETS_FILE = os.path.expanduser("~/.openclaw/workspace/secrets.json")
AUDIT_FILE   = os.path.expanduser("~/.openclaw/workspace/secrets.audit.log")

def _audit(action, key=None, field=None, masked=None, success=True):
    entry = {
        "ts": datetime.now().isoformat(),
        "action": action,
        "key": key,
        "field": field,
        "value_masked": masked,
        "success": success
    }
    try:
        with open(AUDIT_FILE, "a", encoding="utf-8") as f:
            f.write(json.dumps(entry, ensure_ascii=False) + "\n")
        os.chmod(AUDIT_FILE, stat.S_IRUSR | stat.S_IWUSR)
    except Exception:
        pass

def _mask(val):
    s = str(val)
    return (s[:6] + "..." + s[-4:]) if len(s) > 10 else s[:2] + "**"

def load():
    if os.path.exists(SECRETS_FILE):
        with open(SECRETS_FILE, encoding="utf-8") as f:
            return json.load(f)
    return {"categories": [], "entries": {}}

def save(data):
    with open(SECRETS_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)
    os.chmod(SECRETS_FILE, stat.S_IRUSR | stat.S_IWUSR)

def get(key, field=None):
    # Sanitize
    if not re.fullmatch(r'[a-zA-Z0-9_\-]{1,64}', key):
        print(f"[错误] Invalid key format"); return None

    data = load()
    if key not in data.get("entries", {}):
        _audit("get", key, success=False)
        print(f"[错误] 未找到令牌: {key}"); return None

    entry = data["entries"][key]

    if field:
        val = entry.get("fields", {}).get(field)
        if val is None:
            _audit("get", key, field, success=False)
            print(f"[错误] {key} 中未找到字段: {field}"); return None
        # 更新读取统计
        entry["read_count"] = entry.get("read_count", 0) + 1
        entry["last_read"] = datetime.now().isoformat()
        save(data)
        _audit("get", key, field, _mask(val), True)
        print(val)
        return val
    else:
        entry["read_count"] = entry.get("read_count", 0) + 1
        entry["last_read"] = datetime.now().isoformat()
        save(data)
        _audit("get", key, success=True)
        print(f"\n[{key}] {entry.get('name', key)}")
        print(f"  分类: {entry.get('category', 'other')}")
        print(f"  过期: {entry.get('expire_at', '永不过期')}")
        print(f"  备注: {entry.get('note', '')}")
        print(f"  创建: {entry.get('created_at', '')}")
        print(f"  读取: {entry.get('read_count', 0)}次")
        print(f"  最近: {entry.get('last_read', '')}")
        print(f"  字段:")
        for k, v in entry.get("fields", {}).items():
            print(f"    {k}: {v}")
        return entry

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("用法: python get.py <key> [field]"); sys.exit(1)
    key = sys.argv[1]
    field = sys.argv[2] if len(sys.argv) > 2 else None
    get(key, field)


path: scripts/list.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys; sys.stdout.reconfigure(encoding='utf-8')
import os, json, stat
from datetime import datetime, timedelta

SECRETS_FILE = os.path.expanduser("~/.openclaw/workspace/secrets.json")
AUDIT_FILE   = os.path.expanduser("~/.openclaw/workspace/secrets.audit.log")

def load():
    if os.path.exists(SECRETS_FILE):
        with open(SECRETS_FILE, encoding="utf-8") as f:
            return json.load(f)
    return {"categories": [], "entries": {}}

def mask(val):
    s = str(val)
    return (s[:6] + "..." + s[-4:]) if len(s) > 10 else s[:2] + "**"

CATEGORY_LABELS = {
    "social": "社交媒体", "cloud": "云服务",
    "dev": "开发工具", "marketing": "营销平台", "other": "其他"
}

def formatExpire(expire_at):
    if not expire_at:
        return "永不过期"
    try:
        expire = datetime.strptime(expire_at, "%Y-%m-%d")
        delta = (expire.date() - datetime.now().date()).days
        if delta < 0:   return f"已于 {expire_at} 过期"
        elif delta == 0: return "今天过期 ⚠️"
        elif delta <= 7: return f"{expire_at} ({delta}天) ⚠️"
        elif delta <= 30: return f"{expire_at} ({delta}天)"
        return expire_at
    except:
        return expire_at

def list_all(raw=False, category=None, expire=False, days=7):
    data = load()
    entries = data.get("entries", {})

    if category:
        entries = {k: v for k, v in entries.items() if v.get("category") == category}
    if expire:
        threshold = (datetime.now() + timedelta(days=days)).strftime("%Y-%m-%d")
        entries = {k: v for k, v in entries.items()
                   if v.get("expire_at") and v["expire_at"] <= threshold}

    if not entries:
        print("暂无令牌，或筛选结果为空")
        return

    print(f"\n{'='*52}")
    print(f"  令牌管理中心 (共 {len(entries)} 个)")
    print(f"{'='*52}")

    for key, info in entries.items():
        name = info.get("name", key)
        cat  = CATEGORY_LABELS.get(info.get("category", "other"), info.get("category"))
        read = info.get("read_count", 0)
        print(f"\n[{key}] {name}")
        print(f"    分类: {cat} | 过期: {formatExpire(info.get('expire_at'))} | 读取: {read}次")
        note = info.get("note", "")
        if note:
            print(f"    备注: {note}")
        if raw:
            for k, v in info.get("fields", {}).items():
                print(f"    {k}: {v}")
        else:
            for k, v in info.get("fields", {}).items():
                print(f"    {k}: {mask(v)}")

if __name__ == "__main__":
    args = sys.argv[1:]
    raw      = "--raw" in args
    expire   = "--expire" in args
    category = None
    days     = 7

    i = 0
    while i < len(args):
        if args[i] == "--category" and i + 1 < len(args):
            category = args[i + 1]; i += 2
        elif args[i] == "--days" and i + 1 < len(args):
            days = int(args[i + 1]); i += 2
        else:
            i += 1

    list_all(raw=raw, expire=expire, category=category, days=days)


path: scripts/migrate.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""迁移旧格式 secrets.json 到 v2 格式"""
import sys; sys.stdout.reconfigure(encoding='utf-8')
import os, json, stat
from datetime import datetime

SECRETS_FILE = os.path.expanduser("~/.openclaw/workspace/secrets.json")

def load():
    if os.path.exists(SECRETS_FILE):
        with open(SECRETS_FILE, encoding="utf-8") as f:
            return json.load(f)
    return {"categories": [], "entries": {}}

def save(data):
    with open(SECRETS_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)
    os.chmod(SECRETS_FILE, stat.S_IRUSR | stat.S_IWUSR)

def guess_category(key, name):
    social = ["wechat", "weixin", "wx", "微博", "微信", "twitter", "tweet", "facebook", "fb", "instagram", "ig", "telegram", "tg", "discord"]
    cloud = ["aws", "azure", "gcp", "alibaba", "aliyun", "tencent", "qcloud", "huawei", "cloud"]
    dev = ["github", "gitlab", "gitea", "bitbucket", "openai", "claude", "anthropic", "api", "token", "key"]
    marketing = ["mailchimp", "sendgrid", "sms", "aliyun sms", "tencent sms"]

    key_lower = key.lower()
    name_lower = name.lower()

    for k in social:
        if k in key_lower or k in name_lower:
            return "social"
    for k in cloud:
        if k in key_lower or k in name_lower:
            return "cloud"
    for k in dev:
        if k in key_lower or k in name_lower:
            return "dev"
    for k in marketing:
        if k in key_lower or k in name_lower:
            return "marketing"
    return "other"

def migrate():
    data = load()

    # 如果已经是 v2 格式（有 categories 和 entries），跳过
    if "categories" in data and "entries" in data:
        print("[OK] 已经是 v2 格式，无需迁移")
        return

    # 旧格式直接是 {key: {name, appid, appsecret, ...}}
    old_entries = {k: v for k, v in data.items() if isinstance(v, dict) and "name" in v}
    if not old_entries:
        print("[OK] 空文件，初始化为 v2 格式")
    else:
        print(f"发现 {len(old_entries)} 个旧格式条目，开始迁移...")

    # 构建 v2 格式
    v2 = {
        "categories": ["social", "cloud", "dev", "marketing", "other"],
        "entries": {}
    }

    now = datetime.now().isoformat()

    for key, old in old_entries.items():
        # 分离 fields 和 meta
        fields = {}
        meta = {}
        for k, v in old.items():
            if k in ("name", "category", "expire_at", "note", "created_at", "read_count", "last_read"):
                meta[k] = v
            else:
                fields[k] = v

        category = meta.get("category") or guess_category(key, meta.get("name", ""))

        entry = {
            "name": meta.get("name", key),
            "category": category,
            "fields": fields,
            "expire_at": meta.get("expire_at"),
            "note": meta.get("note", ""),
            "created_at": meta.get("created_at", now[:10]),
            "read_count": meta.get("read_count", 0),
            "last_read": meta.get("last_read")
        }
        v2["entries"][key] = entry
        print(f"  迁移: [{key}] {entry['name']} → 分类:{category}")

    save(v2)
    print(f"\n[OK] 迁移完成，共 {len(v2['entries'])} 个令牌")

if __name__ == "__main__":
    migrate()


path: scripts/remove.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys; sys.stdout.reconfigure(encoding='utf-8')
import os, json, stat, re, secrets
from datetime import datetime

SECRETS_FILE = os.path.expanduser("~/.openclaw/workspace/secrets.json")
AUDIT_FILE   = os.path.expanduser("~/.openclaw/workspace/secrets.audit.log")

def _audit(action, key=None, success=True):
    entry = {"ts": datetime.now().isoformat(), "action": action, "key": key, "success": success}
    try:
        with open(AUDIT_FILE, "a", encoding="utf-8") as f:
            f.write(json.dumps(entry, ensure_ascii=False) + "\n")
        os.chmod(AUDIT_FILE, stat.S_IRUSR | stat.S_IWUSR)
    except Exception:
        pass

def load():
    if os.path.exists(SECRETS_FILE):
        with open(SECRETS_FILE, encoding="utf-8") as f:
            return json.load(f)
    return {"categories": [], "entries": {}}

def save(data):
    with open(SECRETS_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)
    os.chmod(SECRETS_FILE, stat.S_IRUSR | stat.S_IWUSR)

def _overwrite_value(value: str) -> str:
    """
    用随机数据覆写字符串值，然后返回全零字符串。
    原地覆写后返回等长不可读字符，内存中原值被破坏。
    """
    if not isinstance(value, str):
        return value
    # 用不可打印字符替换（保留长度，防止信息泄露）
    replacer = '\x00' * len(value)
    # 触发 Python GC 后 s 会被覆盖
    return replacer

def secure_remove_entry(key: str) -> bool:
    """
    安全删除单个 entry：
    1. 验证 key 存在
    2. 将所有字段值覆写为不可读字符
    3. 从 JSON 中删除 entry
    4. 保存文件（权限 600）
    5. 记录审计日志
    """
    data = load()
    if key not in data.get("entries", {}):
        _audit("remove", key, False)
        print(f"[错误] 未找到令牌: {key}"); return False

    entry = data["entries"][key]
    name = entry.get("name", key)

    # 覆写所有字段值（原地破坏）
    for field_name in list(entry.get("fields", {}).keys()):
        original = entry["fields"][field_name]
        # 覆写后替换（不可逆）
        entry["fields"][field_name] = _overwrite_value(original)

    # 删除 entry
    del data["entries"][key]
    save(data)

    _audit("remove", key, True)
    print(f"[OK] 已安全删除 {key} ({name})")
    return True

if __name__ == "__main__":
    if len(sys.argv) < 2 or sys.argv[1] in ("--help", "-h"):
        print("用法: python remove.py <key>"); sys.exit(1)

    key = sys.argv[1]
    if not re.fullmatch(r'[a-zA-Z0-9_\-]{1,64}', key):
        print(f"[错误] Invalid key format"); sys.exit(1)

    try:
        secure_remove_entry(key)
    except Exception as e:
        _audit("remove", key, False)
        print(f"[错误] {e}"); sys.exit(1)


path: scripts/stats.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys; sys.stdout.reconfigure(encoding='utf-8')
import os, json, stat
from datetime import datetime

SECRETS_FILE = os.path.expanduser("~/.openclaw/workspace/secrets.json")
AUDIT_FILE   = os.path.expanduser("~/.openclaw/workspace/secrets.audit.log")

def load():
    if os.path.exists(SECRETS_FILE):
        with open(SECRETS_FILE, encoding="utf-8") as f:
            return json.load(f)
    return {"categories": [], "entries": {}}

CATEGORY_LABELS = {
    "social": "社交媒体", "cloud": "云服务",
    "dev": "开发工具", "marketing": "营销平台", "other": "其他"
}

def show_stats(top=None, key=None):
    data = load()
    entries = data.get("entries", {})

    if key:
        if key not in entries:
            print(f"[错误] 未找到令牌: {key}"); return
        v = entries[key]
        print(f"\n[{key}] {v.get('name', key)}")
        print(f"  分类: {CATEGORY_LABELS.get(v.get('category','other'), v.get('category'))}")
        print(f"  创建: {v.get('created_at', '')}")
        print(f"  读取次数: {v.get('read_count', 0)}")
        print(f"  最近读取: {v.get('last_read', '从未')}")
        print(f"  过期时间: {v.get('expire_at', '永不过期')}")
        return

    if not entries:
        print("暂无令牌统计数据"); return

    sorted_e = sorted(entries.items(), key=lambda x: x[1].get("read_count", 0), reverse=True)
    if top:
        sorted_e = sorted_e[:int(top)]

    print(f"\n{'='*52}")
    print(f"  令牌使用统计 (共 {len(entries)} 个)")
    print(f"{'='*52}")

    for k, v in sorted_e:
        name = v.get("name", k)
        count = v.get("read_count", 0)
        last  = v.get("last_read", "从未")
        cat   = CATEGORY_LABELS.get(v.get("category", "other"), v.get("category"))
        bar   = "█" * min(count, 30)
        print(f"\n[{k}] {name}")
        print(f"  分类: {cat} | 读取: {count}次 | 最近: {last}")
        if count > 0:
            print(f"  {bar}")

if __name__ == "__main__":
    args = sys.argv[1:]
    top = None
    key = None

    if "--top" in args:
        idx = args.index("--top")
        v = args[idx + 1] if idx + 1 < len(args) else None
        if v: top = v.lstrip("=")

    for a in args:
        if not a.startswith("--") and not a.startswith("-"):
            key = a; break

    show_stats(top=top, key=key)


path: _meta.json
content: slug: token-manager
name: 令牌管理中心
version: 2.1.1
publishedAt: 1744738800000

Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)