Back to skill

Security audit

skill-distributor

Security checks for vulnerabilities and agentic risk

Overview

This is a legitimate skill-publishing helper, but it gives local configuration and tokens enough power to run high-impact publish actions without sufficient safeguards.

Install only if you intend to use this as a local publishing tool for skill directories you control. Before running it, inspect any .skill-distributor/config.json in the target skill, use narrowly scoped tokens, prefer dry-run first, verify the exact GitHub repository and branch, and avoid enabling custom SkillHub commands or mirror URLs from untrusted packages.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/distribute.py:108
Finding
Arbitrary Command Execution Through Untrusted Per-Skill Configuration## Vulnerability Details **File Location**: `scripts/distribute.py:108-118, 378-383, 397-412` **Vulnerability Type**: Untrusted configuration used to select and execute a local command **Risk Level**: High ### Complete Code Snippet ```python def load_config(skill_dir): """Configuration priority: skill-local config, user-global config, defaults.""" config = json.loads(json.dumps(DEFAULT_CONFIG)) local_cfg = Path(skill_dir) / ".skill-distributor" / "config.json" candidates = [] if local_cfg.exists(): candidates.append(local_cfg) if HOME_CONFIG_DIR.joinpath("config.json").exists(): candidates.append(HOME_CONFIG_DIR.joinpath("config.json")) for p in candidates: try: merge_config(config, json.loads(p.read_text(encoding="utf-8-sig"))) except Exception as e: log(f"Warning: failed to read configuration {p}: {e}") return config ``` ```python def publish_skillhub(config, work_dir, tag, dry_run): pcfg = config["platforms"].get("skillhub", {}) if not pcfg.get("enabled", True): return command = pcfg.get("command", "skillhub publish").split() exe = shutil.which(command[0]) if command else None if not exe: log("Skipping SkillHub: CLI not found") return ``` ```python token = load_secret("SKILLHUB_TOKEN") env = dict(os.environ) if token: env["SKILLHUB_TOKEN"] = token args = command[1:] + [str(clean_dir), "--version", tag.lstrip("v")] if token: args += ["--token", token] proc = run_cmd(exe, args, work_dir, env=env) ``` ### Technical Analysis The directory being published can contain `.skill-distributor/config.json`. This untrusted, package-local file has priority over the user-global configuration and can redefine `platforms.skillhub.command`. The first token of that setting is resolved with `shutil.which()`, while all remaining tokens become process arguments. No executable allowlist, trusted-path rest ...[truncated 2056 chars]
Remediation
## Remediation Suggestions - Do not permit a skill-local configuration file to select an executable or command. - Use a fixed, trusted SkillHub executable and fixed publication subcommand. - Move any command customization to a user-owned global configuration outside the skill directory. - If customization is essential, enforce an explicit allowlist of executable names and expected subcommands. - Resolve the executable from a trusted installation path rather than accepting arbitrary `PATH` matches. - Pass a minimal environment to child processes instead of copying all of `os.environ`. - Do not place tokens in command-line arguments, where they may be exposed through process inspection; use a narrowly scoped environment variable or secure credential channel. - Require explicit user confirmation when a non-default publisher executable is selected.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/distribute.py:108
Finding
Attacker-Controlled Repository Target Is Force-Pushed Without Destination Confirmation## Vulnerability Details **File Location**: `scripts/distribute.py:108-118, 268-281, 307-315, 458-478` **Vulnerability Type**: Untrusted destination selection leading to destructive remote repository overwrite **Risk Level**: High ### Complete Code Snippet ```python def load_config(skill_dir): """Configuration priority: skill-local config, user-global config, defaults.""" config = json.loads(json.dumps(DEFAULT_CONFIG)) local_cfg = Path(skill_dir) / ".skill-distributor" / "config.json" candidates = [] if local_cfg.exists(): candidates.append(local_cfg) if HOME_CONFIG_DIR.joinpath("config.json").exists(): candidates.append(HOME_CONFIG_DIR.joinpath("config.json")) for p in candidates: try: merge_config(config, json.loads(p.read_text(encoding="utf-8-sig"))) except Exception as e: log(f"Warning: failed to read configuration {p}: {e}") return config ``` ```python def publish_github(config, token, work_dir, version, dry_run, force): owner = config["github"].get("owner", "") repo = config["github"].get("repo", "") branch = config["github"].get("default_branch", "main") private = config["github"].get("private", True) if not owner or not repo: if dry_run: log("Warning: github.owner/repo are not configured") else: raise SystemExit("Error: github.owner and github.repo are required") ``` ```python remote_url = f"https://{token}@github.com/{owner}/{repo}.git" clean_url = f"https://github.com/{owner}/{repo}.git" run(["git", "init", "-b", branch], cwd=work_dir) run(["git", "add", "-A"], cwd=work_dir) ensure_git_identity(config, work_dir) run(["git", "commit", "-m", f"release {tag}"], cwd=work_dir) run(["git", "remote", "remove", "origin"], cwd=work_dir, check=False) run(["git", "remote", "add", "origin", remote_url], cwd=work_dir) run(["git", "push", "--force", "-u", "origin", branch], cwd=w ...[truncated 2817 chars]
Remediation
## Remediation Suggestions - Do not accept GitHub destination fields from configuration stored inside the skill being published. - Bind `owner`, `repo`, and `default_branch` to trusted user-global configuration or explicit CLI arguments. - Before modifying an existing repository, display the fully resolved destination and require destination-specific confirmation. - Replace unconditional force pushes with ordinary fast-forward pushes by default. - If branch replacement is necessary, require a separate explicit option such as `--force-branch`. - Verify the authenticated GitHub identity and enforce an allowlist of permitted repository destinations. - Consider requiring a repository marker or immutable repository identifier so a package cannot silently redirect publication. - Encourage server-side protected branches and token scopes restricted to the intended repository. - Extend preflight validation to reject publication-control configuration embedded in untrusted skill packages.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (56)

Credential Access

High
Category
Privilege Escalation
Content
## GitHub(单一事实源,必配)

- 密钥: `GITHUB_TOKEN`(Personal Access Token,需 `repo` 权限)。
- 仓库不存在时 distribute.py 自动创建(默认 private,可在 config.json `github.private` 修改)。
- 命名空间即 GitHub 身份:`github.com/<owner>/<repo>`。
- 推送后 remote URL 会移除 token,避免凭证残留 `.git/config`。
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
## 镜像站(Gitee / GitCode / GitLab)

- 方式一(平台内置): Gitee / GitCode 提供"从 GitHub 导入并自动同步"功能,开启一次即可。
- 方式二(脚本): 在 `config.json` 的 `mirrors.repos` 或 `MIRROR_URLS` 中列出含令牌的 git URL,由 distribute.py 显式执行 `git push --mirror`(仅发布时触发,无 CI 自动同步)。
- 注意: 镜像仓库是备份通道,不承担发布审核。

## 聚合目录(自动收录,无需主动上架)
Confidence
70% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
## 镜像站(Gitee / GitCode / GitLab)

- 方式一(平台内置): Gitee / GitCode 提供"从 GitHub 导入并自动同步"功能,开启一次即可。
- 方式二(脚本): 在 `config.json` 的 `mirrors.repos` 或 `MIRROR_URLS` 中列出含令牌的 git URL,由 distribute.py 显式执行 `git push --mirror`(仅发布时触发,无 CI 自动同步)。
- 注意: 镜像仓库是备份通道,不承担发布审核。

## 聚合目录(自动收录,无需主动上架)
Confidence
70% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Credential Access

High
Category
Privilege Escalation
Content
import os
        if os.environ.get(v):
            results.append(f"✓ 环境变量 {v} 已设置")
    if HOME_CONFIG_DIR.joinpath("secrets.json").exists():
        try:
            data = json.loads(HOME_CONFIG_DIR.joinpath("secrets.json").read_text(encoding="utf-8"))
            keys = [k for k, val in data.items() if val]
Confidence
92% confidence
Finding
Accessing `secrets.json` under the user's home configuration directory is credential-adjacent behavior because it inspects a file explicitly intended to store secrets. Even though this line only checks existence, in the context of the surrounding function it contributes to discovery of stored credentials and can expose whether a secrets store is present.

Credential Access

High
Category
Privilege Escalation
Content
results.append(f"✓ 环境变量 {v} 已设置")
    if HOME_CONFIG_DIR.joinpath("secrets.json").exists():
        try:
            data = json.loads(HOME_CONFIG_DIR.joinpath("secrets.json").read_text(encoding="utf-8"))
            keys = [k for k, val in data.items() if val]
            results.append(f"✓ secrets.json 存在,含密钥: {', '.join(keys) if keys else '无'}")
        except Exception:
Confidence
96% confidence
Finding
This line reads and parses the contents of `secrets.json`, which is direct access to stored credentials metadata. Although the code does not print secret values, parsing the file enables enumeration of configured secret entries and expands the blast radius if this script is reused, modified, or its output is collected centrally.

Credential Access

High
Category
Privilege Escalation
Content
try:
            data = json.loads(HOME_CONFIG_DIR.joinpath("secrets.json").read_text(encoding="utf-8"))
            keys = [k for k, val in data.items() if val]
            results.append(f"✓ secrets.json 存在,含密钥: {', '.join(keys) if keys else '无'}")
        except Exception:
            results.append("⚠ secrets.json 存在但解析失败")
    if not any("已设置" in r or "存在" in r for r in results):
Confidence
97% confidence
Finding
Printing the names of populated secret keys from `secrets.json` discloses which credentials and integrations are configured on the host. This is sensitive reconnaissance data that can reveal account relationships, token types, or available publishing targets to anyone who can view the output or logs.

Credential Access

High
Category
Privilege Escalation
Content
用法:
  python distribute.py --skill <技能目录> [--tag v1.0.0] [--dry-run] [--skip github,clawhub,mcp,skillhub,mirror,preflight] [--skip-preflight] [--force]

密钥来源(优先级): 环境变量 > ~/.skill-distributor/secrets.json
支持的环境变量: GITHUB_TOKEN, CLAWHUB_AUTH, SKILLHUB_TOKEN, MIRROR_URLS
"""
import argparse
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
用法:
  python distribute.py --skill <技能目录> [--tag v1.0.0] [--dry-run] [--skip github,clawhub,mcp,skillhub,mirror,preflight] [--skip-preflight] [--force]

密钥来源(优先级): 环境变量 > ~/.skill-distributor/secrets.json
支持的环境变量: GITHUB_TOKEN, CLAWHUB_AUTH, SKILLHUB_TOKEN, MIRROR_URLS
"""
import argparse
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
用法:
  python distribute.py --skill <技能目录> [--tag v1.0.0] [--dry-run] [--skip github,clawhub,mcp,skillhub,mirror,preflight] [--skip-preflight] [--force]

密钥来源(优先级): 环境变量 > ~/.skill-distributor/secrets.json
支持的环境变量: GITHUB_TOKEN, CLAWHUB_AUTH, SKILLHUB_TOKEN, MIRROR_URLS
"""
import argparse
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
用法:
  python distribute.py --skill <技能目录> [--tag v1.0.0] [--dry-run] [--skip github,clawhub,mcp,skillhub,mirror,preflight] [--skip-preflight] [--force]

密钥来源(优先级): 环境变量 > ~/.skill-distributor/secrets.json
支持的环境变量: GITHUB_TOKEN, CLAWHUB_AUTH, SKILLHUB_TOKEN, MIRROR_URLS
"""
import argparse
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
用法:
  python distribute.py --skill <技能目录> [--tag v1.0.0] [--dry-run] [--skip github,clawhub,mcp,skillhub,mirror,preflight] [--skip-preflight] [--force]

密钥来源(优先级): 环境变量 > ~/.skill-distributor/secrets.json
支持的环境变量: GITHUB_TOKEN, CLAWHUB_AUTH, SKILLHUB_TOKEN, MIRROR_URLS
"""
import argparse
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
用法:
  python distribute.py --skill <技能目录> [--tag v1.0.0] [--dry-run] [--skip github,clawhub,mcp,skillhub,mirror,preflight] [--skip-preflight] [--force]

密钥来源(优先级): 环境变量 > ~/.skill-distributor/secrets.json
支持的环境变量: GITHUB_TOKEN, CLAWHUB_AUTH, SKILLHUB_TOKEN, MIRROR_URLS
"""
import argparse
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
if os.name == "nt":
        cmd = (f'start "" /b cmd /c "timeout /t {int(delay_sec)} /nobreak >nul & '
               f'rd /s /q \\"{path}\\""')
        subprocess.Popen(cmd, shell=True, creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0))
        return False
    import atexit
    atexit.register(lambda: shutil.rmtree(path, ignore_errors=True))
Confidence
95% confidence
Finding
This duplicate finding points to the same shell-based delayed cleanup issue: command construction and execution through cmd.exe with embedded path data. The publish tool runs in a sensitive environment with repository and platform tokens, so successful injection could lead to code execution and credential compromise.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
if os.name == "nt":
        cmd = (f'start "" /b cmd /c "timeout /t {int(delay_sec)} /nobreak >nul & '
               f'rd /s /q \\"{path}\\""')
        subprocess.Popen(cmd, shell=True, creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0))
        return False
    import atexit
    atexit.register(lambda: shutil.rmtree(path, ignore_errors=True))
Confidence
95% confidence
Finding
This duplicate finding points to the same shell-based delayed cleanup issue: command construction and execution through cmd.exe with embedded path data. The publish tool runs in a sensitive environment with repository and platform tokens, so successful injection could lead to code execution and credential compromise.

Credential Access

High
Category
Privilege Escalation
Content
if item.name in EXCLUDE_DIRS or item.name in EXCLUDE_FILES:
            continue
        if item.name.startswith(".env"):
            # .env / .env.local / .env.prod 等密钥载体一律不打包(含 .env.example,保守)
            continue
        target = dest / item.name
        if item.is_dir():
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
if item.name in EXCLUDE_DIRS or item.name in EXCLUDE_FILES:
            continue
        if item.name.startswith(".env"):
            # .env / .env.local / .env.prod 等密钥载体一律不打包(含 .env.example,保守)
            continue
        target = dest / item.name
        if item.is_dir():
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
# ---------- GitHub ----------

def setup_proxy(config):
    """从 config.proxy 或环境变量设置代理(供 GitHub API 与 git push 使用)"""
    proxy = (config.get("proxy", "") or os.environ.get("HTTPS_PROXY")
             or os.environ.get("https_proxy") or "")
    if proxy:
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
# ---------- GitHub ----------

def setup_proxy(config):
    """从 config.proxy 或环境变量设置代理(供 GitHub API 与 git push 使用)"""
    proxy = (config.get("proxy", "") or os.environ.get("HTTPS_PROXY")
             or os.environ.get("https_proxy") or "")
    if proxy:
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
"""执行命令;Windows 下对 .cmd/.bat/.ps1 包装经 cmd.exe /c 运行并正确处理带空格路径"""
    if exe.lower().endswith((".cmd", ".bat", ".ps1")):
        quoted = " ".join(f'"{a}"' if any(c.isspace() for c in a) else a for a in args)
        return subprocess.run(f'"{exe}" {quoted}', cwd=cwd, capture_output=True,
                              text=True, env=env or os.environ, shell=True)
    return subprocess.run([exe] + args, cwd=cwd, capture_output=True, text=True, env=env or os.environ)
Confidence
97% confidence
Finding
Using shell=True with a command string assembled from exe and args opens a command injection surface. In this script, command and arguments can come from configuration, discovered executables, and release metadata such as tags, so a malicious or compromised config/CLI path could trigger arbitrary commands during publishing.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
if dry_run:
        log(f"[dry-run] clawhub skill publish . --version {tag.lstrip('v')}")
        return
    env = dict(os.environ)
    if auth:
        env["CLAWHUB_AUTH"] = auth
    args = ["skill", "publish", ".", "--version", tag.lstrip("v"), "--changelog", f"Release {tag}"]
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
if dry_run:
        log(f"[dry-run] clawhub skill publish . --version {tag.lstrip('v')}")
        return
    env = dict(os.environ)
    if auth:
        env["CLAWHUB_AUTH"] = auth
    args = ["skill", "publish", ".", "--version", tag.lstrip("v"), "--changelog", f"Release {tag}"]
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Credential Access

High
Category
Privilege Escalation
Content
# 不应被打包进发布副本的目录/文件(与 distribute.py EXCLUDE 保持一致)
EXCLUDE_DIRS = {".git", "__pycache__", ".skill-distributor", ".workbuddy", "node_modules"}
# 密钥载体文件:不扫描内容(存在即 P0 阻断,见 check_secrets)
EXCLUDE_FILES = {"secrets.json", ".env", ".npmrc", ".pypirc", ".netrc"}
# 密钥文件变体(发现即 P0:.env.local / .env.prod 等携带真实密钥)
ENV_FILE_BLOCK = [".env.local", ".env.prod", ".env.production", ".env.dev", ".env.development",
                  ".env.test", ".env.staging", ".env.uat", ".env.qa"]
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# 不应被打包进发布副本的目录/文件(与 distribute.py EXCLUDE 保持一致)
EXCLUDE_DIRS = {".git", "__pycache__", ".skill-distributor", ".workbuddy", "node_modules"}
# 密钥载体文件:不扫描内容(存在即 P0 阻断,见 check_secrets)
EXCLUDE_FILES = {"secrets.json", ".env", ".npmrc", ".pypirc", ".netrc"}
# 密钥文件变体(发现即 P0:.env.local / .env.prod 等携带真实密钥)
ENV_FILE_BLOCK = [".env.local", ".env.prod", ".env.production", ".env.dev", ".env.development",
                  ".env.test", ".env.staging", ".env.uat", ".env.qa"]
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# 不应被打包进发布副本的目录/文件(与 distribute.py EXCLUDE 保持一致)
EXCLUDE_DIRS = {".git", "__pycache__", ".skill-distributor", ".workbuddy", "node_modules"}
# 密钥载体文件:不扫描内容(存在即 P0 阻断,见 check_secrets)
EXCLUDE_FILES = {"secrets.json", ".env", ".npmrc", ".pypirc", ".netrc"}
# 密钥文件变体(发现即 P0:.env.local / .env.prod 等携带真实密钥)
ENV_FILE_BLOCK = [".env.local", ".env.prod", ".env.production", ".env.dev", ".env.development",
                  ".env.test", ".env.staging", ".env.uat", ".env.qa"]
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# 不应被打包进发布副本的目录/文件(与 distribute.py EXCLUDE 保持一致)
EXCLUDE_DIRS = {".git", "__pycache__", ".skill-distributor", ".workbuddy", "node_modules"}
# 密钥载体文件:不扫描内容(存在即 P0 阻断,见 check_secrets)
EXCLUDE_FILES = {"secrets.json", ".env", ".npmrc", ".pypirc", ".netrc"}
# 密钥文件变体(发现即 P0:.env.local / .env.prod 等携带真实密钥)
ENV_FILE_BLOCK = [".env.local", ".env.prod", ".env.production", ".env.dev", ".env.development",
                  ".env.test", ".env.staging", ".env.uat", ".env.qa"]
Confidence
80% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Static analysis

No suspicious patterns detected.