Back to skill

Security audit

Ascii Art Creator

Security checks for vulnerabilities and agentic risk

Overview

The packaged ASCII-art tool mostly matches its stated purpose, but its install instructions tell users to download and run mutable remote Python code that may differ from the reviewed artifact.

Review before installing. Prefer the packaged `artifact/ascii_art.py` or a pinned, checksummed release instead of the documented `curl` command from `main`. Avoid processing untrusted or extremely large image files without resource limits, and run the CI verifier only in a sandbox if checking third-party folders.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T03 · Remote Payload Retrieval and Execution

Error
Location
README.md:34
Finding

Unpinned Remote Payload Download Can Replace the Audited Executable

Content
View full analysis
Remediation
View remediation
/ascii_art.py ``` 3. Publish and verify a SHA-256 digest before execution: ```bash echo " ascii_art.py" | sha256sum --check - ``` 4. Prefer signed, versioned releases with documented signature verification. 5. Use `curl --fail --show-error --location` and write to an explicit destination to avoid silently accepting HTTP failures. 6. Keep `README.md` and `SKILL.md` synchronized so neither document recommends the mutable download path. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
ascii_art.py:451
Finding

Unbounded Image Dimensions and Output Width Permit Resource Exhaustion

Content
View full analysis
str: """Parse PPM/PGM/PBM format using stdlib only.""" from pathlib import Path # Read header magic = f.readline().strip().decode() while True: line = f.readline().strip().decode() if not line.startswith("#"): dimensions = line break w, h = map(int, dimensions.split()) maxval_line = f.readline().strip().decode() maxval = int(maxval_line) if maxval_line else 255 if magic == "P6": # PPM (color) data = f.read() pixels = [] for y in range(h): for x in range(w): idx = (y * w + x) * 3 r, g, b = data[idx], data[idx+1], data[idx+2] pixels.append((r, g, b)) elif magic == "P5": # PGM (grayscale) data = f.read() pixels = [(data[y * w + x], data[y * w + x], data[y * w + x]) for y in range(h) for x in range(w)] elif magic == "P2": # PGM ascii vals = list(map(int, f.read().split())) pixels = [(vals[y * w + x], vals[y * w + x], vals[y * w + x]) for y in range(h) for x in range(w)] elif magic == "P3": # PPM ascii vals = list(map(int, f.read().split())) pixels = [(vals[(y * w + x) * 3], vals[(y * w + x) * 3 + 1], vals[(y * w + x) * 3 + 2]) for y in range(h) for x in range(w)] else: return "⚠ Unsupported NetPBM format" # Resize and convert aspect = h / w out_h = int(aspect * width * 0.45) result = [] for y in range(out_h): row = [] for x in range(width): sx = int(x * w / width) sy = int(y * h / out_h) r, g, b = pixels[sy * w + sx] gray = ...[truncated 2550 chars]
Remediation
View remediation
int: width = int(value) if not 1 <= width <= 500: raise argparse.ArgumentTypeError("width must be between 1 and 500") return width ``` 2. Validate image dimensions before multiplication or allocation: - Require `w > 0` and `h > 0`. - Set maximum width, height, and total pixel count. - Use overflow-safe checks such as `w > MAX_PIXELS // h`. 3. Validate `maxval` against the supported NetPBM range and correctly account for sample sizes greater than one byte. 4. Check the exact expected payload or sample count before indexing: - P6 requires sufficient RGB samples. - P5 requires sufficient grayscale samples. - P2 and P3 require the corresponding number of numeric values. 5. Reject malformed headers, unterminated comment/header sections, truncated data, and unexpected trailing structures with clear errors. 6. Avoid unnecessary duplicate in-memory representations. Process or sample source pixels incrementally where practical. 7. Limit output dimensions and total output characters independently of source dimensions. 8. Catch expected parsing exceptions and return a controlled error without exposing a traceback or leaving the process in a partially completed state. 9. Run image processing in a resource-constrained subprocess or container when files may originate from untrusted users. ]]>
Vulnerability Patterns
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (9)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description says the skill generates various ASCII-art style outputs, but the actual code performs repository/package validation for CI. It does not generate banners, boxes, cowsay art, tables, or image-to-ASCII. Its primary purpose is materially different: checking SKILL.md frontmatter and package structure on the filesystem. This is a clear description-behavior mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared purpose is an ASCII/text-art generation skill, but the actual code has a completely different primary function: it validates product packaging and quality gates in CI. It does not generate banners, boxes, cowsay art, tables, fonts, or image-to-ASCII output. Instead, it inspects local project files, parses SKILL.md, compiles Python files, runs self-tests, scans for secrets, and invokes ci_check.py. This is a clear material mismatch in behavior and capabilities.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding

The skill advertises shell/file-based usage (curl, python ascii_art.py ..., CI execution) but does not declare any permissions or allowed-tools scope. In an agent ecosystem, undeclared shell and file access weakens least-privilege controls and can let a seemingly harmless text-art skill invoke broader local capabilities than users expect.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The top-level documentation explicitly states that all features are implemented with Python standard library only. However, the image conversion path conditionally imports PIL at L362 and fitz at L370, which are external dependencies, so the documentation actively contradicts actual behavior.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
94% confidence
Finding

This helper executes an arbitrary Python file from the target product directory to check for a self-test mode. In a CI or verification context, running untrusted repository code is itself dangerous because a malicious skill can perform arbitrary code execution, exfiltrate environment secrets, modify the workspace, or pivot to the host before any trust decision is made.

Content

Scanner excerpt · ci/verify_product.py (reported line 37)May include surrounding context.

python
def has_self_test(fp):
    r = subprocess.run([sys.executable, fp, "self-test"], capture_output=True, text=True, timeout=30)
    return r.returncode == 0 and "PASS" in r.stdout

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · ci/verify_product.py (reported line 67)May include surrounding context.

python
all_ok &= axis(False, "frontmatter (no SKILL.md)")
        all_ok &= axis(False, "docs (no SKILL.md)")
    # 3 compiles
    comp_ok = all(subprocess.run([sys.executable, "-m", "py_compile", fp],
                                capture_output=True).returncode == 0 for fp in files)
    all_ok &= axis(comp_ok, "compiles (py_compile all .py)")
    # 4 self-test (any of 3 valid forms)

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
95% confidence
Finding

This code executes any test_*.py or *_test.py file found in the target folder. Because those files come from the untrusted product being verified, an attacker can place malicious code in a test file and achieve arbitrary code execution during CI verification, potentially stealing tokens, tampering with artifacts, or compromising the runner.

Content

Scanner excerpt · ci/verify_product.py (reported line 79)May include surrounding context.

python
tests = [os.path.join(folder, f) for f in os.listdir(folder)
                 if (f.startswith("test_") or f.endswith("_test.py")) and f.endswith(".py")]
        if tests:
            st_ok = all(subprocess.run([sys.executable, t], capture_output=True, text=True,
                                      timeout=30).returncode == 0 for t in tests)
            st_note = f"{len(tests)} test_*.py"
        elif re.search(r"(?im)^test\s*:", skill_txt):

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · ci/verify_product.py (reported line 92)May include surrounding context.

python
# 7 deploy-ready
    ci = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ci_check.py")
    if os.path.isfile(ci) and os.path.isfile(skill):
        r = subprocess.run([sys.executable, ci], cwd=folder, capture_output=True, text=True)
        all_ok &= axis(r.returncode == 0, "deploy-ready (ci/ci_check.py PASS)")
    else:
        all_ok &= axis(True, "deploy-ready (skipped)")

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

Line L084 explicitly states 'Documentation in English,' which is a natural-language locale constraint. The README does not present this as an opt-in choice or explain why English-only documentation is necessary for a region-specific or compliance-bound use case.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.