Back to skill

Security audit

STH Video Template Generation

Security checks for vulnerabilities and agentic risk

Overview

This skill matches its video-generation purpose, but it needs review because it can automatically send data to outside services, upload media, and modify a PostgreSQL database with weak safeguards.

Install only in a controlled staging or production environment with least-privilege PostgreSQL and GCS credentials. Treat CSV input, database URLs, and MCP responses as untrusted; require a preview/dry run and explicit approval before database updates or uploads. Do not use production API keys or broad database accounts until SQL parameterization, URL allowlisting, and clearer rollback controls are added.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
sth_video_generator.py:84
Finding
SQL Injection Through Unparameterized Database Queries<![CDATA[ ## Vulnerability Details **File Location**: `sth_video_generator.py:84`, `sth_video_generator.py:99`, `sth_video_generator.py:399-404`; equivalent patterns also occur in `sth_video_generator_parallel.py:167,190,436-438` and multiple scripts under `scripts/` **Vulnerability Type**: SQL injection **Risk Level**: High ### Vulnerable Code ```python def get_template_data(template_id: str) -> Optional[Dict[str, str]]: """Fetch template data from song_templates table.""" query = f"SELECT image_url, generate_video_prompt, song_type_id FROM song_templates WHERE id = '{template_id}';" result = run_psql(query) ``` ```python def get_audio_mix_url(song_type_id: str) -> Optional[str]: """Fetch audio mix URL from song_types table.""" query = f"SELECT amix_url FROM song_types WHERE id = '{song_type_id}';" result = run_psql(query) return result if result else None ``` ```python def update_template_urls(template_id: str, video_url: str) -> bool: """Update the song_templates table with video URLs.""" query = f"""UPDATE song_templates SET video_url = '{video_url}', video_url_seedream_v4 = '{video_url}' WHERE id = '{template_id}';""" result = run_psql(query) return result is not None ``` Additional confirmed locations include: - `sth_video_generator_parallel.py:167` - `sth_video_generator_parallel.py:190` - `sth_video_generator_parallel.py:436-438` - `scripts/batch_processor.py:53,63` - `scripts/check_csv_over_12s.py:62,71,80` - `scripts/filter_over_12s.py:62,71` - `scripts/filter_templates.py:60,69` - `scripts/rerun_over_12s.py:62,87,96,105,114,137` - `scripts/resume_filtered_12s.py:54,59` ### Technical Analysis The code builds SQL statements by directly interpolating values into quoted SQL literals. The `template_id` value comes from a user-supplied CSV file. Other interpolated values come from database records or the external MCP service. The resulting query is pas ...[truncated 2127 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace `psql -c` string construction with a PostgreSQL library such as `psycopg` or `psycopg2`. 2. Parameterize every value, including identifiers, song-type IDs, and URLs: ```python with conn.cursor() as cursor: cursor.execute( """ SELECT image_url, generate_video_prompt, song_type_id FROM song_templates WHERE id = %s """, (template_id,) ) ``` ```python with conn.cursor() as cursor: cursor.execute( """ UPDATE song_templates SET video_url = %s, video_url_seedream_v4 = %s WHERE id = %s """, (video_url, video_url, template_id) ) ``` 3. Apply the parameterized approach consistently to all affected maintenance scripts. `scripts/sync_template_data.py:46-50` already demonstrates an appropriate parameterized query pattern. 4. Validate IDs before database use. If IDs are UUIDs, parse them with `uuid.UUID`; otherwise, enforce a strict documented character set and length. 5. Grant the runtime database account only required `SELECT` and narrowly scoped `UPDATE` permissions. 6. Use explicit transactions and roll back on any failure. 7. Add regression tests containing quotes, comment markers, statement separators, and malformed identifiers. 8. Avoid logging complete SQL statements containing externally derived values. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
sth_video_generator.py:295
Finding
Unrestricted Remote URL Fetching from Mutable Data Sources<![CDATA[ ## Vulnerability Details **File Location**: `sth_video_generator.py:295-296` and `sth_video_generator.py:343-344`; equivalent behavior occurs in `sth_video_generator_parallel.py:209-210,460-461` and `scripts/resume_filtered_12s.py:39` **Vulnerability Type**: Unrestricted URL fetching and unsafe processing of remote media **Risk Level**: Medium ### Vulnerable Code ```python # Download audio file with tempfile.NamedTemporaryFile(suffix='.mp3', delete=False) as f: temp_path = f.name download_cmd = ['curl', '-s', '-L', '-o', temp_path, audio_url] subprocess.run(download_cmd, timeout=60, check=True) ``` ```python # Download video print(f" Downloading video for trimming...") download_cmd = ['curl', '-s', '-L', '-o', temp_in, video_url] subprocess.run(download_cmd, timeout=120, check=True) ``` The downloaded files are subsequently passed to native media-processing tools: ```python probe_cmd = [ 'ffprobe', '-v', 'error', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', temp_path ] ``` ```python trim_cmd = [ 'ffmpeg', '-y', '-i', temp_in, '-t', str(duration), '-c:v', 'libx264', '-c:a', 'aac', '-shortest', temp_out ] ``` ### Technical Analysis Audio URLs originate from PostgreSQL, while generated-video URLs may originate from the external MCP service. These values are passed to `curl` without validating: - The URL scheme. - The destination hostname. - The resolved IP address. - Redirect destinations. - The response size. - The response content type. - Whether the destination is an internal or metadata service. The use of an argument array prevents shell command injection. However, it does not prevent curl from accessing arbitrary destinations or supported protocols. The `-L` option follows redirects without revalidating each destination. Downloaded content is processed by `ffprobe` or `ffmpeg`, increasing the attack surface because those native parsers handle complex, ...[truncated 1698 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Accept only `https` URLs. 2. Maintain an explicit allowlist of expected media-storage hostnames. 3. Resolve hostnames before connecting and reject loopback, private, link-local, multicast, reserved, and cloud metadata address ranges. 4. Revalidate the scheme, hostname, and resolved address after every redirect, or disable redirects unless specifically required. 5. Enforce maximum response sizes while streaming downloads. 6. Set strict connect, read, and total timeouts. 7. Validate response `Content-Type` and verify the downloaded file signature before invoking media tools. 8. Keep ffmpeg and ffprobe patched and run them under a restricted account or sandbox with: - No unnecessary network access. - Limited CPU and memory. - Limited temporary-disk quota. - No access to service-account keys or unrelated files. 9. Use a Python HTTP client with centralized URL-validation controls rather than invoking curl repeatedly. 10. Delete partial temporary files after every failure and set restrictive file permissions. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
sth_video_generator.py:134
Finding
MCP API Key Exposed in Subprocess Command-Line Arguments<![CDATA[ ## Vulnerability Details **File Location**: `sth_video_generator.py:134-142` and `sth_video_generator.py:220-228`; equivalent behavior occurs in `sth_video_generator_parallel.py:268-276,366-374` **Vulnerability Type**: Sensitive credential exposure through process arguments **Risk Level**: Medium ### Vulnerable Code ```python cmd = [ 'curl', '-s', '-X', 'POST', '-H', f'X-API-Key: {MCP_CONFIG["api_key"]}', '-H', 'Content-Type: application/json', '-H', 'Accept: application/json, text/event-stream', '-d', json.dumps(payload), MCP_CONFIG['endpoint'] ] ``` The same construction is used for both video-creation requests and job polling. ### Technical Analysis When the MCP API key is configured, it becomes part of curl's process argument vector. Depending on operating-system policy and deployment configuration, command-line arguments may be visible through: - Process inspection utilities. - `/proc` process metadata. - Monitoring and observability agents. - Debugging or incident-response tools. - Process accounting and audit systems. The code does not directly print the header, and no real API key is hard-coded in the repository. Nevertheless, placing a credential in `argv` unnecessarily widens its exposure to other local principals and operational systems. The documented configuration is also inconsistent with the implementation: `SKILL.md` states that `STH_MCP_API_KEY` is read from the environment, while the reviewed generator scripts use a fixed dictionary with a blank `api_key` field. ### Attack Path 1. An operator populates `MCP_CONFIG["api_key"]` or otherwise supplies a real key to the script. 2. The script invokes curl with `X-API-Key: <secret>` as a command-line argument. 3. A local user, process-monitoring component, or diagnostic collector observes the curl process arguments. 4. The observer extracts the API key. 5. The key is reused to submit video-generation requests or query jobs at the configured MCP endpoint, sub ...[truncated 578 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace curl subprocesses with an in-process HTTP client so headers do not appear in process arguments: ```python import os import requests api_key = os.environ["STH_MCP_API_KEY"] response = requests.post( MCP_CONFIG["endpoint"], headers={ "X-API-Key": api_key, "Content-Type": "application/json", "Accept": "application/json, text/event-stream", }, json=payload, timeout=(10, 120), ) response.raise_for_status() ``` 2. Read the key from a managed secret store or the documented `STH_MCP_API_KEY` environment variable rather than editing source code. 3. Ensure request headers are never included in logs, exception traces, or telemetry. 4. Use a dedicated API key with minimum required permissions and quotas. 5. Rotate the key if it has previously been used through command-line arguments on a shared or monitored host. 6. Restrict process inspection where supported, while treating that restriction as defense in depth rather than the primary fix. 7. Add startup validation that rejects missing or placeholder credentials without printing their values. ]]>

T08 · Insecure Dependencies

Note
Location
SKILL.md:49
Finding
Unpinned Third-Party Dependency Installation<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:49-52` **Vulnerability Type**: Unpinned dependency and non-reproducible installation **Risk Level**: Low ### Vulnerable Code ```bash pip3 install google-cloud-storage ``` ### Technical Analysis The setup instructions install `google-cloud-storage` without a fixed version, lockfile, or package hash. The package name is consistent with the legitimate Google Cloud Storage client, and the audit found no evidence of typo-squatting or an intentionally malicious package. However, an unpinned installation resolves whatever package and transitive dependency versions are current at installation time. This makes deployments non-reproducible and prevents the reviewed source tree from defining the exact code that will execute. Package installation may execute build-system or installation-related code with the privileges of the user running pip. If a future package release, transitive dependency, configured package index, or distribution artifact is compromised, following these instructions could introduce unreviewed code. ### Attack Path 1. A user follows the Skill's setup instructions. 2. Pip queries its configured package index and resolves the latest compatible package and transitive dependencies. 3. A compromised, malicious, or unexpectedly changed release is selected. 4. Pip downloads and installs that code. 5. Installation or subsequent imports execute the unreviewed dependency with the user's privileges. ### Impact Assessment The potential impact is determined by the privileges used for package installation and Skill execution. It may include: - Arbitrary code execution under the installing user. - Access to environment variables and local files available to that user. - Access to the GCS service-account key when the generator runs. - Network access available to the process. - Build instability or incompatible runtime behavior. This is a supply-chain hardening deficiency rather than evidence t ...[truncated 49 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create a reviewed dependency file with exact versions. 2. Generate and verify hashes for all direct and transitive packages, for example by using `pip-tools` and `pip install --require-hashes`. 3. Use a lockfile or reproducible build mechanism appropriate to the deployment environment. 4. Install dependencies in an isolated virtual environment rather than the system Python environment. 5. Configure a trusted package index and prevent unexpected fallback to public or untrusted indexes. 6. Run automated vulnerability and license scanning against the resolved dependency set. 7. Review and update pinned versions through a controlled maintenance process. 8. Document the supported Python, `google-cloud-storage`, ffmpeg, and PostgreSQL client versions. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (68)

External Script Fetching

High
Category
Supply Chain
Content
### Request
```bash
curl -s -X POST \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Missing User Warnings

High
Confidence
92% confidence
Finding
The script downloads generated video and uploads the derived output to a public cloud bucket URL without any explicit approval, disclosure, or policy guardrails. In a skill/agent context, undisclosed external transfer of media materially increases data exfiltration and privacy risk, especially because the uploaded object becomes publicly addressable via `public_url`.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The README explicitly describes a workflow that fetches data from PostgreSQL, sends inputs to an external MCP endpoint, and updates persistent database fields, but it does not warn operators that running the tool will transmit template data over the network and modify production-like records. This can lead to unintended data disclosure or accidental database changes by users who reasonably assume a local generation script is non-destructive.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill instructs the agent to process user-supplied CSV input and then update a PostgreSQL-backed production data store, but it does not warn about the destructive or state-changing nature of those actions, require confirmation, or define validation/approval boundaries. In an agent setting, this increases the risk of accidental modification of records, incorrect status updates, or unintended bulk changes triggered by malformed or malicious CSV content.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The document provides authenticated example requests to an external endpoint and shows use of an API key plus job identifiers, but it does not warn users that running the examples transmits sensitive credentials and identifiers off-host. In a skill context, users may copy-paste commands directly, increasing the chance of inadvertent credential exposure or disclosure of internal job metadata to a third-party service.

External Transmission

Medium
Category
Data Exfiltration
Content
### Request
```bash
curl -s -X POST \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
Confidence
92% confidence
Finding
This example performs a POST request to an external domain with an authentication header and job-specific data. Even though it is documentation, the example facilitates outbound transmission of sensitive information if copied verbatim, which is risky when the skill offers no caution about external data disclosure.

External Transmission

Medium
Category
Data Exfiltration
Content
## Example 4: Extract URL Only (if present)

```bash
curl -s -X POST \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
Confidence
91% confidence
Finding
This curl pipeline again sends authenticated data to the external MCP service, then processes the response locally to extract a URL. The added convenience of piping output may encourage blind execution, while the core risk remains exposure of API keys and job identifiers to an external service without adequate warning.

Context-Inappropriate Capability

Medium
Confidence
98% confidence
Finding
The skill issues database queries by interpolating CSV-derived IDs directly into SQL strings and then executes them through `psql`. If an attacker can modify `input.csv`, this enables SQL injection against the local PostgreSQL database, potentially exposing or altering application data.

Missing User Warnings

Medium
Confidence
83% confidence
Finding
The code reads the database password from configuration and injects it into the subprocess environment for `psql`. While this is part of database access, there is no docstring, comment, or user-facing disclosure explaining that credentials are being used and passed to an external command.

Context-Inappropriate Capability

Medium
Confidence
89% confidence
Finding
The script fetches media URLs from the database and passes them directly to `ffprobe`, causing implicit access to external resources. In this context, that is dangerous because the skill has no controls on destination, allowing attacker-influenced URLs to trigger network requests or media parser exposure.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def get_audio_duration(audio_url):
    try:
        cmd = ['ffprobe', '-v', 'error', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', audio_url]
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
        return float(result.stdout.strip()) if result.returncode == 0 else 0
    except: return 0
Confidence
84% confidence
Finding
`ffprobe` is invoked on `audio_url` values retrieved from the database without validation. This can trigger outbound network access to attacker-controlled URLs or processing of malicious media inputs, creating SSRF-style behavior and expanding attack surface through a complex external parser.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The batch processor does more than read records: it creates temporary files, executes another script, and rewrites the input CSV with statuses. In the absence of any manifest or declared purpose, this combination of orchestration and file mutation is an unjustified capability for an unknown-purpose skill.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
f.write(f"Song Template ID,Status\n{template_id},Pending\n")
            
        try:
            subprocess.run(['python3', GENERATOR_SCRIPT, tmp_csv], check=True)
            # Read status back
            with open(tmp_csv, 'r') as f:
                tmp_row = list(csv.DictReader(f))[0]
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
This block writes results back to `INPUT_CSV`, replacing the original file contents. Although the script logs progress, it does not clearly warn users beforehand that the input file itself will be overwritten, which is a safety-relevant file modification.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The code performs network/media probing on URLs fetched from the database without validation, user confirmation, or any trust boundary checks. In this skill context, the URL source is not inherently safe, so ffprobe may be induced to make outbound requests to attacker-chosen endpoints, enabling SSRF, internal network reconnaissance, or interaction with sensitive local services.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
'-of', 'default=noprint_wrappers=1:nokey=1',
            audio_url
        ]
        result = subprocess.run(probe_cmd, capture_output=True, text=True, timeout=30)
        return float(result.stdout.strip()) if result.returncode == 0 else None
    except:
        return None
Confidence
90% confidence
Finding
The script passes a database-derived URL directly to ffprobe, which can trigger outbound network access or access to unexpected protocols/resources. Even without shell injection, this creates an SSRF-style primitive and can be abused to probe internal services or cause the host to fetch attacker-controlled content if the database value is influenced by an attacker.

Missing User Warnings

Medium
Confidence
85% confidence
Finding
The script sets the PGPASSWORD environment variable and invokes psql, which is a sensitive credential-handling operation. Although the code performs the action directly, there is no docstring, comment, or user-facing warning explaining that database credentials are being used and transmitted to a subprocess.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The script performs media probing on URLs obtained indirectly from database content, with no user warning and no validation of destination. In context, the real security issue is not lack of warning but that untrusted URLs can trigger outbound requests to arbitrary systems, increasing SSRF and internal network exposure risk.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
'-of', 'default=noprint_wrappers=1:nokey=1',
            audio_url
        ]
        result = subprocess.run(probe_cmd, capture_output=True, text=True, timeout=30)
        return float(result.stdout.strip()) if result.returncode == 0 else None
    except:
        return None
Confidence
83% confidence
Finding
The code invokes ffprobe on audio_url values pulled from the database, which may point to attacker-controlled remote resources. This can enable server-side request forgery behavior or unintended outbound network access from the host running the script, especially because no scheme, host, or locality validation is performed before probing.

Context-Inappropriate Capability

Medium
Confidence
86% confidence
Finding
No manifest is available, so there is no stated purpose that would justify invoking `psql` against a local database or `ffprobe` on retrieved audio URLs. These are substantial capabilities—database access and external tool execution—that go beyond a minimal CSV-filtering operation implied by the file name alone.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
]
    env = {'PGPASSWORD': DB_CONFIG['password']}
    try:
        result = subprocess.run(cmd, env=env, capture_output=True, text=True, timeout=30)
        return result.stdout.strip() if result.returncode == 0 else None
    except:
        return None
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
]
    env = {'PGPASSWORD': DB_CONFIG['password']}
    try:
        result = subprocess.run(cmd, env=env, capture_output=True, text=True, timeout=30)
        return result.stdout.strip() if result.returncode == 0 else None
    except:
        return None
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
]
    env = {'PGPASSWORD': DB_CONFIG['password']}
    try:
        result = subprocess.run(cmd, env=env, capture_output=True, text=True, timeout=30)
        return result.stdout.strip() if result.returncode == 0 else None
    except:
        return None
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
]
    env = {'PGPASSWORD': DB_CONFIG['password']}
    try:
        result = subprocess.run(cmd, env=env, capture_output=True, text=True, timeout=30)
        return result.stdout.strip() if result.returncode == 0 else None
    except:
        return None
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
]
    env = {'PGPASSWORD': DB_CONFIG['password']}
    try:
        result = subprocess.run(cmd, env=env, capture_output=True, text=True, timeout=30)
        return result.stdout.strip() if result.returncode == 0 else None
    except:
        return None
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Static analysis

No suspicious patterns detected.