Back to skill

Security audit

Skill Forge

Security checks across malware telemetry and agentic risk

Overview

This is a powerful but coherent skill-building tool with disclosed local file, install, scheduling, and Telegram approval behavior, and I found no hidden exfiltration or automatic destructive install path.

Install this only if you want an agent to generate and update skills, read OpenClaw learning/feedback files, and optionally use Telegram plus a macOS schedule. Start with plan modes, protect the Telegram bot token or approval secret, review generated candidates before approval, and choose output directories that do not contain unrelated work.

SkillSpector

By NVIDIA
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (80)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_json(command: list[str], check: bool = True) -> dict:
    completed = subprocess.run(command, check=check, text=True, capture_output=True)
    if not completed.stdout.strip():
        return {
            "status": "no-output",
Confidence
70% confidence
Finding
completed = subprocess.run(command, check=check, text=True, capture_output=True)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_text(command: list[str]) -> str:
    completed = subprocess.run(command, check=True, text=True, capture_output=True)
    return completed.stdout.strip()
Confidence
70% confidence
Finding
completed = subprocess.run(command, check=True, text=True, capture_output=True)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_eval(skill_dir: str, cases: str, skill: str, min_score: int) -> dict:
    completed = subprocess.run(
        [
            sys.executable,
            str(BASE_DIR / "run_replay_eval.py"),
Confidence
70% confidence
Finding
completed = subprocess.run( [ sys.executable, str(BASE_DIR / "run_replay_eval.py"), "--skill-dir", skill_dir, "--cases",

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_child(command: list[str], json_mode: bool = False, check: bool = False) -> int:
    completed = subprocess.run(command, text=True, capture_output=True)
    if json_mode:
        payload = {
            "status": "ok" if completed.returncode == 0 else "failed",
Confidence
70% confidence
Finding
completed = subprocess.run(command, text=True, capture_output=True)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def git_status() -> dict:
    if not (REPO_DIR / ".git").exists():
        return {"available": False, "detail": "not running from a git checkout"}
    head = subprocess.run(
        ["git", "rev-parse", "--short", "HEAD"],
        cwd=REPO_DIR,
        text=True,
Confidence
70% confidence
Finding
head = subprocess.run( ["git", "rev-parse", "--short", "HEAD"], cwd=REPO_DIR, text=True, capture_output=True, )

subprocess module call

Medium
Category
Dangerous Code Execution
Content
text=True,
        capture_output=True,
    )
    status = subprocess.run(
        ["git", "status", "--porcelain=v2", "--branch"],
        cwd=REPO_DIR,
        text=True,
Confidence
70% confidence
Finding
status = subprocess.run( ["git", "status", "--porcelain=v2", "--branch"], cwd=REPO_DIR, text=True, capture_output=True, )

subprocess module call

Medium
Category
Dangerous Code Execution
Content
checks = []
    release_script = REPO_DIR / "tests" / "run_release_checks.py"
    if release_script.exists():
        completed = subprocess.run([sys.executable, str(release_script)], cwd=REPO_DIR, text=True, capture_output=True)
        checks.append(
            {
                "name": "release-checks",
Confidence
70% confidence
Finding
completed = subprocess.run([sys.executable, str(release_script)], cwd=REPO_DIR, text=True, capture_output=True)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
}
        )
    else:
        completed = subprocess.run(
            [sys.executable, str(SCRIPT_DIR / "validate_skill_candidate.py"), str(SKILL_DIR), "--json"],
            text=True,
            capture_output=True,
Confidence
70% confidence
Finding
completed = subprocess.run( [sys.executable, str(SCRIPT_DIR / "validate_skill_candidate.py"), str(SKILL_DIR), "--json"], text=True, capture_output=True,

subprocess module call

Medium
Category
Dangerous Code Execution
Content
}
        )
    if not release_script.exists():
        secret_scan = subprocess.run(
            [sys.executable, str(SCRIPT_DIR / "security" / "scan_secrets.py"), "--root", str(SKILL_DIR), "--json"],
            cwd=SKILL_DIR,
            text=True,
Confidence
70% confidence
Finding
secret_scan = subprocess.run( [sys.executable, str(SCRIPT_DIR / "security" / "scan_secrets.py"), "--root", str(SKILL_DIR), "--json"], cwd=SKILL_DIR, text=Tr

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_json(command: list[str], check: bool = False) -> dict:
    completed = subprocess.run(command, text=True, capture_output=True, check=check)
    if not completed.stdout.strip():
        return {"status": "no-output", "returncode": completed.returncode, "stderr": completed.stderr.strip()}
    try:
Confidence
70% confidence
Finding
completed = subprocess.run(command, text=True, capture_output=True, check=check)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def launchctl(args: list[str]) -> dict:
    completed = subprocess.run(["launchctl", *args], text=True, capture_output=True)
    return {
        "returncode": completed.returncode,
        "stdout": completed.stdout.strip(),
Confidence
70% confidence
Finding
completed = subprocess.run(["launchctl", *args], text=True, capture_output=True)

Tainted flow: 'token' from os.getenv (line 189, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
def get_json(token: str, method: str, params: dict) -> dict:
    query = urllib.parse.urlencode(params)
    with urllib.request.urlopen(f"{api_url(token, method)}?{query}", timeout=35) as response:
        return json.loads(response.read().decode("utf-8"))
Confidence
90% confidence
Finding
with urllib.request.urlopen(f"{api_url(token, method)}?{query}", timeout=35) as response:

Tainted flow: 'request' from os.getenv (line 47, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urllib.request.urlopen(request, timeout=30) as response:
            payload = json.loads(response.read().decode("utf-8"))
    except Exception as exc:
        return {"sent": False, "status": "send-error", "error": str(exc)}
Confidence
90% confidence
Finding
with urllib.request.urlopen(request, timeout=30) as response:

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
This is a mismatch because the description covers only opportunity detection, candidate skill creation, scaffolding, and validation/proposal, but the codebase implements a broader skill lifecycle management system. In addition to forging new skills, it routes opportunities to existing skills for evolution, records feedback, performs replay-based regression checks, installs and uninstalls skills with signed approval gates, communicates with Telegram for approvals and reports, and includes nightly review/scheduling and security scan utilities. These are substantive capabilities, not mere implementation details, and they exceed the declared purpose.

Context-Inappropriate Capability

Medium
Confidence
89% confidence
Finding
The manifest describes a skill for identifying recurring capability gaps, generating candidate skills, scaffolding them, and validating them before installation. This script adds an operational approval channel by discovering Telegram configuration and reading credentials from environment variables, which is not part of that stated purpose and introduces external secret handling.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The manifest focuses on detecting repeated needs, converting them into candidate skills, scaffolding OpenClaw-compatible skills, and validating them before installation. Making outbound requests to Telegram to request and collect approval is a separate remote-control/notification capability that is not clearly justified by that description.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
This module is a generic command-execution wrapper around subprocess.run for both JSON and text output. While some local tooling may be expected for scaffolding or validation, the code exposes unrestricted process execution rather than a narrowly scoped validator/scaffolder operation, which is not justified by the manifest description alone.

Context-Inappropriate Capability

Medium
Confidence
96% confidence
Finding
The manifest describes a skill focused on analyzing repeated agent failures, proposing candidate skills, scaffolding new OpenClaw-compatible skills, and validating them before installation. This module instead searches for Telegram bot token and chat ID configuration across multiple .env files and imports them into process environment variables, which is not an obvious implementation detail of skill-forge's stated purpose.

Context-Inappropriate Capability

Medium
Confidence
82% confidence
Finding
The manifest describes analysis, skill generation, and validation workflows, but this function reads bot credentials from environment variables and posts a nightly review to Telegram. External notification delivery is not an obvious or necessary part of converting recurring needs into candidate skills or validating skills, so this is an additional capability beyond the stated purpose.

Vague Triggers

Medium
Confidence
90% confidence
Finding
The listed triggers include generic phrases such as "missing capability," "feature request," and "make a new skill," which are broad enough to overlap with ordinary discussion rather than a clear invocation intent. The file does not provide exclusions or negative examples to clarify when these cues should not activate the skill.

Natural-Language Policy Violations

Low
Confidence
79% confidence
Finding
The skill includes a concrete example string beginning with Chinese text ("触发词") and then switches to English, but the document does not explain any language-selection policy or indicate that multilingual output is user-driven. This can conflict with organizational language/locale expectations when a skill appears to impose a language choice without opt-in.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
This code deletes an existing candidate path with unlink/rmtree and then writes a copied skill tree, but provides no prompt, log message, or comment warning the user that existing output content may be removed and replaced. Because this is a code file performing potentially destructive filesystem changes, the lack of visible disclosure matches the missing-warning criterion.

Missing User Warnings

Medium
Confidence
79% confidence
Finding
The function writes raw feedback, expected output, actual output, and task fields into references/evolution-feedback.md, which may include user or system data. The script does not provide any visible warning, prompt, or logging to disclose that collected feedback content will be persisted into a new file.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
This Python code creates directories, writes `SKILL.md`, YAML, reference files, and optional scripts, then marks generated scripts executable. Although these file writes are core to a scaffold generator, the code provides no explicit user-facing disclosure at the point of execution about which files will be created or modified, beyond a final print after completion.

VirusTotal

65/65 vendors flagged this skill as clean.

View on VirusTotal

Static analysis

No suspicious patterns detected.