Back to skill

Security audit

runninghub-video

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent RunningHub video-generation purpose, but it uploads local files, uses an API key, downloads outputs, and contains unsafe handling of remote response values that users should review before installing.

Install only if you are comfortable sending selected images and prompts to RunningHub and storing a RunningHub API key for the helper to use. Before use, confirm each local file upload and output directory, avoid sensitive images, and prefer a patched version that sanitizes downloaded filenames and blocks credential-bearing 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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/runninghub_video.py:268
Finding
Server-Controlled Task Identifier Used in Output File Path<![CDATA[ ## Vulnerability Details **File Location**: `scripts/runninghub_video.py`, lines 268-277 **Vulnerability Type**: Path traversal and arbitrary file overwrite **Risk Level**: Medium ### Vulnerable Code ```python def save_outputs(result: dict, out_dir: Path) -> list[Path]: results = result.get("results") or [] if not results: raise SystemExit( "Task finished but no results were returned:\n" f"{json.dumps(result, ensure_ascii=False, indent=2)}" ) out_dir.mkdir(parents=True, exist_ok=True) task_id = result.get("taskId", "runninghub-task") saved_paths: list[Path] = [] for index, item in enumerate(results): url = item.get("url") if not url: continue extension = guess_extension(item, index) destination = out_dir / f"{task_id}-{index + 1}.{extension}" download_result(str(url), destination) ``` The associated extension is also derived from remote response data without an allowlist: ```python def guess_extension(item: dict, index: int) -> str: output_type = item.get("outputType") if output_type: suffix = str(output_type).strip().lstrip(".") return suffix or "bin" parsed = urllib.parse.urlparse(str(item.get("url", ""))) name = Path(parsed.path).name suffix = Path(name).suffix.lstrip(".") if suffix: return suffix return "mp4" if index == 0 else "bin" ``` ### Technical Analysis The `taskId`, `outputType`, and result URL are supplied by the remote API. The code incorporates `taskId` and the derived extension directly into a local path without rejecting path separators, `..` components, control characters, or other unsafe filename content. The destination is not resolved and checked against the intended output directory before `download_result()` opens it with `"wb"`. A traversal-bearing `taskId`, such as `../../target`, can therefore make the resulting path resolve outside `out_dir`. Exploit ...[truncated 1493 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not use a remote task identifier directly as a filename. Generate a local identifier, such as a UUID. - If the task identifier must remain visible, replace every character outside a strict allowlist such as `[A-Za-z0-9_-]`. - Map output types through a fixed extension allowlist, for example: ```python ALLOWED_EXTENSIONS = { "mp4": "mp4", "webm": "webm", "png": "png", "jpg": "jpg", "jpeg": "jpg", } ``` - Resolve and validate every destination before opening it: ```python base = out_dir.resolve() destination = (base / safe_name).resolve() if destination.parent != base: raise SystemExit("Unsafe output path returned by remote service") ``` - Use exclusive creation where overwriting is unnecessary, or require explicit confirmation before replacing an existing file. - Download to a temporary file inside the validated output directory and atomically rename it only after the download succeeds. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/runninghub_video.py:70
Finding
Bearer API Key May Be Forwarded Across Redirect Origins<![CDATA[ ## Vulnerability Details **File Location**: `scripts/runninghub_video.py`, lines 70-81 and 215-232 **Vulnerability Type**: Credential disclosure through unrestricted HTTP redirects **Risk Level**: Medium ### Vulnerable Code ```python def request_json( url: str, *, method: str = "POST", payload: dict | None = None, headers: dict[str, str] | None = None, data: bytes | None = None, timeout: int = 180, ) -> dict: request_headers = {"Accept": "application/json"} if headers: request_headers.update(headers) request_data = data if payload is not None: request_data = json.dumps(payload, ensure_ascii=False).encode("utf-8") request_headers.setdefault("Content-Type", "application/json") request = urllib.request.Request(url, data=request_data, headers=request_headers, method=method) try: with urllib.request.urlopen(request, timeout=timeout) as response: raw = response.read() ``` Authenticated calls provide the sensitive header to this generic request function: ```python def submit_task(api_key: str, endpoint: str, payload: dict) -> dict: response = request_json( endpoint, method="POST", payload=payload, headers={"Authorization": f"Bearer {api_key}"}, ) if response.get("errorMessage") and not response.get("taskId"): raise SystemExit(json.dumps(response, ensure_ascii=False, indent=2)) return response def query_task(api_key: str, task_id: str) -> dict: return request_json( QUERY_ENDPOINT, method="POST", payload={"taskId": task_id}, headers={"Authorization": f"Bearer {api_key}"}, ) ``` ### Technical Analysis The helper correctly sends the API key to hardcoded HTTPS RunningHub endpoints, which is necessary for the declared functionality. However, `urllib.request.urlopen()` uses automatic redirect handling, while the code does not validate the destination origin or explic ...[truncated 1683 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Disable automatic redirects for requests carrying credentials, or implement a custom redirect handler. - Permit redirects only when all of the following remain true: - The scheme is `https`. - The hostname is in an explicit RunningHub allowlist. - The destination uses the expected port. - Remove `Authorization` whenever the scheme, hostname, or port changes. - Prefer rejecting redirects on authenticated API endpoints unless RunningHub explicitly documents them as required. - Separate authenticated API requests from unauthenticated media downloads so that redirect policies cannot be accidentally shared. - Add tests covering same-origin redirects, cross-origin redirects, HTTPS-to-HTTP redirects, and credential-header stripping. - Rotate the API key immediately if logs or network evidence indicate it may already have been exposed. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (4)

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill advertises and operationalizes network access, local file handling, and likely environment-based API key use, but the manifest does not declare any tool scope or permissions boundary. That omission weakens governance and user awareness, making it easier for an agent to perform file reads and external transfers without clear policy constraints or review.

Vague Triggers

Medium
Confidence
90% confidence
Finding
The trigger description is broad enough to activate on common requests involving video generation, uploads, polling, or related model names, which can cause the skill to run in contexts the user did not specifically intend. In this skill, accidental invocation is more dangerous because activation can lead to local file upload and automatic downloading of generated content.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill explicitly instructs automatic upload of local files to a third-party endpoint and automatic download of returned media to local storage, but it does not require an explicit warning or consent checkpoint. This creates a real data exfiltration and local-write risk, especially if users provide sensitive images or if broad triggering causes the skill to act on files the user did not expect to leave the system.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The reference explicitly instructs the skill to upload local media files to a third-party RunningHub endpoint and notes that the returned URLs are reusable, but it provides no warning about privacy, consent, retention, or handling of sensitive local content. In a skill that may automatically upload user-provided local files, this omission can lead to unintended exfiltration of personal or confidential data to an external service.

Static analysis

No suspicious patterns detected.