Back to skill

Security audit

Webpage Export

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent webpage-export purpose, but it gives the agent broad local fetching and file-writing power that is not tightly scoped.

Install only if you are comfortable with the skill fetching URLs and rendering pages from your local machine. Use it on trusted public webpages, choose a dedicated output directory, avoid user-controlled --basename values, and disable browser/PDF fallback for untrusted URLs or shared environments unless network isolation is available.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/export_webpage.py:21
Finding

Unrestricted URL Handling Enables SSRF and Local File Disclosure

Content
View full analysis
str: result = run(['curl', '-L', '--max-time', '30', '-A', UA, url]) return result.stdout ``` The same unvalidated URL is also passed to Chrome: ```python def fetch_rendered_dom(url: str, virtual_time_budget: int) -> str: chrome = chrome_path() if not chrome: raise RuntimeError('未找到 Chrome/Chromium,无法获取浏览器渲染后的 DOM') result = run([ chrome, '--headless=new', '--disable-gpu', '--no-first-run', f'--virtual-time-budget={virtual_time_budget}', '--dump-dom', url, ]) return result.stdout ``` It is additionally passed to Playwright: ```python const url = process.argv[1]; const out = process.argv[2]; const chrome = process.env.CHROME_BIN; const { chromium } = require('playwright'); const browser = await chromium.launch({ headless: true, executablePath: chrome }); const page = await browser.newPage(); await page.goto(url, { waitUntil: 'networkidle', timeout: 45000 }); ``` The initial fetch is invoked directly using the command-line argument: ```python raw = fetch_html(args.url) browser_text_temp = outdir / '.browser_text_tmp.json' doc = extract(args.url, raw, not args.disable_browser_fallback, args.virtual_time_budget, browser_text_temp=browser_text_temp) ``` ### Technical Analysis The user-controlled URL is passed directly to `curl`, Chrome, and Playwright without validation of: - URL scheme - Destination hostname or resolved IP address - Destination port - Redirect destinations - Loopback, link-local, private, multicast, or reserved address ranges - Local-file schemes - DNS rebinding or resolution changes The `cu ...[truncated 2313 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/export_webpage.py:400
Finding

Unsanitized Basename Allows Output Path Traversal and Arbitrary File Overwrite

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/export_webpage.py:398
Finding

Predictable Browser Temporary File Permits Symlink-Based File Overwrite

Content
View full analysis
{ const title = document.title || ''; const bodyText = (document.body && document.body.innerText) ? document.body.innerText : ''; const metas = {}; for (const el of document.querySelectorAll('meta')) { const k = el.getAttribute('property') || el.getAttribute('name'); const v = el.getAttribute('content'); if (k && v) metas[k] = v; } return { title, bodyText, metas }; }); fs.writeFileSync(out, JSON.stringify(payload), 'utf8'); await browser.close(); ``` A fixed temporary filename is selected inside the output directory: ```python raw = fetch_html(args.url) browser_text_temp = outdir / '.browser_text_tmp.json' doc = extract(args.url, raw, not args.disable_browser_fallback, args.virtual_time_budget, browser_text_temp=browser_text_temp) ``` ### Technical Analysis The browser fallback always uses the predictable name `.browser_text_tmp.json`. The file is located under a user-selected or potentially shared output directory. Before writing, the code does not: - Create the file exclusively - Verify ownership - Reject symbolic links - Check that the destination is a regular file - Use a private temporary directory - Randomize the filename - Remove the temporary file after use Node.js `fs.writeFileSync()` follows symbolic links under normal filesystem behavior. A local attacker who can modify the output directory can therefore pre-create `.browser_text_tmp.json` as a symbolic link to another file writable by the Skill process. Exploitation requires browser-visible-text fallback to run. This occurs when browser fallback is enabled and the static or r ...[truncated 1293 chars]
Remediation
View remediation
Vulnerability Patterns
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • 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
Findings (8)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill instructs the agent to run shell commands, read local reference files, and write exported artifacts, but it does not declare any explicit tool scope such as allowed-tools or permissions. This creates an authorization gap where an agent may execute network-fetching and file-writing behavior without clear policy boundaries, increasing the chance of overbroad access or unsafe execution in shared environments.

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 · scripts/export_webpage.py (reported line 18)May include surrounding context.

python
def run(cmd):
    return subprocess.run(cmd, check=True, capture_output=True, text=True)


def fetch_html(url: str) -> str:

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

This code hard-codes a Chinese default title ('网页内容提取'), and many subsequent user-facing strings and runtime errors are also fixed in Chinese. For a general-purpose webpage export script, forcing one language without opt-in or documented regional scope is a natural-language locale policy violation.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
93% confidence
Finding

The script launches Node/Playwright to visit an attacker-controlled URL and execute that page's JavaScript in a full browser context. Because this is an automated fetch/export skill, hostile pages can trigger browser-based attacks, SSRF to internal network resources, or exploitation of browser/renderer bugs; the risk is increased by the optional fallback being automatic when content quality is low.

Content

Scanner excerpt · scripts/export_webpage.py (reported line 255)May include surrounding context.

python
'HOME': os.environ.get('HOME', ''),
        'CHROME_BIN': chrome,
    }
    subprocess.run([
        'node',
        '-e',
        script,

Tainted flow: 'child_env' from os.environ.get (line 250, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/export_webpage.py (reported line 255)May include surrounding context.

python
'HOME': os.environ.get('HOME', ''),
        'CHROME_BIN': chrome,
    }
    subprocess.run([
        'node',
        '-e',
        script,

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 · scripts/export_webpage.py (reported line 358)May include surrounding context.

python
def write_docx(txt_path: pathlib.Path, docx_path: pathlib.Path):
    if which('textutil') is None:
        raise RuntimeError('textutil 不可用,无法生成 docx')
    subprocess.run(['textutil', '-convert', 'docx', str(txt_path), '-output', str(docx_path)], check=True)


def write_pdf(url: str, pdf_path: pathlib.Path, virtual_time_budget: int):

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
95% confidence
Finding

The PDF generation path launches a real headless Chrome instance against a user-supplied URL. Rendering attacker-controlled content in a local browser can expose the host to browser exploitation, unexpected network access from the browser, and SSRF-style interactions with internal services; in an agent skill context this is more dangerous because the tool is designed to fetch arbitrary webpages automatically.

Content

Scanner excerpt · scripts/export_webpage.py (reported line 365)May include surrounding context.

python
chrome = chrome_path()
    if not chrome:
        raise RuntimeError('未找到 Chrome/Chromium,无法生成 PDF')
    subprocess.run([
        chrome,
        '--headless=new',
        '--disable-gpu',

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

The guide states that Chrome/Chromium should be preferred for better Chinese rendering, which introduces a language-specific preference in natural-language guidance. Because the document does not indicate user opt-in, alternative locale selection, or a clearly justified region-specific constraint, this can conflict with language/locale policy expectations.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.