Back to skill

Security audit

Apple Viz

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it claims, but it renders unescaped user-supplied chart data in a browser and can fall back to an unsandboxed Chromium renderer with unpinned runtime dependencies.

Review before installing. Use only in an isolated environment with non-sensitive or trusted chart data unless the skill is changed to escape all user-controlled fields, strictly validate colors and numeric inputs, remove the --no-sandbox fallback, restrict browser/network behavior, and pin dependencies.

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

T09 · Insecure Skill Coding Practices

Error
Location
generate.py:82
Finding

Unescaped User Input Enables Active HTML Injection in the Screenshot Browser

Content
View full analysis
""" label_els += f'{lbl}' value_els += f'{val}' svg_w = 48 + len(values) * (bar_w + 12) unit_label = f'{unit}' if unit else "" return f""" {css_base(dark, width, height)}
{title}
{unit_label} {bars_svg} {label_els} {value_els}
""" ``` The same unsafe interpolation pattern is used for line-chart labels and units, donut labels and colors, progress labels and colors, stat-card fields, and horizontal-bar fields. ...[truncated 2254 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
generate.py:408
Finding

Chromium Sandbox Disabled While Rendering Attacker-Controlled Content

Content
View full analysis
bool: try: import asyncio import pyppeteer async def _take(): browser = await pyppeteer.launch(headless=True, args=["--no-sandbox"]) page = await browser.newPage() await page.setViewport({"width": width, "height": height}) await page.goto(f"file://{html_path}") await page.waitForTimeout(600) await page.screenshot({"path": output, "fullPage": False}) await browser.close() asyncio.get_event_loop().run_until_complete(_take()) return True ``` ### Technical Analysis The pyppeteer fallback launches Chromium with `--no-sandbox`. Chromium's sandbox is a defense-in-depth boundary designed to contain a compromised renderer and restrict its access to host resources. This configuration is particularly dangerous because the rendered HTML contains unescaped, attacker-controlled fields. HTML injection alone normally provides code execution inside the renderer's browser security model. Disabling the process sandbox removes a major containment layer if the injected content triggers a Chromium engine vulnerability. The fallback is reached whenever the Playwright screenshot command returns a nonzero status, times out, or is unavailable. ### Attack Path 1. An attacker supplies active HTML through a chart title or data field. 2. Playwright is unavailable or its screenshot operation fails. 3. The program automatically invokes `screenshot_with_pyppeteer`. 4. Pyppeteer starts Chromium with `--no-sandbox`. 5. Chromium opens the attacker-influenced local HTML document. 6. Injected JavaScript executes in the renderer. 7. If the attacker can exploit a compatible Chromium vulnerabilit ...[truncated 812 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
generate.py:393
Finding

Unpinned Dependencies and Automatic Runtime Package Retrieval

Content
View full analysis
bool: try: result = subprocess.run( ["npx", "--yes", "playwright", "screenshot", "--browser", "chromium", "--viewport-size", f"{width},{height}", "--wait-for-timeout", "800", f"file://{html_path}", output], capture_output=True, text=True, timeout=60 ) return result.returncode == 0 except (subprocess.TimeoutExpired, FileNotFoundError): return False ``` `requirements.txt`: ```text pyppeteer ``` `SKILL.md`: ```markdown Playwright (via npx) for screenshots. Falls back to pyppeteer if npx is unavailable. ```bash pip3 install -r {baseDir}/requirements.txt ``` ``` ### Technical Analysis `npx --yes playwright` can resolve and download a package from the configured npm registry when a suitable local package is unavailable. No exact Playwright version or integrity constraint is specified, and `--yes` suppresses interactive confirmation. The Python dependency is likewise declared as `pyppeteer` without an exact version or hash. Installation therefore resolves a mutable package version from the configured package index. This makes execution non-reproducible and creates a supply-chain trust boundary at invocation or installation time. A compromised upstream release, registry account, package source, or dependency graph could introduce code that executes with the privileges of the user running the Skill. The code uses an argument array rather than a shell command, so this specific issue is not shell command injection. ### Attack Path 1. The Skill runs on a host without an already installed or cached Playwright packag ...[truncated 994 chars]
Remediation
View remediation
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (10)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The implementation largely matches the stated purpose: it builds Apple-themed HTML visualizations, supports rendering/screenshotting to PNG, and uses browser automation tools only for that screenshot step. There is no evidence of unrelated data access, network exfiltration, or materially different behavior. However, the code supports six chart types in practice: bar, line, donut, progress, hbar, and stat-card, while the declared description lists only five and omits hbar. That is an undeclared capability, so this should be flagged as a mismatch, albeit a limited one rather than a major purpose deviation.

Content

No source excerpt is available for this finding.

Unvalidated Output Injection

High
Category
Output Handling
Confidence
95% confidence
Finding

User-controlled strings from title, labels, segment labels, and similar fields are interpolated directly into HTML/SVG without escaping, then loaded by a real browser engine via file:// for rendering. That creates an HTML/script injection primitive in a privileged local rendering context, potentially enabling JavaScript execution, local file access attempts, network egress, or renderer exploitation depending on browser behavior and environment.

Content

Scanner excerpt · generate.py (reported line 395)May include surrounding context.

python
def screenshot_with_playwright(html_path: str, output: str, width: int, height: int) -> bool:
    try:
        result = subprocess.run(
            ["npx", "--yes", "playwright", "screenshot",
             "--browser", "chromium",
             "--viewport-size", f"{width},{height}",

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill declares shell and file-write capable behavior but does not constrain tool scope with explicit permissions or allowed-tools. In an agent environment, this can let the skill invoke broader command execution or write paths than intended, increasing the chance of command misuse, unsafe file writes, or abuse through prompt-triggered activation.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The trigger phrases are very broad and likely to match ordinary user requests to visualize or graph data, causing the skill to activate in many contexts without clear scope limits. Over-broad activation increases the chance that shell/file-writing behavior is invoked unexpectedly, including on untrusted user-provided data or in situations where a safer built-in capability would suffice.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The 'When to use' section defines ambiguous activation conditions without constraints on trust boundaries, input format, or operational limits. In context, this is more dangerous because the skill can launch shell-driven rendering and write PNG output, so accidental or overly eager activation broadens the attack surface and may process attacker-controlled content unexpectedly.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
84% confidence
Finding

The skill depends on Playwright via npx without a pinned version, which introduces supply-chain and reproducibility risk. A future or maliciously substituted package version could execute unexpected code when the skill runs screenshots, especially because npx fetches and executes packages dynamically.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The manifest describes generating Apple-style HTML visualizations and screenshotting them as PNGs, but this implementation does so by invoking an external package runner and browser toolchain through subprocess. Launching arbitrary external tooling is a broader execution capability than the manifest suggests and is not explicitly declared as part of the skill's scope.

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

python
def screenshot_with_playwright(html_path: str, output: str, width: int, height: int) -> bool:
    try:
        result = subprocess.run(
            ["npx", "--yes", "playwright", "screenshot",
             "--browser", "chromium",
             "--viewport-size", f"{width},{height}",

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

The code writes HTML containing user-controlled title and data fields to a predictable location under /tmp and only removes it on success. On failures, the file path is printed and the file persists, which can expose sensitive user-provided content to other local users/processes on shared systems or through logs.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
98% confidence
Finding

The dependency is unpinned, so installs may pull different pyppeteer versions over time, including versions with newly introduced bugs or breaking changes. Because this skill uses a browser automation library that fetches and executes browser components, supply-chain drift increases security and reliability risk beyond a typical utility package.

Content

Scanner excerpt · requirements.txt (reported line 1)May include surrounding context.

text
pyppeteer

Static analysis

No suspicious patterns detected.