Back to skill

Security audit

ai-video

Security checks for vulnerabilities and agentic risk

Overview

This skill is mostly a real skills.video helper, but unsafe URL options could expose a user's API key to a non-skills.video server.

Review this before installing. Only use it if you trust the publisher and can ensure the helper scripts are invoked with relative API paths and the default https://open.skills.video API base, or after the publisher adds strict HTTPS and host allowlisting so your SKILLS_VIDEO_API_KEY cannot be sent elsewhere.

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/create_and_wait.py:63
Finding

Arbitrary SSE Endpoint Allows Disclosure of the skills.video API Key and Generation Payload

Content
View full analysis
str: if endpoint.startswith("http://") or endpoint.startswith("https://"): return endpoint if not endpoint.startswith("/"): endpoint = "/" + endpoint return f"{base_url.rstrip('/')}{endpoint}" ``` ```python def run_sse( url: str, api_key: str, payload: dict[str, Any], request_timeout: float, ) -> tuple[int, str | None, Any]: req = request.Request( url, headers={ "Authorization": f"Bearer {api_key}", "Accept": "text/event-stream", "Content-Type": "application/json", }, data=json.dumps(payload).encode("utf-8"), method="POST", ) ``` ```python with request.urlopen(req, timeout=request_timeout) as resp: ``` ```python parser.add_argument("--sse-endpoint", required=True, help="SSE create endpoint path or full URL") parser.add_argument("--base-url", default="https://open.skills.video/api/v1") ``` ```python api_key = os.environ.get("SKILLS_VIDEO_API_KEY", "").strip() ``` ```python payload = load_payload(args) url = endpoint_url(args.base_url, args.sse_endpoint) emit({"event": "start", "url": url, "mode": "sse_then_poll_fallback"}) sse_rc, generation_id, terminal_payload = run_sse( url=url, api_key=api_key, payload=payload, request_timeout=args.sse_request_timeout, ) ``` ### Technical Analysis The helper expressly accepts either an endpoint path or a complete HTTP/HTTPS URL. When a complete URL is supplied, `endpoint_url()` returns it without validating the destination hostname or requiring encrypted transport. The resulting request automatically includes the `SKILLS_VIDEO_API_KEY` as a bearer credential and sends t ...[truncated 1920 chars]
Remediation
View remediation
str: if endpoint.startswith(("http://", "https://")): raise ValueError("Absolute endpoint URLs are not allowed") url = urljoin(base_url.rstrip("/") + "/", endpoint.lstrip("/")) parsed = urlsplit(url) if parsed.scheme != "https" or parsed.hostname not in TRUSTED_HOSTS: raise ValueError("Untrusted API destination") if parsed.username or parsed.password: raise ValueError("URL credentials are not allowed") return url ``` ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/wait_generation.py:81
Finding

Unrestricted Polling Base URL Allows Disclosure of the skills.video API Key

Content
View full analysis
tuple[int, Any]: url = f"{base_url.rstrip('/')}/generation/{generation_id}" req = request.Request( url, headers={ "Authorization": f"Bearer {api_key}", "Accept": "application/json", }, method="GET", ) try: with request.urlopen(req, timeout=request_timeout) as resp: raw = resp.read().decode("utf-8", errors="replace") return resp.getcode(), parse_json_or_text(raw) ``` ```python parser.add_argument("--generation-id", required=True, help="Generation id to poll") parser.add_argument("--base-url", default="https://open.skills.video/api/v1") ``` ```python api_key = os.environ.get("SKILLS_VIDEO_API_KEY", "").strip() ``` ```python http_status, payload = fetch_generation( base_url=args.base_url, generation_id=args.generation_id, api_key=api_key, request_timeout=args.request_timeout, ) ``` ### Technical Analysis The polling helper exposes `--base-url` without validating its scheme or hostname. `fetch_generation()` concatenates this value into a request URL and unconditionally attaches `SKILLS_VIDEO_API_KEY` in the `Authorization` header. Although the default URL is legitimate, a caller can replace it with any HTTP or HTTPS origin. Authenticated polling only needs access to the documented skills.video host; transmitting the service credential to arbitrary destinations exceeds the least privilege required for generation-status polling. An `http://` base URL additionally transmits the credential without transport encryption. ### Attack Path 1. An attacker causes the helper to be invoked with a malici ...[truncated 1127 chars]
Remediation
View remediation
str: parsed = urlsplit(base_url) if parsed.scheme != "https": raise ValueError("The API base URL must use HTTPS") if parsed.hostname not in TRUSTED_HOSTS: raise ValueError("Untrusted API hostname") if parsed.username or parsed.password: raise ValueError("URL credentials are not allowed") return base_url.rstrip("/") ``` Call this validation before reading or transmitting `SKILLS_VIDEO_API_KEY`. ]]>
Vulnerability Patterns
  • Rogue AgentSelf-Modification, Session Persistence
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (6)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The declared description presents a skill for constructing and executing skills.video API requests based on OpenAPI specs. However, the supplied code does not interact with OpenAPI specs, construct requests, call any API, or handle video generation workflows. Its sole function is to verify the presence of an API key in an environment variable and print instructions for obtaining and setting it. While API key validation could be a supporting utility for such a skill, this code chunk by itself materially differs from the declared primary purpose.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The declared description says the skill builds and executes skills.video video generation REST requests from OpenAPI specs. The supplied code instead focuses on post-error handling: loading an HTTP response body, extracting an error message, classifying runtime failures, producing guidance, and printing structured JSON. While this may support debugging of video API calls, it is not the described primary behavior and lacks any request-construction or execution logic from OpenAPI specs. Therefore the description does not accurately represent the actual code behavior.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The declared description suggests a general tool for building and executing skills.video video-generation REST requests from OpenAPI specifications, useful for creating, debugging, or documenting API calls. The supplied code does not parse OpenAPI specs, construct arbitrary requests, or execute video generation creation calls. Instead, it is a narrow-purpose CLI script that polls an existing generation by ID via GET /generation/{generation_id}, retries on transient errors, waits until terminal status, and reports results. This is a materially different primary purpose and adds an undeclared capability: asynchronous job polling/wait logic.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill declares capabilities that include environment access, file reads, network access, and shell execution, but it does not define any explicit tool scope or permissions boundary. In an agent environment, this increases the chance the skill will run with broader-than-necessary authority, enabling unintended API calls, file access, or command execution if the skill is invoked or adapted unsafely.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/create_and_wait.py (reported line 311)May include surrounding context.

python
}
    )

    result = subprocess.run(cmd, check=False)
    emit(
        {
            "event": "fallback_polling_end",

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · scripts/ensure_api_key.py (reported line 36)May include surrounding context.

python
"dashboard_url": "https://skills.video/dashboard/developer",
                "how_to_get_key": [
                    "Sign in at the dashboard URL.",
                    "Click 'Create API Key'.",
                    "Copy the generated key.",
                ],
                "set_env_examples": [

Static analysis

No suspicious patterns detected.