Back to skill

Security audit

extractor

Security checks for vulnerabilities and agentic risk

Overview

This HTML extraction skill needs review because its LLM helper can send page or file contents, and possibly an API key, to a configurable endpoint while the docs partly say LLM extraction is not implemented.

Install only if you are comfortable with user-selected URLs being loaded by a headless browser and with LLM extraction sending source HTML to the configured LLM endpoint. Do not use remote LLM mode on private pages or local files unless OPENAI_BASE_URL is a trusted local or HTTPS service, and consider adding endpoint allowlisting and clearer consent/disclosure before broad use.

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/extractor.py:236
Finding

Unrestricted Transmission of HTML Content to a Configurable LLM Endpoint

Content
View full analysis
max_html_chars: html_content = html_content[:max_html_chars] + "\n... (truncated)" template = prompt_template or self.DEFAULT_LLM_PROMPT prompt = template.format( fields=", ".join(fields), html_content=html_content, ) url = f"{base_url.rstrip('/')}/chat/completions" payload = json.dumps({ "model": model, "messages": [ {"role": "system", "content": "你是一个信息提取助手,只返回 JSON 格式结果。"}, {"role": "user", "content": prompt}, ], "temperature": 0.1, "max_tokens": 1024, }).encode("utf-8") headers = { "Content-Type": "application/json", } if api_key and api_key != "ollama": headers["Authorization"] = f"Bearer {api_key}" req = urllib.request.Request(url, data=payload, headers=headers, method="POST") with urllib.request.urlopen(req, timeout=60) as resp: result = json.loads(resp.read().decode("utf-8")) ``` ### Technical Analysis `extract_by_llm()` embeds the extractor's HTML input in an LLM prompt and sends it through an HTTP POST request. The destination can be selected directly with `base_url` or indirectly through `OPENAI_BASE_URL`. The implementation does not: - Restrict remote destinations to trusted providers. - Require TLS for non-loopback destinations. - Warn or request confirmation before sending document contents. - Redact credentials, personal information, or other sensitive values from HTML. - Bind an API key to a specific trusted origin. - Distinguish content loaded from a public URL from content loaded from a local ...[truncated 1770 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/extractor.py:412
Finding

Unrestricted Browser Navigation Enables Server-Side Requests to Internal Destinations

Content
View full analysis
str: try: from playwright.sync_api import sync_playwright except ImportError: raise ImportError( "Playwright not installed. Run: pip install playwright && playwright install chromium" ) with sync_playwright() as p: browser = p.chromium.launch(headless=True) try: page = browser.new_page() page.goto(url, timeout=timeout, wait_until="domcontentloaded") html = page.content() return html finally: browser.close() def load_html(source: str) -> str: if source.startswith(("http://", "https://")): return load_html_from_url(source) else: return load_html_from_file(source) ``` ### Technical Analysis The loader sends a headless browser to any caller-supplied HTTP or HTTPS URL. It does not validate the hostname or resolved IP address and does not block: - Loopback destinations. - Private network ranges. - Link-local addresses. - Reserved or special-purpose addresses. - Redirects from a public address to a prohibited destination. Playwright may also load scripts, images, frames, and other subresources referenced by the page, expanding the network activity beyond the original URL. Fetching user-selected public pages is necessary for the Skill's documented URL mode. Access to internal and special-purpose network destinations is not necessary for ordinary page extraction and therefore exceeds minimum privilege when the Skill operates in a server or agent environment. ### Attack Path 1. An attacker supplies an HTTP or HTTPS URL that identifies an internal service, loopback listener, link-local endpoint, or attacker-controlled redirec ...[truncated 998 chars]
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (9)

Tainted flow: 'req' from os.environ.get (line 291, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
93% confidence
Finding

The LLM extraction path builds an outbound HTTP request using configuration sourced from environment variables, including the destination base URL and optional API key, and then sends page HTML to that endpoint. In a skill context, this creates a real data-exfiltration risk because sensitive local or fetched page content can be transmitted to an attacker-controlled service if environment variables are manipulated or misconfigured.

Content

Scanner excerpt · scripts/extractor.py (reported line 292)May include surrounding context.

python
try:
            req = urllib.request.Request(url, data=payload, headers=headers, method="POST")
            with urllib.request.urlopen(req, timeout=60) as resp:
                result = json.loads(resp.read().decode("utf-8"))

            # 解析响应

Hidden Instructions

High
Category
Prompt Injection
Confidence
60% confidence
Finding

Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 1)May include surrounding context.

md
---
name: extractor
description: "Extract structured data from HTML pages or URLs using CSS selectors, XPath, regex, or LLM natural..."
tags: [analysis, data, api-integration, file-based, cli]

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The title 'Extractor 使用文档' indicates the usage documentation is presented in Chinese, and the file continues entirely in Chinese without any opt-in, alternative locale, or justification that the skill is region-specific. This creates a natural-language locale policy concern because it imposes a specific language on users by default.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

At L009 the documentation states "LLM 提取(TODO)", implying that LLM extraction is not implemented yet. However, L225-L322 contain a working extract_by_llm implementation that constructs prompts, reads OPENAI_* environment variables, and sends HTML content to a remote OpenAI-compatible API endpoint.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The LLM extraction feature sends raw HTML content to a remote OpenAI-compatible API without any user-facing disclosure, consent gate, or sensitivity check. Because the tool can load local files or authenticated web content and then forward that content externally, this materially increases the risk of unintended disclosure of secrets, personal data, or proprietary page content.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

This markdown file shows CLI usage that retrieves content from a URL and saves results with -o result.json, both of which can affect privacy or local data state. The documentation does not include any warning or disclosure about contacting external sites or creating output files.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

Natural-language strings throughout the file, including the module docstring, CLI help text, and LLM prompt template, are written only in Chinese. This imposes a fixed language on users without offering a language choice or documenting a justified locale restriction.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
75% confidence
Finding

The load_html docstring describes the source as "URL 或文件路径", which is accurate, but the nearby loading documentation may lead users to assume URL loading is the primary or only behavior. However, this is not a strong contradiction, so confidence is limited and the issue is weak compared with the explicit LLM TODO mismatch.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
78% confidence
Finding

This code file includes tests for extract_by_llm, which sends HTML content to an LLM endpoint via urllib.request.urlopen. Although mocked in most tests, the file contains no comment, docstring, or user-facing disclosure warning that HTML content may be transmitted to an external service, which is relevant for privacy and safety expectations.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.