Back to skill

Security audit

Html2md

Security checks for vulnerabilities and agentic risk

Overview

This skill appears to do what it claims, but it should be reviewed carefully because it can fetch any reachable URL and includes a flagged HTTP dependency.

Install only if callers and workflows are trusted, or run it in a sandbox with egress blocked to private networks and metadata services. Validate URLs before passing them to the tool, update or audit dependencies before use, and treat converted markdown from remote pages as untrusted quoted content.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/html2md.js:77
Finding

Unrestricted URL Fetching Enables Server-Side Request Forgery

Content
View full analysis
controller.abort(), 15000); let response; try { response = await fetch(url, { signal: controller.signal, redirect: 'follow', headers: { 'User-Agent': 'html2md/1.0 (agent-friendly HTML converter; +https://github.com/openclaw/html2md)', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.5', }, }); } catch (err) { if (err.name === 'AbortError') die(`Timeout: request exceeded 15s for ${url}`); const cause = err.cause?.message || err.message; die(`Network error fetching ${url}: ${cause}`); } finally { clearTimeout(timer); } if (!response.ok) die(`HTTP ${response.status} ${response.statusText} — ${url}`); const ct = response.headers.get('content-type') || ''; if (!ct.includes('html') && !ct.includes('xml') && !ct.includes('text')) { die(`Non-HTML content type: ${ct} — use a different tool for binary content`); } return { html: await response.text(), finalUrl: response.url }; } ``` The untrusted URL reaches this function directly: ```js } else if (url) { const result = await fetchHtml(url); html = result.html; pageUrl = result.finalUrl; } ``` ### Technical Analysis The CLI fetches a caller-provided URL without validating its scheme, hostname, resolved IP addresses, port, or network destination. It also enables automatic redirects through `redirect: 'follow'` without validating each redirect target. This behavior is necessary at a basic level because URL retrieval is part of the declared HTML conversion functionality. However, ...[truncated 1962 chars]
Remediation
View remediation

other

Warning
Location
scripts/html2md.js:124
Finding

Untrusted Web Content Is Emitted for Agent Consumption Without a Trust Boundary

Content
View full analysis
str ? str.trim().split(/\s+/).length : 0; if (!article || wordCount(article.content) < MIN_WORDS) { return { title, content: bodyFallback(dom), }; } return { title: article.title || title, content: article.content || '', }; } ``` The extracted text is converted and emitted without an explicit untrusted-content boundary: ```js // Extract const extracted = extractContent(html, pageUrl); title = extracted.title; // Convert const td = buildTurndown(opts); let markdown = td.turndown(extracted.content); // Post-process markdown = postProcess(markdown, opts); // Prepend title if not already first heading if (title && !markdown.startsWith('# ')) { markdown = `# ${title}\n\n${markdown}`; } // Token budget if (opts.maxTokens) { markdown = truncateToTokens(markdown, opts.maxTokens); } const tokens = countTokens(markdown); // Output if (opts.json) { const out = { title, url: pageUrl, markdown, tokens, }; process.stdout.write(JSON.stringify(out, null, 2) + '\n'); } else { process.stdout.write(markdown + '\n'); } ``` ### Technical Analysis Readability and Turndown remove or transform presentation elements, but they intentionally preserve the textual content of a page. Consequently, attacke ...[truncated 2224 chars]
Remediation
View remediation
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (6)

Known Vulnerable Dependency: undici==7.22.0 — 16 advisory(ies): CVE-2026-1525 (Undici has an HTTP Request/Response Smuggling issue); CVE-2026-6733 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); CVE-2026-1527 (Undici has CRLF Injection in undici via `upgrade` option) +13 more

High
Category
Supply Chain
Confidence
98% confidence
Finding

The lockfile pins jsdom's transitive dependency undici to 7.22.0, and the supplied finding indicates multiple known high-severity advisories affecting that exact version, including request/response smuggling, response queue poisoning, and CRLF injection. In this skill's context, html2md explicitly supports URL fetching and web scraping, so vulnerable HTTP client behavior is security-relevant because untrusted remote servers could potentially manipulate request/response handling or poison reused connections.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill explicitly supports fetching arbitrary URLs, which is a network-capable behavior, but its manifest does not declare any tool scope such as permissions or allowed-tools. In an agent environment, undeclared network access weakens policy enforcement and reviewability, and can enable unintended outbound requests or SSRF-style misuse when user-controlled URLs are passed through the skill.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
94% confidence
Finding

The request headers hard-code Accept-Language: 'en-US,en;q=0.5', which imposes a specific language/locale preference for fetched content. This is a natural-language policy concern because the tool does not offer user opt-in or configuration for locale selection.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
93% confidence
Finding

The dependency is specified with a caret range, which allows newer compatible versions to be installed over time rather than a single immutable version. This can introduce supply-chain risk through unexpected upstream changes or compromised releases, although by itself it does not indicate malicious behavior and is common in normal JavaScript projects.

Content

Scanner excerpt · scripts/package.json (reported line 17)May include surrounding context.

json
"author": "",
  "license": "ISC",
  "dependencies": {
    "@mozilla/readability": "^0.6.0",
    "jsdom": "^28.1.0",
    "turndown": "^7.2.2"
  }

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
93% confidence
Finding

The jsdom dependency is declared with a caret version range, so installations may resolve to different package versions over time. In a tool that processes untrusted HTML and may fetch remote content, dependency drift increases supply-chain exposure because security posture can change without a code change in this repository.

Content

Scanner excerpt · scripts/package.json (reported line 18)May include surrounding context.

json
"license": "ISC",
  "dependencies": {
    "@mozilla/readability": "^0.6.0",
    "jsdom": "^28.1.0",
    "turndown": "^7.2.2"
  }
}

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
92% confidence
Finding

Using a caret range for turndown permits automatic uptake of future patch and minor releases, which reduces build reproducibility and increases exposure to compromised or breaking upstream releases. This is a genuine but low-severity supply-chain hardening issue rather than an immediate exploit in the file itself.

Content

Scanner excerpt · scripts/package.json (reported line 19)May include surrounding context.

json
"dependencies": {
    "@mozilla/readability": "^0.6.0",
    "jsdom": "^28.1.0",
    "turndown": "^7.2.2"
  }
}