Back to skill

Security audit

MD Table Image

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it advertises, but it renders user-controlled markdown as active HTML in an unsandboxed browser and ships risky dependency versions.

Review before installing. It is suitable only for trusted markdown unless the renderer is changed to escape or sanitize input, disable JavaScript and network requests, restore browser sandboxing, and update or pin safer dependencies.

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
scripts/render.mjs:44
Finding

Unescaped Markdown and title content executes as active HTML in an unsandboxed browser

Content
View full analysis
' + cells.map(c => `<${tag}>${formatCell(c)}`).join('') + ''; if (!headerDone) headerDone = false; // will be set by separator } else { if (inTable) { html += ''; inTable = false; headerDone = false; } // Handle headers const hMatch = trimmed.match(/^(#{1,6})\s+(.+)/); if (hMatch) { const level = hMatch[1].length; html += `${hMatch[2]}`; } else if (trimmed === '') { html += '
'; } else { html += `

${formatCell(trimmed)}

`; } } function formatCell(text) { return text .replace(/\*\*(.+?)\*\*/g, '$1') .replace(/`(.+?)`/g, '$1') .replace(/~~(.+?)~~/g, '$1'); } ``` The resulting HTML also directly embeds the untrusted title and is loaded into Chrome with JavaScript enabled and the browser sandbox disabled: ```js ${values.title ? `
${values.title}
` : ''} ${mdToHtml(md)} `; const browser = await puppeteer.launch({ executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', headless: 'new', args: ['--no-sandbox', '--disable-setuid-sandbox'], }); const page = await browser.newPage(); await page.setViewport({ width: parseInt(values.width), height: 100 }); await page.setContent(htmlContent, { waitUntil: 'networkidle0' }); ``` ### Technical Analysis Markdown and title values are inserted into an HTML document without contextual HTML escaping or sanitization. The `formatCell()` function only replaces a few Markdown patterns; it does not encode characters such as `<`, `>`, `&`, `"`, or `'`. Several input paths are affected: - Table cells pass through `formatCell()` and are then ...[truncated 2575 chars]
Remediation
View remediation
`, `"`, and `'` before inserting Markdown cells, paragraphs, headings, or titles into templates. - Apply Markdown emphasis transformations only through a parser that safely emits permitted elements. 2. **Reject or sanitize raw HTML** - Use a maintained Markdown renderer configured with raw HTML disabled. - If limited HTML is required, sanitize it using a strict allowlist that excludes scripts, event-handler attributes, frames, embedded objects, external-resource tags, and dangerous URL schemes. 3. **Restore browser isolation** - Remove `--no-sandbox` and `--disable-setuid-sandbox`. - Run Chrome under a dedicated, minimally privileged operating-system account or inside an additional container or sandbox. 4. **Disable unnecessary active capabilities** - Disable JavaScript for this static rendering use case with `page.setJavaScriptEnabled(false)`. - Add request interception and reject all network requests unless a narrowly defined local resource is explicitly required. - Apply a restrictive Content Security Policy, such as one denying scripts, frames, plugins, connections, and remote media. 5. **Add resource controls** - Impose maximum input length, table size, viewport width, and render duration. - Validate `--width` as a finite integer within a safe range. - Use explicit timeouts and ensure the browser is closed in a `finally` block. 6. **Add security regression tests** - Verify that inputs containing `
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • 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 (8)

Known Vulnerable Dependency: basic-ftp==5.1.0 — 4 advisory(ies): CVE-2026-27699 (Basic FTP has Path Traversal Vulnerability in its downloadToDir() method); GHSA-6v7q-wjvx-w8wg (basic-ftp: Incomplete CRLF Injection Protection Allows Arbitrary FTP Command Exe); CVE-2026-41324 (basic-ftp vulnerable to denial of service via unbounded memory consumption in Cl) +1 more

Critical
Category
Supply Chain
Confidence
93% confidence
Finding

The lockfile includes basic-ftp 5.1.0 transitively via get-uri -> pac-proxy-agent -> proxy-agent -> @puppeteer/browsers -> puppeteer-core, and the scanner reports multiple advisories affecting that exact version. This is a real supply-chain risk because vulnerable FTP handling code may be reachable if proxy/PAC resolution or URI fetching paths are exercised, though in this skill's table-to-image use case it is likely indirect rather than core functionality.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: extract-zip==2.0.1 — 2 advisory(ies): CVE-2026-19693 (extract-zip allows arbitrary file writes through symlink archive entries); CVE-2026-56876 (extract-zip unvalidated symlink path traversal)

High
Category
Supply Chain
Confidence
96% confidence
Finding

extract-zip 2.0.1 is present as a transitive dependency of @puppeteer/browsers, and the cited advisories describe arbitrary file write and symlink traversal during archive extraction. That is dangerous because browser download/install workflows commonly process archives, so a malicious archive or compromised download source could write outside the intended directory and potentially overwrite sensitive files.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: ip-address==10.1.0 — 2 advisory(ies): CVE-2026-69192 (ip-address: Address4 decodes leading-zero octets as decimal while resolvers deco); CVE-2026-42338 (ip-address has XSS in Address6 HTML-emitting methods)

High
Category
Supply Chain
Confidence
84% confidence
Finding

ip-address 10.1.0 is included transitively through socks -> socks-proxy-agent -> proxy-agent, and the reported issues affect address parsing and HTML-emitting methods. This is a genuine vulnerable package version, but in the context of a markdown-table-to-image skill the affected functionality is likely only reachable through optional proxy handling paths, making exploitation less central to the advertised use case.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: ws==8.19.0 — 2 advisory(ies): CVE-2026-45736 (ws: Uninitialized memory disclosure); CVE-2026-48779 (ws: Memory exhaustion DoS from tiny fragments and data chunks)

High
Category
Supply Chain
Confidence
91% confidence
Finding

ws 8.19.0 is a direct transitive dependency of puppeteer-core and the advisories indicate memory disclosure and memory exhaustion risks in WebSocket handling. Puppeteer relies on WebSocket communication with the browser's DevTools endpoint, so malformed or attacker-influenced traffic in that channel could cause denial of service or data exposure, especially in shared or remote-browser deployments.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

User-controlled markdown and title content are inserted into HTML without escaping, then rendered inside a real browser engine. An attacker can inject arbitrary HTML, including tags that trigger network requests or script execution in the page context, which is broader than necessary for table rendering and can lead to data exfiltration, local file access attempts, or browser-driven abuse depending on runtime settings.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
93% confidence
Finding

The dependency uses a caret range ("^24.37.5"), which allows automatic adoption of future minor and patch releases. This weakens supply-chain reproducibility and can introduce vulnerable or malicious upstream changes without an explicit review, especially for a package like puppeteer-core that interacts with browser automation and can materially affect runtime behavior.

Content

Scanner excerpt · package.json (reported line 14)May include surrounding context.

json
"license": "ISC",
  "type": "commonjs",
  "dependencies": {
    "puppeteer-core": "^24.37.5"
  }
}

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
86% confidence
Finding

The manifest describes a narrowly scoped table-to-PNG renderer, but the implementation converts not only table rows but also non-table content such as headings, blank lines, and paragraphs. This broadens the behavior beyond 'markdown tables' into more general markdown-to-image rendering.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

The code saves output to values.o, which defaults to /tmp/table.png but can be any supplied path. Although file output is part of the script's function, there is no explicit warning, confirmation, or visible disclosure beyond the terse usage message that the command will write or overwrite a file at the chosen location.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.