Back to skill

Security audit

Multi-Cloud Docs/API Benchmark

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its documentation-benchmark purpose, but it needlessly asks for cloud credentials and has weak URL validation that can be abused to fetch unintended hosts.

Review before installing. Do not provide Alibaba Cloud access keys for this skill unless the publisher removes or justifies that prerequisite. If used, run it in a network-restricted environment and prefer pinned, verified HTTPS documentation links because the current URL validation can be fooled into fetching unintended hosts.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/benchmark_multicloud_docs_api.py:128
Finding
Official-Domain Allowlist Bypass Enables Server-Side Request Forgery## Vulnerability Details **File Location**: `scripts/benchmark_multicloud_docs_api.py:128-130, 286-290, 552-555` **Vulnerability Type**: URL allowlist bypass leading to server-side request forgery (SSRF) **Risk Level**: Medium ### Complete Code Snippets ```python def domain_allowed(url: str, domains: tuple[str, ...]) -> bool: low = url.lower() return any(d in low for d in domains) ``` ```python def classify_links(links: list[str], max_fetch: int = 3) -> dict[str, bool]: chunks = ["\n".join(links).lower()] for url in links[:max_fetch]: try: html = fetch_text(url, timeout=12).lower() except Exception: continue ``` ```python manual = getattr(args, f"{p.key}_links", "").strip() if manual: links = [x.strip() for x in manual.split(",") if x.strip()] links = [u for u in links if domain_allowed(u, p.domains)] ``` ### Technical Analysis The application attempts to constrain manually supplied and discovered URLs to official provider domains. However, `domain_allowed()` performs a case-insensitive substring search across the entire URL rather than parsing the URL and validating its hostname. Consequently, untrusted URLs pass validation whenever trusted-domain text appears anywhere in the URL. Examples include: ```text http://127.0.0.1/?docs.aws.amazon.com https://docs.aws.amazon.com.attacker.example/ https://attacker.example/path/help.aliyun.com ``` The accepted URLs are passed to `classify_links()`, which calls `fetch_text()`. That function ultimately uses `urllib.request.urlopen()`, causing the host running the skill to issue the request. Redirect targets are not independently validated. Therefore, even a URL whose initial hostname is genuinely allowlisted could redirect the client to an internal or attacker-controlled destination. ### Attack Path 1. An attacker or untrusted user supplies a crafted URL through a manual argument such as: ```bash python scripts/benchmark_multicloud_docs_api ...[truncated 1694 chars]
Remediation
## Remediation Suggestions 1. Parse each URL with `urllib.parse.urlsplit()` and validate the parsed hostname rather than searching the complete URL: ```python import urllib.parse def domain_allowed(url: str, domains: tuple[str, ...]) -> bool: try: parsed = urllib.parse.urlsplit(url) except ValueError: return False if parsed.scheme != "https": return False if parsed.username is not None or parsed.password is not None: return False host = (parsed.hostname or "").rstrip(".").lower() if not host: return False return any( host == domain.lower() or host.endswith("." + domain.lower()) for domain in domains ) ``` 2. Resolve the hostname and reject every address that is loopback, private, link-local, multicast, unspecified, or otherwise reserved. Validate all returned IPv4 and IPv6 addresses to prevent DNS rebinding through mixed address sets. 3. Restrict or reject nonstandard destination ports unless they are explicitly required. 4. Disable automatic redirects or validate the scheme, hostname, port, and resolved addresses of every redirect destination before following it. 5. Apply the same validation to manual links, preset seed links, search results, metadata links, and any other URL source immediately before each network request. Validation only at ingestion is insufficient. 6. Consider using an outbound proxy or network sandbox that denies localhost, private ranges, link-local ranges, and cloud metadata endpoints independently of application-level checks. 7. Add regression tests for malicious inputs, including: - Trusted text in a query string or path. - Trusted-domain prefixes on attacker-controlled hostnames. - User-information hostname confusion. - Encoded and mixed-case hostnames. - IPv4 and IPv6 loopback/private addresses. - Public URLs that redirect to internal addresses. - DNS responses co ...[truncated 41 chars]
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (10)

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill instructs execution of a Python script that performs network discovery and writes local artifacts, but the manifest declares no explicit tool scope or permission boundary. In an agent setting, this creates an authorization gap where the runtime may permit broader file and network actions than reviewers or users expect, increasing the chance of unintended data access or external requests.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The prerequisite section asks for Alibaba Cloud credentials and references 'mutating operations' even though the skill is described as a cross-cloud documentation benchmark that should only fetch public documentation and write local reports. This inconsistency can mislead operators into supplying unnecessary secrets, expanding the blast radius if the script, dependencies, or agent environment are compromised.

Vague Triggers

Medium
Confidence
88% confidence
Finding
The default prompt uses a broad invocation phrase for a benchmarking skill without adding strong scope constraints, eligibility checks, or disambiguation requirements. This can cause the skill to activate on ordinary comparison or benchmarking requests and perform unintended external-doc benchmarking behavior, increasing the chance of overreach, irrelevant tool use, or accidental invocation in contexts the user did not clearly authorize.

Vague Triggers

Medium
Confidence
89% confidence
Finding
This manifest file defines activation keywords such as "llm platform" and "model studio," which are generic phrases that may appear in ordinary technical discussions unrelated to this specific preset. The file provides no exclusion conditions, negative examples, or narrower context to clarify when the preset should activate versus when it should not.

External Transmission

Medium
Category
Data Exfiltration
Content
"skills/platform/docs/aliyun-platform-docs-benchmark/references/scoring.json"
)
GCP_DISCOVERY_APIS = "https://discovery.googleapis.com/discovery/v1/apis"
GITHUB_API_SEARCH_CODE = "https://api.github.com/search/code?q="

DEFAULT_SCORING_PROFILE = {
    "link_cap": 8,
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
def alicloud_openapi_signals(product: str) -> dict[str, Any]:
    try:
        zh = fetch_json("https://api.aliyun.com/meta/v1/products.json?language=ZH_CN")
        en = fetch_json("https://api.aliyun.com/meta/v1/products.json?language=EN_US")
    except Exception:
        return {"resolved": False}
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
def alicloud_openapi_signals(product: str) -> dict[str, Any]:
    try:
        zh = fetch_json("https://api.aliyun.com/meta/v1/products.json?language=ZH_CN")
        en = fetch_json("https://api.aliyun.com/meta/v1/products.json?language=EN_US")
    except Exception:
        return {"resolved": False}
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
def alicloud_openapi_signals(product: str) -> dict[str, Any]:
    try:
        zh = fetch_json("https://api.aliyun.com/meta/v1/products.json?language=ZH_CN")
        en = fetch_json("https://api.aliyun.com/meta/v1/products.json?language=EN_US")
    except Exception:
        return {"resolved": False}
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
The request headers hard-code `Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8`, which imposes a specific locale ordering for fetched content. This is a natural-language policy concern because the script does not offer the user any language or locale choice, nor document why this preference is required.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
preset_seeds = [str(x) for x in preset_seeds_raw if isinstance(x, str)]
        preset_seeds = [u for u in preset_seeds if domain_allowed(u, p.domains) or "github.com" in u.lower()]

        manual = getattr(args, f"{p.key}_links", "").strip()
        if manual:
            links = [x.strip() for x in manual.split(",") if x.strip()]
            links = [u for u in links if domain_allowed(u, p.domains)]
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Static analysis

No suspicious patterns detected.