Back to skill

Security audit

Shortvideo

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real ShortVideo video-generation integration, but it needs Review because it can fetch arbitrary URLs and send credentials or media to an environment-configured server without validation.

Install only if you trust the publisher and can keep SHORTVIDEO_BASE_URL pinned to the legitimate HTTPS ShortVideo API. Do not pass internal, localhost, private-network, or cloud metadata URLs as media inputs. Treat any local files, prompts, product details, and source videos you provide as data that may be uploaded to the configured ShortVideo service, and prefer scoping the API key to this service instead of putting broad secrets in shared shell or agent config files.

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/impl.py:51
Finding
Environment-controlled API origin can receive bearer credentials and user media<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/impl.py:51-66`, `scripts/impl.py:84-106`, `scripts/impl.py:205-220`, `scripts/impl.py:256-262` - `scripts/product-to-video.py:49-64`, `scripts/product-to-video.py:145-164`, `scripts/product-to-video.py:194-209`, `scripts/product-to-video.py:240-246` - `scripts/image-to-ad-video.py:50-65`, `scripts/image-to-ad-video.py:164-183`, `scripts/image-to-ad-video.py:213-228`, `scripts/image-to-ad-video.py:259-265` - `scripts/replicate-video.py:59-74`, `scripts/replicate-video.py:164-183`, `scripts/replicate-video.py:213-228`, `scripts/replicate-video.py:259-265` - `scripts/poll-videos.py:41-56`, `scripts/poll-videos.py:69-75` **Vulnerability Type**: Unvalidated credential destination and sensitive-data transmission **Risk Level**: High ### Vulnerable Code The shared implementation demonstrates the issue: ```python def get_config() -> tuple[str, str]: """Get base_url and api_key from environment variables.""" base_url = os.environ.get("SHORTVIDEO_BASE_URL", "") api_key = os.environ.get("SHORTVIDEO_API_KEY", "") if not base_url: print("Error: SHORTVIDEO_BASE_URL environment variable not set") print("Set it with: export SHORTVIDEO_BASE_URL='https://api.shortvideo.ai'") sys.exit(1) if not api_key: print("Error: SHORTVIDEO_API_KEY environment variable not set") print("Please visit https://shortvideo.ai to get your API key") print("Set it with: export SHORTVIDEO_API_KEY='your-api-key'") sys.exit(1) return base_url.rstrip("/"), api_key ``` The environment-controlled origin is then used for authenticated media uploads: ```python def upload_file(filepath: str, upload_type: str = None) -> dict[str, Any]: """Upload a local file to OSS.""" base_url, api_key = get_config() if not os.path.isfile(filepath): return {"status": "error", "error": f"File not found: {filepath}"} file_size = os.path.getsize(filepat ...[truncated 3685 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use a compiled-in default origin such as `https://api.shortvideo.ai` rather than requiring an unrestricted origin from the environment. 2. If custom origins are operationally necessary, require an explicit development-only opt-in and validate the parsed URL: - Require the `https` scheme. - Allowlist expected hostnames. - Reject embedded usernames or passwords. - Reject fragments, unexpected ports, and malformed origins. - Normalize the hostname before comparison. 3. Separate development credentials from production credentials. Never send a production API key to a custom endpoint. 4. Disable redirects for authenticated requests, or manually follow redirects only after confirming that the destination has the same trusted origin. Strip the `Authorization` header on any cross-origin redirect. 5. Present the validated upload destination to the user before transmitting local media. 6. Apply server-side API-key scopes and credit limits so that a leaked key has the narrowest possible permissions. 7. Consolidate HTTP configuration into one hardened client module so all five scripts consistently enforce the same destination policy. 8. Add automated tests that verify rejection of HTTP URLs, unapproved hosts, embedded credentials, unusual ports, and cross-origin redirects. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/product-to-video.py:68
Finding
Unrestricted media URL fetching permits SSRF and external relaying of internal responses<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/product-to-video.py:68-70`, `scripts/product-to-video.py:101-133`, `scripts/product-to-video.py:439-452` - `scripts/image-to-ad-video.py:69-71`, `scripts/image-to-ad-video.py:105-152`, `scripts/image-to-ad-video.py:458-470` - `scripts/replicate-video.py:78-80`, `scripts/replicate-video.py:110-152`, `scripts/replicate-video.py:415-445` **Vulnerability Type**: Server-side request forgery and sensitive-content relay **Risk Level**: High ### Vulnerable Code The product-to-video script classifies any HTTP or HTTPS string as a downloadable media URL: ```python def is_url(path: str) -> bool: """Check if path is a URL.""" return path and path.startswith(("http://", "https://")) ``` It then fetches that URL without validating the hostname, resolved address, scheme security, or redirects: ```python def download_image_from_url(url: str, timeout: int = 30) -> dict[str, Any]: """Download image from URL to a temporary file.""" try: print(f" Downloading from URL: {url}") response = requests.get(url, timeout=timeout, stream=True) response.raise_for_status() # Check file size content_length = response.headers.get("Content-Length") if content_length and int(content_length) > MAX_FILE_SIZE: return {"status": "error", "error": f"Image too large: {content_length} bytes (max: {MAX_FILE_SIZE})"} # Get extension ext = get_extension_from_url(url) # Create temp file temp_dir = tempfile.gettempdir() filename = f"shortvideo_{uuid.uuid4().hex}{ext}" temp_path = os.path.join(temp_dir, filename) # Download to temp file downloaded_size = 0 with open(temp_path, "wb") as f: for chunk in response.iter_content(chunk_size=8192): if chunk: f.write(chunk) downloaded_size += len(chunk) if downl ...[truncated 3786 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Prefer accepting local files or previously issued ShortVideo asset identifiers instead of fetching arbitrary URLs. 2. If remote fetching is required, enforce a strict URL policy: - Permit only HTTPS. - Reject embedded credentials and nonstandard ports unless explicitly required. - Allowlist trusted media hosts where feasible. 3. Resolve the hostname and reject all loopback, private, link-local, multicast, unspecified, reserved, and documentation ranges for both IPv4 and IPv6. 4. Explicitly block cloud metadata hosts and addresses, including link-local metadata services. 5. Disable automatic redirects with `allow_redirects=False`. If redirects are necessary, impose a small redirect limit and repeat complete scheme, hostname, port, DNS, and IP validation for every hop. 6. Protect against DNS rebinding by ensuring that the validated address is the address used for the connection, or place downloads behind a hardened egress proxy with network-level filtering. 7. Enforce expected MIME types and validate file signatures rather than trusting URL extensions. Abort on a non-image response for image parameters and on a non-video response for video parameters. 8. Retain streaming size checks, but also impose connection/read timeouts and a maximum decompressed response size. 9. Do not automatically upload fetched content until validation succeeds. 10. Add tests covering loopback, RFC1918 ranges, IPv6 local addresses, metadata endpoints, decimal/hexadecimal IP representations, DNS aliases, redirect chains, and DNS-rebinding scenarios. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (55)

Tainted flow: 'url' from os.environ.get (line 261, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
"""
    try:
        print(f"  Downloading from URL: {url}")
        response = requests.get(url, timeout=timeout, stream=True)
        response.raise_for_status()

        # Check content type
Confidence
98% confidence
Finding
The script downloads arbitrary user-supplied image URLs with requests.get, which creates a classic SSRF primitive. In the context of an agent skill, a user can cause the runtime to make outbound requests to internal services, cloud metadata endpoints, or other sensitive network locations, and the downloaded response is then processed and uploaded onward.

Tainted flow: 'url' from os.environ.get (line 261, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
with open(filepath, "rb") as f:
            files = {"file": (filename, f)}
            data = {"type": upload_type}
            response = requests.post(url, files=files, data=data, headers=headers, timeout=60)
            response.raise_for_status()
            result = response.json()
Confidence
90% confidence
Finding
The upload endpoint is also derived from SHORTVIDEO_BASE_URL and receives file contents together with the Authorization token. If the endpoint is redirected to an untrusted server, both uploaded user data and API credentials can be exfiltrated.

Tainted flow: 'url' from os.environ.get (line 261, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
}

    try:
        response = requests.post(url, json=payload, headers=headers, timeout=30)
        response.raise_for_status()
        result = response.json()
Confidence
90% confidence
Finding
The destination for task creation is derived from SHORTVIDEO_BASE_URL and used with an Authorization bearer token. If that environment variable is misconfigured or attacker-controlled, the skill will send API requests and credentials to an arbitrary host, enabling credential exfiltration or unauthorized external transmission.

Tainted flow: 'url' from os.environ.get (line 261, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
headers = {"Authorization": f"Bearer {api_key}"}

    try:
        response = requests.get(url, headers=headers, timeout=30)
        response.raise_for_status()
        result = response.json()
Confidence
90% confidence
Finding
The video fetch endpoint is built from an environment-controlled base URL and includes the bearer token in the request headers. A malicious or compromised base URL setting would redirect authenticated requests to an attacker-controlled server and expose credentials or job metadata.

Tainted flow: 'url' from os.environ.get (line 258, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
with open(filepath, "rb") as f:
            files = {"file": (filename, f)}
            data = {"type": upload_type}
            response = requests.post(url, files=files, data=data, headers=headers, timeout=60)
            response.raise_for_status()
            result = response.json()
Confidence
98% confidence
Finding
This upload path sends local file contents plus the API bearer token to a URL derived from SHORTVIDEO_BASE_URL with no host validation. If the environment variable is tampered with, the skill can exfiltrate arbitrary local files selected for upload directly to an attacker-controlled endpoint, making this especially dangerous in an agent context where local workspace files may contain sensitive data.

Tainted flow: 'url' from os.environ.get (line 258, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
payload = {"user_id": user_id, "type": task_type, "args": args}

    try:
        response = requests.post(url, json=payload, headers=headers, timeout=30)
        response.raise_for_status()
        result = response.json()
Confidence
97% confidence
Finding
The request target is derived from SHORTVIDEO_BASE_URL, and the code sends the Authorization bearer token and user-supplied task data to that host without validating the scheme, domain, or allowlist. If an attacker can influence the environment variable, they can redirect requests and exfiltrate API credentials and submitted content to an arbitrary server, which is a real SSRF/credential-leak style issue in agent or CI environments.

Tainted flow: 'url' from os.environ.get (line 258, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
headers = {"Authorization": f"Bearer {api_key}"}

    try:
        response = requests.get(url, headers=headers, timeout=30)
        response.raise_for_status()
        result = response.json()
Confidence
96% confidence
Finding
The fetch endpoint URL is built from an untrusted environment-derived base URL and called with the bearer token in the Authorization header. In a hostile runtime, changing SHORTVIDEO_BASE_URL would cause authenticated requests to be sent to an attacker-controlled service, exposing secrets and potentially enabling internal network access patterns.

Tainted flow: 'url' from os.environ.get (line 71, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
headers = {"Authorization": f"Bearer {api_key}"}

    try:
        response = requests.get(url, headers=headers, timeout=30)
        response.raise_for_status()
        result = response.json()
Confidence
94% confidence
Finding
The script builds the request URL from SHORTVIDEO_BASE_URL, an environment variable, and then sends the bearer API key to that destination in the Authorization header. If an attacker can influence the environment or deployment configuration, they can redirect requests to an attacker-controlled host and exfiltrate the API key, making this a real SSRF/credential-leak risk rather than a harmless configuration issue.

Tainted flow: 'url' from os.environ.get (line 242, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
"""Download image from URL to a temporary file."""
    try:
        print(f"  Downloading from URL: {url}")
        response = requests.get(url, timeout=timeout, stream=True)
        response.raise_for_status()

        # Check file size
Confidence
95% confidence
Finding
The script accepts a user-supplied --image URL and fetches it directly with requests.get, enabling server-side request forgery if an attacker can influence that argument. In an agent skill context, this is more dangerous because the agent may be induced to access internal services, cloud metadata endpoints, or other sensitive network locations on behalf of the user or runtime environment.

Tainted flow: 'url' from os.environ.get (line 242, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
with open(filepath, "rb") as f:
            files = {"file": (filename, f)}
            data = {"type": upload_type}
            response = requests.post(url, files=files, data=data, headers=headers, timeout=60)
            response.raise_for_status()
            result = response.json()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'url' from os.environ.get (line 242, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
}

    try:
        response = requests.post(url, json=payload, headers=headers, timeout=30)
        response.raise_for_status()
        result = response.json()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'url' from os.environ.get (line 242, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
headers = {"Authorization": f"Bearer {api_key}"}

    try:
        response = requests.get(url, headers=headers, timeout=30)
        response.raise_for_status()
        result = response.json()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'url' from os.environ.get (line 261, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
"""
    try:
        print(f"  Downloading from URL: {url}")
        response = requests.get(url, timeout=timeout, stream=True)
        response.raise_for_status()

        # Check file size
Confidence
96% confidence
Finding
The script downloads arbitrary user-supplied URLs with requests.get before uploading them to the ShortVideo service. In an agent skill context this creates an SSRF-style remote fetch primitive that can access internal services, cloud metadata endpoints, or other network locations reachable from the runtime, which is broader than ordinary video-task submission.

Tainted flow: 'url' from os.environ.get (line 261, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
with open(filepath, "rb") as f:
            files = {"file": (filename, f)}
            data = {"type": upload_type}
            response = requests.post(url, files=files, data=data, headers=headers, timeout=120)
            response.raise_for_status()
            result = response.json()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'url' from os.environ.get (line 261, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
}

    try:
        response = requests.post(url, json=payload, headers=headers, timeout=30)
        response.raise_for_status()
        result = response.json()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'url' from os.environ.get (line 261, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
headers = {"Authorization": f"Bearer {api_key}"}

    try:
        response = requests.get(url, headers=headers, timeout=30)
        response.raise_for_status()
        result = response.json()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Agent Config Directory Access

High
Category
Agent Snooping
Content
export SHORTVIDEO_API_KEY="your-api-key-here"
```

Or add to `~/.claude/settings.json`:

```json
{
Confidence
90% confidence
Finding
Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description says the skill supports three major capabilities: product-to-video, image-to-ad-video, and replicate-video. However, this code chunk is narrowly focused on image-to-ad-video only. The hardcoded task type is 'vidu/image-to-ad-video-v2', the CLI only accepts images plus duration/aspect ratio/prompt, and there is no handling of product metadata/catalog inputs or existing video replication inputs. The network access to ShortVideo API and OSS upload is consistent with the declared domain and is a supporting detail, not an undeclared capability. The mismatch is therefore that the declared purpose is broader than the implemented behavior in the supplied code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The code only reads configuration from environment variables, calls a fetch endpoint (/api/video/fetch) with provided video IDs, polls for completion, summarizes statuses, and prints result metadata/URLs. Its primary purpose is monitoring and retrieving results for previously created videos, not generating videos. While this may be part of a larger video workflow, the declared description for this skill specifically emphasizes creation capabilities that are absent from the supplied code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The code’s primary purpose is consistent with one portion of the description—creating product videos via the ShortVideo service—but it does not support the other two prominently declared capabilities: image-to-ad-video and replicate-video. The script contains no handlers, arguments, endpoints, or task types for those modes. Its behavior is therefore materially narrower than the declared description. The URL download, local file upload, OSS upload, and polling logic are supporting implementation details for product-to-video and are not themselves problematic undeclared capabilities.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The description presents the skill as a broad video-generation capability with three supported functions: product-to-video, image-to-ad-video, and replicate-video. However, the code shown is only for `scripts/replicate-video.py` and exclusively creates tasks of type `vidu/replicate-video`. It requires an input video, supports optional product/model images and prompt text, uploads assets, and polls for output videos. There is no code here for generating videos directly from products alone or for creating ad videos from images. The network access to ShortVideo API and file handling are consistent with the declared domain, so the mismatch is not about permissions/resources but about the represented scope and primary supported capabilities. Therefore this chunk does not accurately match the full declared description.

Agent Config Directory Access

High
Category
Agent Snooping
Content
### Method 1: Claude Code Config

Add to `~/.claude/settings.json`:

```json
{
Confidence
90% confidence
Finding
The skill instructs users to place API credentials in agent-accessible configuration under ~/.claude/settings.json. While not inherently malicious, referencing agent config paths increases the chance that an agent with filesystem access may read or manipulate credential-bearing files, expanding exposure if the broader environment is compromised or if other skills misuse config access.

Lp1

High
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The script performs multiple outbound network operations—downloading arbitrary URLs, uploading files, creating tasks, and polling results—without declared network permission coverage. In an agent setting, undeclared network access materially increases risk because the skill can transmit user data or secrets externally without transparent consent.

Lp1

High
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The script performs multiple outbound network operations—downloading arbitrary URLs, uploading files, creating tasks, and polling results—without declared network permission coverage. In an agent setting, undeclared network access materially increases risk because the skill can transmit user data or secrets externally without transparent consent.

Session Persistence

Medium
Category
Rogue Agent
Content
# ShortVideo Skills

Create videos using ShortVideo API with Claude Code / OpenClaw skills.

A collection of Python scripts and Claude Code skills for generating marketing videos, ad videos, and replicating existing videos using the ShortVideo backend API.
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.

Static analysis

No suspicious patterns detected.