Back to skill

Security audit

Execution Verifier

Security checks for vulnerabilities and agentic risk

Overview

The skill’s basic progress checker is reasonable, but its closed-loop mode can automatically run powerful commands without enough safeguards.

Review this skill before installing. The passive verifier is relatively narrow, but the closed-loop mode should only be used if you intentionally want a scheduled process that can automatically run work commands. Avoid --force, do not pass untrusted text into --verify-cmd or --execute-cmd, and restrict any executor to a fixed, low-privilege command you are comfortable running repeatedly.

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 (1)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/verify_execute_verify.py:9
Finding
Arbitrary Shell Command Execution with Fail-Open Triggering## Vulnerability Details **File Location**: `scripts/verify_execute_verify.py:9-10, 24-25, 31-38` **Vulnerability Type**: OS command injection and unsafe failure handling **Risk Level**: High ### Vulnerable Code ```python def run(cmd: str): p = subprocess.run(cmd, shell=True, capture_output=True, text=True) return { "cmd": cmd, "code": p.returncode, "stdout": p.stdout.strip(), "stderr": p.stderr.strip(), } ``` ```python ap.add_argument("--verify-cmd", required=True, help="command that outputs verifier JSON") ap.add_argument("--execute-cmd", required=True, help="executor command to run when no progress") ``` ```python before_raw = run(args.verify_cmd) before = parse_json(before_raw["stdout"]) or {"progress_detected": False, "parse_error": True} triggered = False exec_raw = None if not before.get("progress_detected", False): triggered = True exec_raw = run(args.execute_cmd) time.sleep(args.sleep_sec) ``` ### Technical Analysis The script accepts `--verify-cmd` and `--execute-cmd` as command strings and passes both directly to `subprocess.run()` with `shell=True`. Consequently, shell metacharacters, command separators, redirections, pipelines, and command substitutions embedded in either argument are interpreted by the operating-system shell. The execution decision also fails open. If the verifier produces malformed JSON, no output, or output that cannot be decoded, `parse_json()` returns `None`. The fallback object sets `progress_detected` to `False`, which automatically enters the executor branch. The script does not require the verifier process to return a successful exit status before trusting its output, and the final result reports `"ok": true` regardless of subprocess failures. This is exploitable when an untrusted user, automation component, configuration source, or agent-generated value can influence either command argument. Direct command-line access already permits intentional command exe ...[truncated 2100 chars]
Remediation
## Remediation Suggestions 1. **Eliminate shell interpretation.** Accept commands as argument arrays and invoke them with `shell=False`: ```python def run(argv: list[str]): p = subprocess.run( argv, shell=False, capture_output=True, text=True, check=False, ) ``` 2. **Prefer dedicated structured options.** Instead of accepting an unrestricted verifier command, call the known verifier script directly and expose only validated parameters such as project directory and time window. 3. **Use an allowlist for executor operations.** If multiple executors must be supported, map fixed identifiers to predefined argument arrays. Do not concatenate user-controlled values into command strings. 4. **Fail closed on verifier errors.** Abort without invoking the executor if: - The verifier returns a nonzero exit code. - Standard output is empty or malformed. - The decoded value is not a JSON object. - `progress_detected` is absent or is not a Boolean. 5. **Return accurate status information.** Set the top-level `ok` field to `false` when verification, execution, parsing, or re-verification fails. 6. **Apply execution safeguards.** Add subprocess timeouts, restrict the working directory and environment, run under a least-privileged service account, and avoid exposing unnecessary secrets to child processes. 7. **If compatibility requires parsing a trusted command string**, parse it once into an argument list with `shlex.split()` and still use `shell=False`. This is not a substitute for validation when the command source is untrusted.
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (10)

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding
The skill claims to enforce a safe progress-verification workflow, but its documented behavior includes executing arbitrary shell commands supplied as arguments and relying on external command strings rather than independently enforcing the described checks. This mismatch is dangerous because operators may trust the skill as a passive verifier while it can actually trigger unreviewed command execution, increasing the chance of unintended or unsafe actions.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
def run(cmd: str):
    p = subprocess.run(cmd, shell=True, capture_output=True, text=True)
    return {
        "cmd": cmd,
        "code": p.returncode,
Confidence
99% confidence
Finding
This is a classic tool-parameter abuse issue: operational behavior is delegated to externally supplied command strings, which are then executed by a shell. The skill context makes this more dangerous, not less, because the purpose of the script is to trigger execution automatically when 'no progress' is detected, reducing human scrutiny and increasing the chance of unsafe or attacker-influenced command execution in an automated loop.

Lp3

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

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The strict report format requires responses using Chinese labels and wording such as `已完成`, `进行中`, and `下一步+ETA`. This imposes a specific language on users without opt-in or explanation, which is a natural-language policy violation under the stated rules.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
--window-min 30
```

## Closed-loop mode (verify → auto-execute → re-verify)

Use built-in script:
Confidence
90% confidence
Finding
The skill explicitly promotes autonomous decision-making through an auto-execute path that decides to run commands based on verifier output. In a security-sensitive context, unattended execution increases risk because it reduces human review and can amplify mistakes, especially when paired with shell commands and forceful task triggering.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The closed-loop mode automatically triggers an execution command when progress is not detected, without any prominent warning, confirmation, or constraint on what may be executed. This is dangerous because a user or downstream agent may invoke what appears to be a verification routine and unexpectedly cause real system changes or privileged task execution.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The documented executor example uses a force flag, which implies bypassing normal safeguards, yet the skill provides no warning about the operational or security consequences. In a verification-themed skill, this is especially risky because it normalizes forced execution in response to missing progress, potentially causing unsafe or irreversible actions under misleadingly routine conditions.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The tool takes shell commands directly from CLI arguments and executes them without any confirmation, safety prompt, or validation. This creates a command-injection and misuse surface where a caller may accidentally or maliciously supply destructive commands, and the script will execute them as part of its normal workflow.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run(cmd: str):
    p = subprocess.run(cmd, shell=True, capture_output=True, text=True)
    return {
        "cmd": cmd,
        "code": p.returncode,
Confidence
98% confidence
Finding
The script executes arbitrary strings from the CLI via subprocess.run(..., shell=True). Because both --verify-cmd and --execute-cmd are attacker-controllable inputs at invocation time, shell metacharacters, command chaining, and environment expansion can lead to unintended command execution beyond the apparent command name. In a skill meant to orchestrate other commands, this is especially dangerous because the behavior is normalized and likely to be used with high-trust automation contexts.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def last_commit_age_min(repo_dir: Path):
    try:
        ts = subprocess.check_output(
            ["git", "-C", str(repo_dir), "log", "-1", "--format=%ct"],
            text=True,
            stderr=subprocess.DEVNULL,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Static analysis

No suspicious patterns detected.