Back to skill

Security audit

XHS Cover Generator

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed image generator with some dependency and privacy caveats, but no evidence of hidden, destructive, persistent, or credential-seeking behavior.

Install only if you are comfortable with a skill that contacts Pollinations.ai for base-image prompts and GitHub for a CJK font download. Do not put private or proprietary text in --base-prompt, and prefer running it in a normal user context rather than with elevated privileges.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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)

T08 · Insecure Dependencies

Warning
Location
scripts/generate.py:20
Finding

Unverified Mutable Font Dependency and Unsafe Shared Cache

Content
View full analysis

Vulnerability Details

File Location: scripts/generate.py, lines 20–29
Vulnerability Type: Unverified third-party dependency stored in an unsafe shared temporary location
Risk Level: Medium

python
FONT_URL = "https://github.com/googlefonts/noto-cjk/raw/main/Sans/OTF/SimplifiedChinese/NotoSansCJKsc-Bold.otf"
FONT_CACHE = Path(tempfile.gettempdir()) / "NotoSansCJKsc-Bold.otf"
FONT_SHA256 = None  # skip hash check for now

def ensure_font():
    if FONT_CACHE.exists() and FONT_CACHE.stat().st_size > 1_000_000:
        return str(FONT_CACHE)
    print(f"Downloading CJK font to {FONT_CACHE} ...")
    subprocess.run(["curl", "-sL", FONT_URL, "-o", str(FONT_CACHE), "--max-time", "120"], check=True)
    return str(FONT_CACHE)

Technical Analysis

The font is downloaded from the mutable main branch of an external repository and is not verified against a known cryptographic digest. Although FONT_SHA256 is declared, it is set to None and never used. Consequently, changes to the remote branch or compromise of the upstream delivery path could cause the program to process content that was not present during the audit.

The cache is also stored under a predictable filename in the system-wide temporary directory. An existing cache entry is trusted solely because it exists and is larger than one megabyte. The code does not verify its digest, ownership, permissions, regular-file status, or whether it is a symbolic link. The returned file is subsequently parsed by Pillow through ImageFont.truetype() in all three cover-generation paths.

This creates a local cache-poisoning and race-condition opportunity. An attacker with write access to the shared temporary directory could place or replace the predictable file before the program runs. The download itself is written directly to the final cache path rather than securely downloaded to a private temporary file and atomically installed after verification.

Attack Path

  1. A local attacker identi ...[truncated 1798 chars]
Remediation
View remediation

Remediation Suggestions

  1. Pin the font URL to an immutable release artifact or exact repository commit rather than the mutable main branch.
  2. Define an expected SHA-256 digest in source and verify every downloaded or cached font before use. Delete and reject any file whose digest does not match.
  3. Store the cache in a user-private cache directory with restrictive permissions instead of a shared system temporary directory.
  4. If temporary storage is required, create a private directory with mode 0700 and use securely generated filenames.
  5. Reject symbolic links and non-regular files. Where supported, open files with no-follow and exclusive-creation semantics.
  6. Download into a securely created temporary file, validate HTTP success, enforce reasonable size limits, calculate the digest, and atomically rename the verified file into the cache.
  7. Add curl --fail or use a Python HTTP client that explicitly validates status codes, TLS, response size, and timeouts.
  8. Handle download and validation failures without passing partial or untrusted content to Pillow.
  9. Consider packaging the verified font with the skill if licensing and package-size constraints permit, eliminating the runtime dependency.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (7)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill documentation indicates it uses shell and network capabilities (python3, curl, Pollinations.ai, font download) but does not declare any tool scope or permission boundaries. This creates a mismatch between the apparent behavior and the declared security model, increasing the chance an agent invokes networked or shell actions without explicit review or least-privilege controls.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The trigger phrases include broad English terms like make a cover image, xhs cover, and xiaohongshu image, which can match many ordinary user requests and cause unintended auto-selection of this skill. In context, that matters because the skill can initiate shell execution and network access, so overbroad routing increases the chance of unnecessary external calls or file-generating actions without clear user intent.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
94% confidence
Finding

The script downloads a font at runtime by invoking curl on a remote GitHub URL, but it explicitly disables integrity verification by leaving FONT_SHA256 unset and performs only a size check. This creates a supply-chain risk: if the remote content is replaced, intercepted, or unexpectedly changed, untrusted font data is consumed by the skill and later parsed by PIL/FreeType.

Content

Scanner excerpt · scripts/generate.py (reported line 27)May include surrounding context.

python
if FONT_CACHE.exists() and FONT_CACHE.stat().st_size > 1_000_000:
        return str(FONT_CACHE)
    print(f"Downloading CJK font to {FONT_CACHE} ...")
    subprocess.run(["curl", "-sL", FONT_URL, "-o", str(FONT_CACHE), "--max-time", "120"], check=True)
    return str(FONT_CACHE)

# --- image generation ---

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

User-provided base_prompt text is transmitted to Pollinations.ai without an explicit warning or consent mechanism. In an agent skill context, prompts may contain sensitive business, personal, or proprietary information, so silent exfiltration to a third-party service increases privacy and data-handling risk.

Content

No source excerpt is available for this finding.

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 · scripts/generate.py (reported line 36)May include surrounding context.

python
import urllib.parse
    url = f"https://image.pollinations.ai/prompt/{urllib.parse.quote(prompt)}?width={width}&height={height}&model=flux&nologo=true"
    tmp = Path(tempfile.gettempdir()) / f"xhs_base_{hashlib.md5(prompt.encode()).hexdigest()}.jpg"
    subprocess.run(["curl", "-sL", url, "-o", str(tmp), "--max-time", "180"], check=True)
    return str(tmp)

def make_split_cover(args):

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

The usage text specifies an "english prompt for AI base image," which imposes a language constraint in the skill's natural-language instructions. The file does not indicate that other languages are supported or that English is optional, so this is a language-policy concern.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

ensure_font() automatically retrieves a font file from GitHub using curl if the cached font is missing. While there is a print statement announcing the download, the main usage/help text does not disclose that running the script may contact external services, which can surprise users in restricted or privacy-sensitive environments.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.