Back to skill

Security audit

web-to-markdown

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its web-scraping purpose, but it can send user URLs to third-party services and fetch or download arbitrary content without strong safety boundaries.

Install only if you are comfortable with webpage URLs being sent to external conversion services and with the agent making arbitrary web requests. Avoid private, internal, authenticated, signed, or token-bearing URLs, run it in a constrained environment, and prefer pinned dependencies and explicit confirmation before downloads.

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/scrape.py:9
Finding

Unrestricted URL Fetching Enables Server-Side Request Forgery

Content
View full analysis
str: """Scrape a webpage and return content as markdown-like text.""" fetcher = Fetcher(auto_match=None) page = fetcher.fetch(url) ``` Related image-validation and download sinks: ```python def is_valid_image(url: str, min_size: int = 0) -> bool: """Check if URL points to a valid image (optionally by size).""" # Skip common small images (icons, avatars, etc.) skip_patterns = [ r'/avatar', r'/icon', r'/logo', r'/favicon', r'/emoji', r'/smiley', r'/button', r'/badge', r'/spinner', r'/loading', r'/blank\.', r'/pixel\.', r'/1x1', r'/spinner', ] for pattern in skip_patterns: if re.search(pattern, url, re.IGNORECASE): return False # Check file size if min_size specified if min_size > 0: try: resp = requests.head(url, timeout=10, allow_redirects=True) content_length = int(resp.headers.get('content-length', 0)) if content_length > 0 and content_length < min_size: return False except: pass return True ``` ```python def download_image(url: str, output_dir: Path, index: int) -> bool: """Download a single image.""" try: filename = get_filename(url, index) output_path = output_dir / filename # Skip if exists if output_path.exists(): print(f" Skip (exists): {filename}") return True print(f" Downloading: {url[:60]}...") response = requests.get(url, timeout=30) response.raise_for_status() with open(output_pa ...[truncated 2111 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/extract_images.py:16
Finding

Sensitive Target URLs Are Disclosed to an External Conversion Service

Content
View full analysis
str: """Fetch page content using r.jina.ai.""" fetch_url = f"https://r.jina.ai/{url}" try: response = requests.get(fetch_url, timeout=30) response.raise_for_status() return response.text except Exception as e: print(f"Error fetching page: {e}", file=sys.stderr) return "" ``` The same implementation appears in `scripts/download_images.py`: ```python def fetch_page(url: str) -> str: """Fetch page content using r.jina.ai.""" fetch_url = f"https://r.jina.ai/{url}" try: response = requests.get(fetch_url, timeout=30) response.raise_for_status() return response.text except Exception as e: print(f"Error fetching page: {e}", file=sys.stderr) return "" ``` ### Technical Analysis The complete user-supplied target URL is concatenated into a request sent to `r.jina.ai`. URLs can contain credentials, signed query strings, password-reset tokens, document-sharing tokens, session identifiers, private hostnames, and other sensitive data. The scripts neither inspect nor redact these components before disclosure. This is a trust-boundary issue: users may reasonably expect the Skill to retrieve a page, but the implementation delegates retrieval to an external service that receives the entire target URL and may process or log it. ### Attack Path 1. A user invokes the extraction or download script with a sensitive URL, such as a signed object-storage link or a document-sharing URL containing an access token. 2. `fetch_page()` embeds that URL verbatim after `https://r.jina.ai/`. 3. The HTTP request discloses the target URL, including its path and query string, to the ...[truncated 633 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
SKILL.md:154
Finding

Unpinned Third-Party Dependency Installation Creates Supply-Chain Risk

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/download_images.py:107
Finding

Unbounded Response Buffering and File Writes Enable Resource Exhaustion

Content
View full analysis
bool: """Download a single image.""" try: filename = get_filename(url, index) output_path = output_dir / filename # Skip if exists if output_path.exists(): print(f" Skip (exists): {filename}") return True print(f" Downloading: {url[:60]}...") response = requests.get(url, timeout=30) response.raise_for_status() with open(output_path, 'wb') as f: f.write(response.content) print(f" Saved: {output_path}") return True except Exception as e: print(f" Error: {e}", file=sys.stderr) return False ``` ### Technical Analysis `requests.get()` is used without streaming, so `response.content` buffers the complete remote body in memory before writing it. The function does not enforce a maximum response size, cumulative download quota, validated `Content-Type`, or image-file signature. The `--min-size` option only establishes a lower bound and offers no protection against excessively large responses. A request timeout does not limit the number of bytes returned within the timeout. An attacker-controlled URL can therefore provide a very large body or non-image content despite having an image-like path. ### Attack Path 1. An attacker causes an image-like HTTPS URL under their control to appear in processed page content. 2. The URL passes the extension/path-based extraction and filtering logic. 3. The server returns a very large response or arbitrary non-image content. 4. `requests` buffers the full body in process memory. 5. The script writes the entire response to disk. 6. Repeating this across the configured image limit can exhaust ...[truncated 444 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (10)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The description claims three major capabilities: Markdown conversion, image URL extraction, and batch image download, with support for multiple conversion services and automatic fallback. The code chunk only implements the image-download path. It fetches page content through a single service (r.jina.ai), extracts candidate image URLs using regex, filters them, and downloads them locally. There is no Markdown conversion logic, no use of markdown.new or defuddle.md, and no fallback mechanism if r.jina.ai fails. While the implemented behavior partially overlaps with the declared image extraction/download functionality, the declared description materially overstates the available capabilities in this supplied code chunk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

There is a material description-behavior mismatch. The code chunk implements a narrow subset of the declared functionality: it fetches a page via r.jina.ai and extracts image-like URLs from text. That partially matches the 'extract image URLs' claim. However, major advertised capabilities are absent, including Markdown conversion, batch downloading of images, and support for multiple conversion services with automatic fallback. The primary purpose described is a broader multi-function scraping skill, while the actual code shown is a single-purpose image URL extractor. No clearly undeclared sensitive capability is present, but the declared description substantially overstates what this code chunk actually does.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The implemented code only supports a subset of the declared functionality: scraping a webpage and converting its textual content into a markdown-like representation. The description claims broader capabilities, especially image extraction and batch image downloading, which are absent from the code. It also specifically claims support for external conversion services with automatic fallback, but this code instead uses Scrapling directly and contains no such fallback logic. The primary purpose is related, but the declared description materially overstates the implemented capabilities.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill documents network access and file-writing behavior but does not declare any explicit tool scope or permissions boundaries. In an agent environment, this increases the chance the skill is invoked with broader capabilities than users expect, enabling unintended web requests and local file writes.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The activation guidance is broad enough to match many ordinary requests involving reading webpages or downloading content, which can trigger network access and file-writing actions without sufficiently specific user intent. In an agent setting, overbroad routing increases the risk of unnecessary data exfiltration to third-party services or unprompted downloads.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill explicitly routes user-supplied URLs and potentially page contents through third-party services like markdown.new, defuddle.md, and r.jina.ai without a clear privacy warning or consent step. If users provide sensitive, internal, or authenticated links, this can leak confidential data to external providers outside the user's expectations.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The code sends any user-supplied URL to the third-party service r.jina.ai without explicit disclosure or consent, which can leak sensitive URLs, query parameters, internal endpoints, or user browsing targets to an external operator. In this skill context, that is more dangerous because the skill is explicitly marketed for arbitrary web scraping and may be used on private, pre-signed, authenticated, or otherwise sensitive links that users reasonably expect to fetch directly.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
72% confidence
Finding

Throughout the document, headings and descriptions are consistently presented in both Chinese and English, suggesting a fixed bilingual locale choice rather than a user-selected language preference. The file does not state that language choice is configurable or based on user preference.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

The module docstring says the script extracts image URLs from any website, which implies operating on the target site's content directly. In practice, fetch_page rewrites the requested URL to https://r.jina.ai/... and extracts URLs from that proxy/conversion output instead, which can materially differ from the original page content.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The function name and surrounding interface suggest fetching the supplied page URL, but the implementation unconditionally transforms it into an r.jina.ai URL. This is an intent/documentation divergence because callers reading the docstring may assume direct retrieval of the target page rather than indirect retrieval through a conversion service.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.