Back to skill

Security audit

API Changelog Generator

Security checks for vulnerabilities and agentic risk

Overview

The skill has a legitimate API changelog purpose, but its diff command can run unintended local code if given a crafted file path.

Review this skill before installing or running it. Its API changelog workflow is understandable, but the diff command should be fixed to pass file paths through argv or another data-only channel before using it on untrusted filenames or specs. Also treat its breaking-change output as incomplete for auth changes, nested schemas, and $ref-heavy specs.

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
SKILL.md:19
Finding

Arbitrary Python Code Execution Through Unsafe File-Path Interpolation

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, lines 19–20 and 36–37
Vulnerability Type: Python code injection caused by unsafe shell-variable interpolation
Risk Level: High

Vulnerable Code

bash
OLD_SPEC="${1:?Usage: diff <old-spec> <new-spec>}"
NEW_SPEC="${2:?Usage: diff <old-spec> <new-spec>}"

python3 -c "
# ...
old = load_spec('$OLD_SPEC')
new = load_spec('$NEW_SPEC')
# ...
" 2>/dev/null

Technical Analysis

The first and second command-line arguments are assigned to OLD_SPEC and NEW_SPEC, then interpolated directly into the Python source passed to python3 -c.

Shell quoting does not make these values safe Python string literals. An argument containing a single quote can terminate the Python string, inject Python expressions or statements, and comment out the remaining source on that line. The injected code executes before the OpenAPI comparison proceeds.

This is a source-code injection vulnerability rather than ordinary shell command injection: the shell constructs a Python program containing attacker-controlled text, and the Python interpreter executes that text. Redirecting standard error to /dev/null may also conceal syntax errors or evidence of unsuccessful exploitation.

Attack Path

  1. An attacker creates an API specification or filename containing Python syntax, or persuades the user or agent to invoke diff using such a path.
  2. The malicious path is received as $1 or $2.
  3. The shell substitutes it into load_spec('$OLD_SPEC') or load_spec('$NEW_SPEC').
  4. A quote in the path escapes the intended Python string literal.
  5. The Python interpreter executes the injected code with the privileges of the user or agent running the skill.

For example, a path shaped like the following demonstrates the injection structure:

text
x');__import__('os').system('id');#

It would transform the affected statement into code equivalent to:

python
old = load_spec('x');__import__('os').system('id');#')

...[truncated 825 chars]
Remediation
View remediation

Remediation Suggestions

Do not interpolate file paths or other untrusted values into executable Python source. Pass them as distinct command-line arguments and read them through sys.argv.

A hardened structure is:

bash
OLD_SPEC="${1:?Usage: diff <old-spec> <new-spec>}"
NEW_SPEC="${2:?Usage: diff <old-spec> <new-spec>}"

python3 - "$OLD_SPEC" "$NEW_SPEC" <<'PY'
import json
import sys

def load_spec(path):
    with open(path, encoding="utf-8") as f:
        if path.endswith((".yml", ".yaml")):
            try:
                import yaml
                return yaml.safe_load(f)
            except ImportError:
                print(
                    "Warning: PyYAML not installed, trying JSON parse",
                    file=sys.stderr,
                )
                return json.load(f)
        return json.load(f)

old = load_spec(sys.argv[1])
new = load_spec(sys.argv[2])

# Perform the comparison here.
PY

Additional hardening measures:

  1. Keep the heredoc delimiter quoted (<<'PY') so the shell cannot expand variables or command substitutions inside the Python program.
  2. Pass every external value through sys.argv, standard input, or another data-only channel.
  3. Never construct Python source, shell commands, or expressions by concatenating user-controlled filenames.
  4. Validate that both paths refer to expected regular files and handle symbolic links according to the intended trust boundary.
  5. Report parsing failures explicitly rather than suppressing all standard-error output with 2>/dev/null.
  6. Add regression tests using paths containing single quotes, double quotes, spaces, semicolons, command substitutions, newlines, and Python syntax to verify that filenames remain data rather than executable code.
Vulnerability Patterns
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (4)

Tool Parameter Abuse

High
Category
Tool Misuse
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).

Content

Scanner excerpt · SKILL.md (reported line 159)May include surrounding context.

md
- `GET /api/v2/webhooks/{id}` — Get webhook details

### Breaking Changes
- `DELETE /api/v1/legacy-auth` — Removed (use `/api/v2/auth` instead)
- `POST /api/v2/users` — `email` parameter is now required
- Schema `UserResponse` — removed `legacy_id` property

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The breaking section claims detection of response schema type changes and changed authentication requirements. In the only implemented comparison code, response handling at L96-L102 checks only added or removed response status codes, and there is no logic for auth/security scheme comparison anywhere in the file. This overstates implemented breaking-change analysis.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The note at L257 states that schema diffing handles nested objects and $ref resolution. However, the implemented diff logic at L123-L141 only inspects direct property keys and required arrays on each schema object, with no traversal of nested structures and no $ref dereferencing. This is an active contradiction between documented capability and actual behavior.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

In this markdown skill description, the track command explicitly maintains and updates API-CHANGELOG.md, which is a user-data-affecting file write. The surrounding documentation explains what the command does but does not include any caution about modifying an existing file or recommend review/backup before updating it.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.