Back to skill

Security audit

Poster Forge

Security checks for vulnerabilities and agentic risk

Overview

This is a real poster generator, but it needs review because it sends prompts to outside services and renders unescaped user text in unsandboxed Chromium.

Review before installing. Avoid using this skill with sensitive or proprietary prompt text unless you are comfortable sending it to Pollinations.ai, and avoid feeding it untrusted HTML-like content. Prefer text or local-only modes where possible, and run it in a restricted environment if using HTML rendering.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/generate.py:119
Finding

Unescaped Template Input Enables Server-Side Request Forgery Through Headless Chromium

Content
View full analysis
...
{args.left_label}
{_chat_items(args.left_items)}
VS
{args.right_label}
{_chat_items(args.right_items)}
{args.title}
{args.subtitle}
""", ``` ```python "tutorial": f""" ...
{args.title}
{args.subtitle}
{args.code_content or ''}
{args.tagline or ''}
""", ``` ```python def _chat_items(items_str): if not items_str: return "" items = [i.strip() for i in items_str.split("|") if i.strip()] return "\n".join(f'
{item}
' for item in items) ``` ```python if not success and mode in ("html", "auto"): print(f"Trying HTML engine (Chromium)...") html = get_template(args.template, args) out_dir = os.path.dirname(os.path.abspath(args.output)) or "." tmp_png = os.path.join(out_dir, "pf_html_tmp.png") if html_render(html, args.width, args.height, tmp_png): ``` ### Technical Analysis Multiple command-line values are interpolated directly into an HTML document without contextual HTML escaping. Affected values include the title, subtitle, tagline, labels, list items, and tutorial code content. Because these values are treated as markup rather than text, an attacker can inject active HTML elements such as `img`, `iframe`, `object`, or CSS res ...[truncated 2146 chars]
Remediation
View remediation
{escape(item, quote=True)}' for item in items ) ``` 3. Add a restrictive Content Security Policy to generated templates. For templates that require no external resources, use a policy similar to: ```html ``` 4. Render templates in an isolated environment without access to loopback, private networks, cloud metadata addresses, or the public Internet. 5. Disable unnecessary browser features and external resource loading. Where feasible, replace browser rendering with a renderer that does not support network-capable active content. 6. Add regression tests using payloads containing `img`, `iframe`, `object`, CSS `url()`, and closing tags, verifying that they appear as literal poster text and do not generate network requests. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate.py:26
Finding

Predictable Temporary Files Permit Symlink-Based File Overwrite

Content
View full analysis
1_000_000: return str(path) url = FONT_URLS[weight] print(f"Downloading {weight} font...") subprocess.run(["curl", "-sL", url, "-o", str(path), "--max-time", "120"], check=True) return str(path) ``` ```python def ai_generate_base(prompt, width, height): 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"pf_ai_{hashlib.md5(prompt.encode()).hexdigest()}.jpg" result = subprocess.run(["curl", "-sL", url, "-o", str(tmp), "--max-time", "180"], capture_output=True) ``` ```python out_dir = os.path.dirname(os.path.abspath(output_path)) or "." html_file = os.path.join(out_dir, "pf_render_tmp.html") with open(html_file, "w", encoding="utf-8") as f: f.write(html_content) ``` ```python tmp = str(Path(tempfile.gettempdir()) / "pf_textbase.png") base.save(tmp) add_text_overlay(tmp, args, args.output) ``` ### Technical Analysis The skill creates temporary artifacts under deterministic names rather than using securely generated, exclusively created temporary files. Examples include: - `/tmp/pf_textbase.png` - `/tmp/pf_ai_.jpg` - `/tmp/poster-forge-fonts/...` - `pf_render_tmp.html` in the output directory - `pf_screenshot.png` and `pf_html_tmp.png` in the output directory Operations such as `curl -o`, Python's normal `open(..., "w")`, P ...[truncated 2022 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate.py:79
Finding

Headless Chromium Processes Attacker-Influenced HTML With Its Sandbox Disabled

Content
View full analysis
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
scripts/generate.py:22
Finding

Remote Font Files Are Retrieved From a Mutable Branch Without Integrity Verification

Content
View full analysis
1_000_000: return str(path) url = FONT_URLS[weight] print(f"Downloading {weight} font...") subprocess.run(["curl", "-sL", url, "-o", str(path), "--max-time", "120"], check=True) return str(path) ``` ```python def font(size, weight="bold"): from PIL import ImageFont return ImageFont.truetype(ensure_font(weight), size) ``` ### Technical Analysis The font URLs reference the mutable `main` branch of a remote GitHub repository. The downloaded files are not authenticated using a pinned digest or signature. The only local acceptance check for an existing file is that its size exceeds one megabyte. TLS protects the network connection to GitHub but does not guarantee that the mutable repository content remains identical to the version reviewed by the skill author. Repository compromise, account compromise, unintended upstream replacement, or local cache tampering could cause different font data to be consumed. The downloaded OpenType file is subsequently parsed by Pillow and its underlying font-rendering libraries. A malicious or malformed font could exploit a vulnerability in those native parsers. Even without native-code exploitation, corrupted assets could cause crashes, incorrect output, or denial of serv ...[truncated 1177 chars]
Remediation
View remediation
", "regular": "", } digest = hashlib.sha256(path.read_bytes()).hexdigest() if digest != EXPECTED_SHA256[weight]: path.unlink(missing_ok=True) raise RuntimeError("Downloaded font failed integrity verification") ``` 4. Download to a securely created temporary file, verify the digest, and only then atomically move it into a private cache. 5. Fail closed on download errors, redirects to unexpected hosts, digest mismatches, or invalid file formats. 6. Keep Pillow, FreeType, and other image/font parsing libraries current. 7. Document the pinned font version, source commit, license, and expected hashes so dependency changes undergo explicit review. ]]>
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 (8)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill advertises and documents capabilities that invoke shell commands, write files, and access external network services, but it does not declare any explicit tool scope or permission boundary. This creates a mismatch between what the agent may do and what reviewers or policy layers can enforce, increasing the risk of unintended command execution, arbitrary file writes, and data exfiltration through external requests.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The trigger phrases are broad enough to activate on many generic image-creation requests, which can cause the skill to run in contexts where the user did not intend external generation, shell execution, or file creation. Because this skill can fall back across multiple engines, overbroad activation increases the chance that sensitive prompts or content are processed by higher-risk paths without clear user consent.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The documentation states that the default mode is auto and that it tries the AI engine first, but it does not clearly warn that this may transmit user prompts to an external service (Pollinations.ai). In a skill that may be invoked for arbitrary user content, missing disclosure increases privacy and compliance risk because users and operators may unknowingly send sensitive or proprietary text off-platform.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The script downloads fonts from GitHub at runtime and stores them locally without clear notice, integrity verification, or a trust-pinning mechanism. While fonts are less dangerous than executables, this still creates supply-chain and unexpected network-write behavior risks, especially in automated agent environments where outbound access may be restricted or monitored.

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 39)May include surrounding context.

python
return str(path)
    url = FONT_URLS[weight]
    print(f"Downloading {weight} font...")
    subprocess.run(["curl", "-sL", url, "-o", str(path), "--max-time", "120"], check=True)
    return str(path)

def font(size, weight="bold"):

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

User-provided prompt text is transmitted to the external Pollinations.ai service without any clear warning or consent flow. In this skill context, users may include sensitive business, personal, or proprietary content in poster prompts, so silent third-party transmission creates a real confidentiality/privacy 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 51)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"pf_ai_{hashlib.md5(prompt.encode()).hexdigest()}.jpg"
    result = subprocess.run(["curl", "-sL", url, "-o", str(tmp), "--max-time", "180"],
                           capture_output=True)
    if result.returncode != 0 or not tmp.exists() or tmp.stat().st_size < 1000:
        return None

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
93% confidence
Finding

The HTML renderer writes user-controlled content directly into an HTML file and opens it in Chromium with --no-sandbox. Because title, subtitle, labels, and item fields are interpolated without HTML escaping, an attacker can inject arbitrary HTML/JS; in a no-sandbox browser context this materially increases the risk of local file access, network exfiltration, or browser-level compromise depending on the Chromium environment.

Content

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

python
return False

    tmp_name = "pf_screenshot.png"
    result = subprocess.run([
        chromium, "--headless", "--disable-gpu", "--no-sandbox",
        f"--window-size={width},{height}",
        f"--screenshot={tmp_name}",

Static analysis

No suspicious patterns detected.