Back to skill

Security audit

Media.io Video to Video API

Security checks for vulnerabilities and agentic risk

Overview

This Media.io API skill is mostly purpose-aligned, but its authenticated router has an under-scoped URL parameter handling flaw that could send a user's API key to unintended Media.io API paths.

Review before installing. Only use this with a Media.io API key you intend to expose to Media.io requests, avoid invoking it with task IDs or parameters from untrusted content, and prefer a version that validates and URL-encodes path parameters before sending authenticated requests.

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:44
Finding
Unvalidated Path Parameter Substitution Allows Same-Origin API Path Manipulation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/skill_router.py`, lines 44–53 **Vulnerability Type**: Improper validation and encoding of URL path parameters **Risk Level**: Medium ### Vulnerable Code ```python 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' } if '{' in url: for k, v in params.items(): url = url.replace(f'{{{k}}}', str(v)) 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 endpoint allowlist check is performed before user-supplied path parameters are substituted into the URL. The `Task Result` endpoint is defined as: ```text https://openapi.media.io/generation/result/{task_id} ``` The implementation inserts `task_id` using direct string replacement without URL encoding or validation. Consequently, reserved URL characters and path-navigation sequences—such as `/`, `..`, `?`, and `#`—can alter the structure or interpretation of the final request URL. Although the original endpoint is restricted to HTTPS on `openapi.media.io`, this validation does not ensure that the final substituted URL still targets the intended `/generation/result/<task_id>` API route. HTTP client URL normalization may resolve dot segments before sending the request. Query or fragment delimiters may also modify the effective request target. The host remains constrained to `openapi.media.io`, so this is not a general server-side request forgery vulnerability. However, it creates a same-origin endpoint-confusion condition and may cause the caller's API key to be used against an unintended Media.io path. ### Attack Path 1. An attacker supplies parameters to an invocation of the ...[truncated 1602 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate each path parameter according to its expected format. For `task_id`, use a strict allowlist such as an API-documented UUID or alphanumeric identifier pattern: ```python import re task_id = params.get("task_id") if not isinstance(task_id, str) or not re.fullmatch(r"[A-Za-z0-9_-]{1,128}", task_id): return {"error": "Invalid task_id format."} ``` 2. Percent-encode path parameter values rather than inserting them directly: ```python from urllib.parse import quote encoded_value = quote(str(v), safe="") url = url.replace(f"{{{k}}}", encoded_value) ``` 3. Reject missing and unresolved placeholders before making the request. 4. Parse and validate the final URL after all substitutions. Confirm the scheme, hostname, port, query, fragment, and expected path prefix: ```python final = urlparse(url) if ( final.scheme != "https" or final.hostname != "openapi.media.io" or final.port not in (None, 443) or final.query or final.fragment or not final.path.startswith("/generation/result/") ): return {"error": "Invalid final endpoint URL."} ``` 5. Prefer explicit request builders for each supported API instead of generic textual URL-template replacement. This allows parameter schemas and permitted routes to be enforced independently. 6. Add tests covering slash characters, dot segments, percent-encoded traversal, query delimiters, fragment delimiters, empty values, oversized identifiers, and non-string parameter values. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (3)

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 --url https://openapi.media.io/user/credits --header 'Content-Type: application/json' --header 'X-API-KEY: <api-key>' --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\"}],\"title\":\"Response\"}",
    "api_code_demo": "{\"list\":[{\"code\":\"0\",\"describe\":\"Success\"}],\"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.

Context-Inappropriate Capability

Medium
Confidence
83% confidence
Finding
With no manifest available, there is no declared purpose that would justify this skill's ability to retrieve an API key from the environment and send authenticated HTTPS requests to an external service. The code is effectively a generic API router/invoker, which is a materially powerful capability absent any stated scope.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
This code sends request data to a remote HTTPS endpoint using an API key header and a JSON body, but there is no confirmation prompt, logging, print statement, or explanatory comment/docstring warning the user that data will be transmitted externally. For a code file, outbound network transmission of user-supplied or system-provided data should have some visible disclosure unless the warning is documented elsewhere or clearly implied by the skill's stated purpose, which is not established in this file alone.

Static analysis

No suspicious patterns detected.