Back to skill

Security audit

AEO Keyword Selector

Security checks for vulnerabilities and agentic risk

Overview

The skill is a mostly coherent SEO keyword helper, but its bundled sitemap parser can recursively fetch arbitrary URLs without origin or size limits.

Review before installing. Use this only with trusted site sitemaps, preferably HTTPS and known domains, and avoid feeding it untrusted sitemap URLs until the parser has same-origin, private-network, redirect, response-size, and traversal limits.

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/parse_sitemap.py:20
Finding

Unrestricted Recursive Sitemap Fetching Enables Server-Side Request Forgery

Content
View full analysis

Vulnerability Details

File Location: scripts/parse_sitemap.py, lines 20-39
Vulnerability Type: Server-Side Request Forgery (SSRF)
Risk Level: High

Vulnerable Code

python
def read_xml(source: str) -> bytes:
    if source.startswith("http://") or source.startswith("https://"):
        ctx = ssl.create_default_context()
        with urllib.request.urlopen(source, context=ctx, timeout=30) as resp:
            return resp.read()
    return Path(source).read_bytes()


def parse_urls(source: str) -> list[str]:
    seen_sitemaps = set()
    urls: list[str] = []
    queue = deque([source])

    while queue:
        current = queue.popleft()
        if current in seen_sitemaps:
            continue
        seen_sitemaps.add(current)

        data = read_xml(current)

Nested sitemap locations are subsequently placed into the same unrestricted processing queue:

python
        if tag == "sitemapindex":
            for loc in root.findall("sm:sitemap/sm:loc", NS):
                if loc.text:
                    queue.append(loc.text.strip())

Technical Analysis

The parser accepts attacker-influenced HTTP and HTTPS URLs and passes them directly to urllib.request.urlopen. It does not validate the destination hostname, resolved IP address, port, URL credentials, or redirect target. It also does not require nested sitemap URLs to remain on the origin of the initial sitemap.

Because child locations extracted from a remotely retrieved sitemap are recursively fetched, a malicious sitemap can direct the Agent environment to make HTTP requests to loopback addresses, private network ranges, link-local services, or other destinations that are inaccessible to the attacker directly. Python's URL opener also follows HTTP redirects by default, so validating only an initial URL in future changes would remain insufficient unless every redirect target is checked.

Plain HTTP is acce ...[truncated 1715 chars]

Remediation
View remediation

Remediation Suggestions

  • Require HTTPS for all remote sitemap sources and reject plaintext HTTP URLs.
  • Parse URLs with urllib.parse.urlsplit and allow only explicitly supported schemes.
  • Establish an explicit hostname allowlist or require nested sitemap URLs to use the same approved origin as the initial sitemap.
  • Resolve each destination hostname and reject loopback, private, link-local, multicast, unspecified, reserved, and other non-public address ranges for both IPv4 and IPv6.
  • Repeat destination validation after DNS resolution and for every redirect target to prevent redirect-based and DNS-rebinding bypasses.
  • Disable automatic redirects or implement a bounded redirect handler that validates every hop.
  • Reject URLs containing embedded credentials and restrict destination ports to an approved set, normally TCP 443.
  • Consider accepting only site owners' preapproved sitemap origins rather than arbitrary user-provided URLs.
  • Log rejected destinations without exposing credentials or sensitive URL parameters.
  • Add tests for loopback addresses, private IPv4 and IPv6 ranges, mixed-format IP addresses, redirects to internal hosts, cross-origin child sitemaps, and DNS-rebinding scenarios.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/parse_sitemap.py:20
Finding

Unbounded Sitemap Download and Recursion Allow Resource Exhaustion

Content
View full analysis

Vulnerability Details

File Location: scripts/parse_sitemap.py, lines 20-48
Vulnerability Type: Uncontrolled Resource Consumption
Risk Level: Medium

Vulnerable Code

python
def read_xml(source: str) -> bytes:
    if source.startswith("http://") or source.startswith("https://"):
        ctx = ssl.create_default_context()
        with urllib.request.urlopen(source, context=ctx, timeout=30) as resp:
            return resp.read()
    return Path(source).read_bytes()


def parse_urls(source: str) -> list[str]:
    seen_sitemaps = set()
    urls: list[str] = []
    queue = deque([source])

    while queue:
        current = queue.popleft()
        if current in seen_sitemaps:
            continue
        seen_sitemaps.add(current)

        data = read_xml(current)
        root = ET.fromstring(data)
        tag = root.tag.split("}")[-1]

        if tag == "sitemapindex":
            for loc in root.findall("sm:sitemap/sm:loc", NS):
                if loc.text:
                    queue.append(loc.text.strip())
        elif tag == "urlset":
            for loc in root.findall("sm:url/sm:loc", NS):
                if loc.text:
                    urls.append(loc.text.strip())

Technical Analysis

resp.read() loads the entire remote response into memory without enforcing a maximum byte count. Local files are similarly loaded in full. The XML document is then parsed into an in-memory element tree, increasing peak memory consumption beyond the original response size.

The breadth-first sitemap traversal has no maximum recursion depth, sitemap count, queue size, aggregate download size, processing deadline, or total extracted URL limit. Although seen_sitemaps prevents the same exact string from being processed repeatedly, an attacker can generate an arbitrary number of unique sitemap URLs. The urls, queue, and seen_sitemaps collections can consequently grow until ...[truncated 1601 chars]

Remediation
View remediation

Remediation Suggestions

  • Enforce a strict maximum response size before and during download; do not rely solely on the Content-Length header.
  • Read remote content incrementally and abort once the configured byte limit is exceeded.
  • Replace full-tree parsing with an appropriately hardened streaming parser where practical.
  • Define limits for maximum sitemap depth, total sitemap documents, queue length, URLs per sitemap, and total extracted URLs.
  • Apply a maximum aggregate byte budget across the complete recursive traversal.
  • Enforce an overall processing deadline in addition to per-request connection and read timeouts.
  • Validate sitemap files against reasonable protocol limits and reject anomalously large documents early.
  • Normalize sitemap URLs before deduplication so trivial textual variations cannot bypass the processed-sitemap check.
  • Handle limit violations with controlled errors and avoid writing partial results as if processing succeeded.
  • Add tests using oversized responses, broad sitemap trees, deep sitemap chains, large URL sets, slow responses, and unique-URL cycles.
Vulnerability Patterns
  • 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
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (3)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description presents a content-strategy skill for autonomously choosing a blog keyword based on business topics, sitemap architecture, intent checks, and SEMrush validation. The supplied code does none of that. It is a standalone utility that reads a sitemap XML file or URL, recursively follows sitemap indexes, and extracts URL locations into a flat list. While sitemap parsing could be a small supporting component in a broader SEO workflow, this code chunk by itself lacks the core declared behavior and instead has a materially different primary purpose: XML sitemap flattening. Therefore this is a clear description-behavior mismatch.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill explicitly instructs the agent to read local reference files, optionally use a sitemap parsing script, fetch pages from site URLs, and use SEMrush/browser checks, but it declares no permissions or allowed-tools boundary. In an agent framework, that creates an overbroad capability surface where file access and network use are implied but not constrained, increasing the chance of unintended data access, SSRF-style fetches, or execution of auxiliary scripts beyond what is necessary.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill allows implicit invocation across multiple products, but the manifest provides no trigger constraints, activation examples, or narrowing conditions. That can cause the skill to be auto-invoked in loosely related contexts, increasing the chance of unintended keyword-selection decisions that influence content strategy or page creation without clear user intent or oversight.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.