Back to skill

Security audit

Media.io Wan Video Generator

Security checks for vulnerabilities and agentic risk

Overview

This skill is a straightforward Media.io video-generation integration that sends prompts, image URLs, task IDs, and a Media.io API key to Media.io, with some documentation and redirect-hardening gaps to keep in mind.

Install only if you intend to use Media.io's remote service with a Media.io API key. Avoid submitting confidential prompts or private image URLs unless you are comfortable sharing them with Media.io, and consider using a narrowly scoped or dedicated key because the router currently relies on API_KEY and does not explicitly block cross-origin redirects.

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/skill_router.py:49
Finding
API Key Disclosure Through Unvalidated Cross-Origin Redirects## Vulnerability Details **File Location**: `scripts/skill_router.py`, lines 49-67 **Vulnerability Type**: Outbound request redirect validation bypass and credential exposure **Risk Level**: Medium ### Vulnerable Code ```python # Restrict outbound requests to the expected Media.io API host. parsed = urlparse(url) if parsed.scheme != 'https' or parsed.netloc.lower() != 'openapi.media.io': return {'error': f"Blocked endpoint host: {parsed.netloc}"} headers = { 'X-API-KEY': resolved_api_key, 'Content-Type': 'application/json' } # Replace path parameters in endpoint URLs. if '{' in url: for k, v in params.items(): url = url.replace(f'{{{k}}}', str(v)) # Keep non-path parameters in the JSON body. body = {k: v for k, v in params.items() if f'{{{k}}}' not in api['endpoint']} try: resp = requests.request(method, url, headers=headers, json={'data': body} if body else {}, timeout=30) ``` ### Technical Analysis The implementation verifies that the initial endpoint uses HTTPS and has the exact hostname `openapi.media.io`. It then sends the request using `requests.request` without disabling redirects. The Python `requests` library follows redirects by default. The code does not inspect or validate redirect destinations before following them. Although `requests` has special handling for standard authorization headers, the credential is supplied through the custom `X-API-KEY` header. Such custom headers can remain attached to a redirected request, including a redirect to a different origin. Consequently, the initial host allowlist does not guarantee that the API key is sent exclusively to `openapi.media.io`. A cross-origin redirect returned by the accepted endpoint could cause the request and its sensitive header to be delivered to another host. Exploitation depends on the trusted endpoint returning an attacker-controlled cross-origin redirect, such as through server compromise, an upstre ...[truncated 1444 chars]
Remediation
## Remediation Suggestions Disable automatic redirects for authenticated API requests: ```python resp = requests.request( method, url, headers=headers, json={'data': body} if body else {}, timeout=30, allow_redirects=False, ) ``` Treat unexpected redirect responses as errors. If redirects are operationally required, implement controlled redirect handling: 1. Follow redirects manually. 2. Parse and normalize every redirect destination. 3. Require the destination scheme to remain `https`. 4. Require the destination hostname to remain exactly `openapi.media.io`. 5. Reject user-information components, nonstandard ports, malformed hosts, and protocol-relative destinations. 6. Set a small maximum redirect count to prevent loops. 7. Remove `X-API-KEY` before any request whose origin differs from the original origin. 8. Add automated tests covering `301`, `302`, `303`, `307`, and `308` redirects to both approved and unapproved hosts. A hardened implementation should also validate the fully substituted URL immediately before transmission so that the URL receiving the credential is always subject to the final allowlist check.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (6)

Missing User Warnings

Medium
Confidence
84% confidence
Finding
This JSON manifest/documentation instructs clients to include an X-API-KEY in request headers and make POST requests to external openapi.media.io endpoints, but it provides no user-facing warning that credentials and request data will be transmitted to a third-party service. For markdown/manifest-style documentation, SQP-2 applies when behaviors affecting privacy or system integrity are not disclosed.

External Transmission

Medium
Category
Data Exfiltration
Content
"description": "API to query user credits balance.",
    "api_header": "{\"list\": [{\"name\": \"X-API-KEY\", \"value\": \"API key to authorize requests\"}, {\"name\": \"Content-Type\", \"value\": \"application/json\"}], \"title\": \"Authorizations\", \"describe\": \"Add the following authorization information in the request header\"}",
    "api_body": "{\"title\": \"Request Body\", \"category\": [{\"list\": [], \"title\": \"Query Credits\", \"describe\": \"Request body to query user credits balance\"}]}",
    "api_request_demo": "{\"title\": \"Example Request\", \"request\": [{\"title\": \"Query User Credits\", \"language\": \"cURL\", \"code_example\": \"curl --request POST \\n  --url https://openapi.media.io/user/credits \\n  --header 'Content-Type: application/json' \\n  --header 'X-API-KEY: <api-key>' \\n  --data '{}'\"}]}",
    "api_response": "{\"list\": [{\"name\": \"code\", \"type\": \"integer\", \"describe\": \"Response status code, 0 indicates success\"}, {\"name\": \"msg\", \"type\": \"string\", \"describe\": \"Response message, empty string on success\"}, {\"name\": \"data\", \"type\": \"object\", \"describe\": \"Response data object\"}, {\"name\": \"credits\", \"type\": \"integer\", \"describe\": \"User credits balance, located within the data object\"}], \"title\": \"Response\", \"describe\": \"After the request is successfully processed, the server will return the following response\"}",
    "api_code_demo": "{\"list\": [{\"code\": \"0\", \"describe\": \"Success\"}, {\"code\": \"40001\", \"describe\": \"Invalid API key\"}, {\"code\": \"40002\", \"describe\": \"API key expired\"}], \"title\": \"Status Code\"}",
    "content": null,
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The manifest documents image-to-video generation requests using an image URL and prompt, which can contain user media and potentially sensitive content, yet no warning explains that these inputs will be transmitted off-system for processing. That omission is a user-warning gap for manifest/JSON documentation describing data-affecting behavior.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
These entries define text-to-video APIs where user prompts are posted to external generation endpoints, but the documentation does not warn that prompt contents leave the local environment and are processed by a third-party service. Under SQP-2 for markdown/manifest files, such privacy-impacting behavior should be disclosed.

Intent-Code Divergence

Low
Confidence
96% confidence
Finding
The entry is explicitly labeled "Text To Video" and its request body lists only prompt, ratio, and duration, with no image parameter. The inline example title "create video by text or image" contradicts the documented behavior and could mislead developers about supported inputs.

Intent-Code Divergence

Low
Confidence
96% confidence
Finding
This endpoint is documented as "Text To Video" and its body schema contains only text-driven generation fields, yet the example request title says "create video by text or image." That is an active contradiction between the example documentation and the endpoint's described behavior.

Static analysis

No suspicious patterns detected.