Back to skill

Security audit

competitor-monitor

Security checks for vulnerabilities and agentic risk

Overview

This skill mostly matches site-change monitoring, but it needs review because it can fetch any URL from the runner and its main snapshot path bypasses the promised robots.txt check.

Install only if you trust the operator and will restrict it to approved public target sites. Avoid running it in environments that can reach internal admin services or cloud metadata endpoints, choose dedicated output/state directories, and treat learned_patterns.json and snapshot files as retained local business data that may need review or cleanup.

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

Unrestricted URL Fetching Enables Server-Side Request Forgery

Content
View full analysis

Vulnerability Details

File Location: scripts/scrape.py, lines 52-75
Vulnerability Type: Server-Side Request Forgery (SSRF)
Risk Level: High

Vulnerable Code

python
def fetch(url, use_js=False, wait=None, timeout=20):
    if use_js:
        return fetch_playwright(url, wait, timeout)
    headers = {"User-Agent": random.choice(USER_AGENTS), "Referer": "https://www.google.com/"}
    resp = requests.get(url, headers=headers, timeout=timeout)
    resp.raise_for_status()
    return resp.text


def fetch_playwright(url, wait, timeout):
    try:
        from playwright.sync_api import sync_playwright
    except ImportError:
        print("❌ 需要 playwright:pip install playwright && playwright install chromium", file=sys.stderr)
        sys.exit(1)
    with sync_playwright() as pw:
        browser = pw.chromium.launch(headless=True)
        page = browser.new_page()
        page.goto(url, timeout=timeout * 1000)
        if wait:
            page.wait_for_selector(wait, timeout=timeout * 1000)
        html = page.content()
        browser.close()
        return html

The untrusted URL reaches these functions through the command-line interface:

python
ap.add_argument("url")
...
html = fetch(args.url, args.js, args.wait, args.timeout)

The snapshot workflow also passes its URL directly to the same fetch function:

python
def build_records(url, fields, use_js, wait, timeout):
    html = fetch(url, use_js, wait, timeout)

Technical Analysis

The application accepts a user-controlled URL and passes it directly to either requests.get() or Playwright's page.goto(). It does not validate:

  • The URL scheme.
  • The destination hostname.
  • The resolved IPv4 or IPv6 address.
  • Whether the destination is loopback, link-local, private, reserved, or multicast.
  • Whether redirects lead to a prohibited address.
  • Whether the destination is a cloud ...[truncated 2305 chars]
Remediation
View remediation

Remediation Suggestions

  1. Accept only explicitly supported schemes, preferably http and https. Reject file, ftp, data, browser-internal, and all other schemes.
  2. Parse URLs with a strict parser and reject missing hosts, embedded credentials, malformed ports, and ambiguous host representations.
  3. Resolve all destination addresses before connecting. Reject IPv4 and IPv6 addresses in loopback, private, link-local, reserved, unspecified, multicast, and carrier-grade NAT ranges according to the deployment policy.
  4. Explicitly block cloud metadata destinations and hostnames, including link-local metadata addresses.
  5. Disable automatic redirects or validate every redirect target using the same scheme, hostname, DNS, and IP checks before following it.
  6. Defend against DNS rebinding by connecting only to the validated resolved address while preserving the intended HTTP host and TLS verification semantics.
  7. Prefer an allowlist of approved public monitoring domains when the set of targets is known.
  8. Apply outbound firewall or proxy restrictions so the process cannot reach internal networks or metadata services even if application validation fails.
  9. In Playwright mode, intercept all browser requests and reject prohibited navigation, iframe, script, image, stylesheet, XHR, fetch, and WebSocket destinations.
  10. Set response-size, redirect-count, and overall request limits to reduce resource-exhaustion risks.
  11. Add tests covering decimal, hexadecimal, octal, IPv6, IPv4-mapped IPv6, hostname aliases, redirect chains, and DNS-rebinding cases.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/snapshot.py:25
Finding

Snapshot Monitoring Bypasses the Declared robots.txt Enforcement

Content
View full analysis

Vulnerability Details

File Location: scripts/snapshot.py, lines 25-42
Vulnerability Type: Missing access-policy enforcement in a secondary execution path
Risk Level: Medium

Vulnerable Code

The snapshot workflow directly invokes fetch() without calling can_fetch():

python
def build_records(url, fields, use_js, wait, timeout):
    html = fetch(url, use_js, wait, timeout)
    # 构造临时 args 对象给 extract(仅 field 模式)
    class A:
        pass
    a = A()
    a.select = None
    a.attr = None
    a.text = False
    a.field = fields
    a.js = use_js
    a.wait = wait
    a.timeout = timeout
    a.out = None
    a.no_robots = False
    a.url = url
    return extract(html, a)

Although a.no_robots is set to False, the extract() function does not inspect that property. The robots.txt enforcement exists only in scrape.py's command-line main() path:

python
def can_fetch(url, check=True):
    if not check:
        return True
    try:
        from urllib.parse import urlparse
        from urllib.robotparser import RobotFileParser
        p = urlparse(url)
        base = f"{p.scheme}://{p.netloc}"
        rp = RobotFileParser()
        rp.set_url(base + "/robots.txt")
        rp.read()
        return rp.can_fetch("*", url)
    except Exception:
        return True  # 读取失败则放行(保守默认)
python
if not can_fetch(args.url, not args.no_robots):
    print("⛔ robots.txt 禁止抓取该 URL:", args.url, file=sys.stderr)
    sys.exit(1)

try:
    html = fetch(args.url, args.js, args.wait, args.timeout)
except Exception as e:
    print(f"❌ 抓取失败: {type(e).__name__}: {e}", file=sys.stderr)
    sys.exit(1)

Technical Analysis

Policy enforcement is implemented in the scrape.py CLI wrapper rather than in the shared network-fetching boundary. Consequently, callers that import fetch() can omit the robots.txt check.

snapshot.py is th ...[truncated 2067 chars]

Remediation
View remediation

Remediation Suggestions

  1. Move robots.txt enforcement into the shared fetch boundary so every caller receives the same policy checks.
  2. Alternatively, expose a single high-level fetch API that performs URL validation, robots.txt evaluation, redirect validation, rate limiting, and retrieval; do not export an unguarded fetch primitive to normal workflows.
  3. In snapshot.py, explicitly invoke can_fetch(url) before fetching and terminate when access is disallowed.
  4. If strict robots.txt compliance is promised, fail closed when robots.txt cannot be retrieved or parsed. If fail-open behavior is required, document it accurately and make it an explicit operator-controlled policy.
  5. Use a stable, truthful crawler user-agent and evaluate rules for that same user-agent rather than checking "*" while sending a browser user-agent.
  6. Re-evaluate robots.txt after cross-origin redirects and apply destination URL restrictions before following them.
  7. Remove the ineffective a.no_robots assignment or refactor the argument object so it cannot imply enforcement that never occurs.
  8. Add integration tests verifying that both scrape.py and snapshot.py reject disallowed paths and handle robots.txt retrieval failures according to the documented policy.
  9. Add per-host rate limiting and request scheduling controls to support the Skill's declared operational boundaries.
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • 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
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (13)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared purpose is site-change monitoring, but the visible content does not actually implement that workflow and instead introduces a generic learning/recording subsystem. This mismatch is dangerous because users and orchestrators may trust the stated purpose while the skill accumulates operational notes and cross-skill data that were not clearly disclosed as the primary behavior.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The declared purpose is site-change monitoring, but the visible content does not actually implement that workflow and instead introduces a generic learning/recording subsystem. This mismatch is dangerous because users and orchestrators may trust the stated purpose while the skill accumulates operational notes and cross-skill data that were not clearly disclosed as the primary behavior.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The manifest describes a skill for fetching target pages, diffing snapshots, and alerting on price/content/structure changes. This file instead implements a reusable, skill-agnostic telemetry and preference-learning system that records usage history, errors, and user preferences for arbitrary skills, which is a materially different behavior from monitoring competitor websites.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill clearly describes network fetching and persistent snapshot storage, but it does not declare any explicit tool scope such as allowed tools or permissions. That creates an authorization gap where an agent may invoke network and file-write capabilities more broadly than users expect, increasing the chance of unintended scraping or local data writes.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The self-evolution subsystem records notes, failures, and reflections across runs in a way that is unrelated to the narrow task of monitoring webpage changes. Persistent cross-skill learning stores operational context that could include sensitive URLs, selectors, business strategy, or user-entered notes, expanding data collection beyond user expectations.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The natural-language descriptions, usage text, and user-facing messages are entirely in Chinese, and the tool does not indicate any option for language or locale selection. Under the stated policy, forcing a specific language without user opt-in is a natural-language policy violation.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The module persistently stores arbitrary preferences, operation history, error notes, and recent activity for any skill without data minimization, schema restriction, or explicit relevance to competitor-page monitoring. If notes or preferences contain sensitive business targets, credentials pasted by mistake, or operational details, they will be written to disk and retained, increasing privacy and local data exposure risk.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

record_preference writes user-supplied key/value data directly to a persistent JSON file, and record_op similarly stores notes and history, but the CLI provides no warning that these values are retained on disk. In a monitoring skill context, users may enter competitor identifiers, URLs, pricing observations, or other sensitive operational notes assuming they are ephemeral, creating an avoidable privacy and compliance risk.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

技能清单描述该技能应围绕页面变更监控、历史快照比对与告警展开,但本文件的模块文档将其定位为可对任意网页做通用采集、链接提取和结构化导出的独立抓取器。代码也确实实现了广义网页抽取与导出能力,而非仅服务于变更检测流程,表现出比声明用途更宽的行为范围。

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

This code file contains natural-language instructions and messages entirely in Chinese, including the main docstring and CLI help text. Under the stated policy, forcing a specific language without user opt-in or clear regional justification is a language/locale policy violation.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

extract() 在未指定字段时默认提取页面全部链接,并支持通过任意 CSS 选择器批量抽取字段,形成一个通用内容采集器。对于“竞品/站点变更监控”这一用途,抓取页面内容用于快照和 diff 是合理的,但内置广义数据抽取能力并非该目的的直接、明显必需能力。

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The skill persists monitoring snapshots and learned patterns locally but does not explain retention, overwrite semantics, or how users can inspect and delete stored data. Even if the data is not highly sensitive by default, silent persistence can leak browsing targets, extracted content, and operator notes over time.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

manifest 强调的是定期抓取、历史快照 diff 与告警,而这里实现了可将任意抽取结果保存到用户指定文件的通用导出功能。虽然监控可能需要保存快照,但这种开放式 JSON/CSV 导出更像独立采集工具能力,而不是仅面向监控工作流的最小实现。

Content

No source excerpt is available for this finding.