Back to skill

Security audit

Nano Banana Veo

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it says, but its video download path can expose the user’s Gemini API key to an unvalidated URL returned by the remote service.

Review before installing. Use a restricted Gemini API key with tight quotas and billing controls, avoid sensitive prompts or assets, and prefer a fixed version or patched release that validates video download hosts and redirects before attaching credentials.

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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate.py:129
Finding
Gemini API Key Forwarded to an Unvalidated Remote URL## Vulnerability Details **File Location**: `scripts/generate.py`, lines 129-135 **Vulnerability Type**: Credential disclosure through an untrusted download destination **Risk Level**: Medium **Vulnerable Code**: ```python video_uri = outputs[0].get('video', {}).get('uri') if video_uri: print(f"Downloading video from: {video_uri[:60]}...") video_response = requests.get(video_uri, headers={"x-goog-api-key": api_key}, timeout=60) with open(output_path, 'wb') as f: f.write(video_response.content) print(f"✅ Video saved to: {output_path}") ``` ### Technical Analysis The video download URI comes from the remote API response and is used directly as a request destination. The script does not validate the URI scheme or hostname before attaching the `GEMINI_API_KEY` in the `x-goog-api-key` header. Consequently, a malicious, compromised, or otherwise unexpected upstream response could provide an attacker-controlled URI. The subsequent request would disclose the API key to that destination. The `requests` library also follows redirects by default, and the code does not validate redirect destinations or disable redirects. Sending credentials to arbitrary response-provided destinations exceeds the minimum privileges required by the declared functionality. Video retrieval should be restricted to documented Google-owned service endpoints or performed through an official SDK that does not expose the API key to an arbitrary download host. The downloaded response is also written without checking the HTTP status, content type, or response size. While the file is not executed by this script, this could allow an invalid or unexpectedly large response to be written to the caller-selected output path. ### Attack Path 1. A user invokes the Skill with video generation enabled. 2. The script submits the generated image and video prompt to the declared Gemini endpoint. 3. The script polls for completion and reads ...[truncated 897 chars]
Remediation
## Remediation Suggestions 1. Parse the returned URI with `urllib.parse.urlparse`. 2. Require the `https` scheme and an explicit allowlist of documented Google download hostnames. 3. Reject embedded credentials, unexpected ports, malformed hostnames, and hostnames that merely use an allowed suffix deceptively. 4. Disable automatic redirects with `allow_redirects=False`, or validate every redirect destination before following it. 5. Prefer an official Google SDK download method or a scoped, short-lived signed URL that does not require forwarding the API key. 6. Call `raise_for_status()` before writing the response. 7. Validate the response content type and enforce a maximum download size. 8. Stream the response into a temporary file and atomically replace the requested output only after successful validation. 9. Restrict the Gemini API key by API, application, quota, and billing controls wherever supported. Example validation pattern: ```python from urllib.parse import urlparse allowed_hosts = { "generativelanguage.googleapis.com", # Add only Google hosts explicitly documented for video downloads. } parsed = urlparse(video_uri) if parsed.scheme != "https" or parsed.hostname not in allowed_hosts: raise ValueError("Unapproved video download URI") video_response = requests.get( video_uri, headers={"x-goog-api-key": api_key}, timeout=60, allow_redirects=False, stream=True, ) video_response.raise_for_status() ```

T08 · Insecure Dependencies

Note
Location
SKILL.md:48
Finding
Third-Party Dependencies Are Installed Without Version or Integrity Pinning## Vulnerability Details **File Location**: `SKILL.md`, lines 48-55 **Vulnerability Type**: Unpinned third-party dependencies and non-reproducible installation **Risk Level**: Low **Vulnerable Documentation**: ```markdown - `google-genai` and `requests` Python packages ## Installation ```bash # Install dependencies pip install google-genai requests # Or with uv uv pip install google-genai requests ``` ``` ### Technical Analysis The installation instructions retrieve unconstrained versions of `google-genai` and `requests` from the configured package index. These dependency names are consistent with the Skill's declared functionality, and the reviewed code does not demonstrate dependency confusion or typosquatting. However, accepting whichever versions are current at installation time makes installation non-reproducible and allows dependency behavior to change after the Skill itself has been audited. If a package, transitive dependency, package-index account, or configured package source is compromised, users following these instructions could install unsafe code. Python packages may execute code during build or installation, and installed packages execute with the privileges of the invoking user when imported. ### Attack Path 1. A user follows the documented `pip install` or `uv pip install` command. 2. The package manager resolves the newest versions available from its configured index. 3. A future defective or compromised package release, transitive dependency, or package source is selected. 4. Malicious code executes during installation, build processing, or subsequent import. 5. That code receives the local privileges of the user running the installation or Skill. ### Impact Assessment The potential impact depends on the privileges used for package installation and execution. In a typical user environment, a compromised dependency could read user-accessible files and environment variables, includin ...[truncated 341 chars]
Remediation
## Remediation Suggestions 1. Define reviewed dependency versions in a lockfile or requirements file instead of installing unconstrained latest releases. 2. Pin direct and transitive dependencies to exact versions. 3. Include cryptographic hashes where supported, such as a hash-locked requirements file used with `pip --require-hashes`. 4. Commit the lockfile to the Skill package and update it through a controlled review process. 5. Document the trusted package index and avoid untrusted supplemental indexes. 6. Run dependency vulnerability and provenance checks during release preparation. 7. Install and run the Skill in an isolated virtual environment without administrative privileges. Example: ```text google-genai==<reviewed-version> --hash=sha256:<reviewed-hash> requests==<reviewed-version> --hash=sha256:<reviewed-hash> ``` The versions and hashes must be populated from reviewed release artifacts rather than copied without verification.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • 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 (8)

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

Critical
Category
Data Flow
Content
}
        
        print(f"Starting Veo video generation (duration: {duration}s)...")
        response = requests.post(url, headers=headers, json=data, timeout=120)
        result = response.json()
        
        if response.status_code != 200:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
time.sleep(10)
            print(f"Poll {i+1}/60...")
            
            poll_response = requests.get(poll_url, headers={"x-goog-api-key": api_key}, timeout=30)
            poll_result = poll_response.json()
            
            if poll_result.get('done'):
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
video_uri = outputs[0].get('video', {}).get('uri')
                    if video_uri:
                        print(f"Downloading video from: {video_uri[:60]}...")
                        video_response = requests.get(video_uri, headers={"x-goog-api-key": api_key}, timeout=60)
                        with open(output_path, 'wb') as f:
                            f.write(video_response.content)
                        print(f"✅ Video saved to: {output_path}")
Confidence
96% confidence
Finding
An environment-derived API key is attached to a GET request whose destination is obtained from prior network input. This creates a real risk of credential exfiltration if the URI points to an unexpected host, making the static analysis result valid in this location.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill documents code execution that uses environment variables and outbound network access, but it does not declare an explicit tool scope such as permissions or allowed-tools. That omission weakens least-privilege controls and makes it easier for the skill to access sensitive runtime capabilities without clear review or user visibility.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The documentation says the skill requires a GEMINI_API_KEY, but it does not clearly warn that user prompts, image prompts, video prompts, and possibly generated assets are sent to Google's external Gemini/Veo services. This creates a data-handling transparency gap that can lead users to submit sensitive or proprietary content without informed consent.

External Transmission

Medium
Category
Data Exfiltration
Content
}
        
        print(f"Starting Veo video generation (duration: {duration}s)...")
        response = requests.post(url, headers=headers, json=data, timeout=120)
        result = response.json()
        
        if response.status_code != 200:
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Tainted flow: 'poll_url' from requests.post (line 117, network input) → requests.get (network output)

Medium
Category
Data Flow
Content
time.sleep(10)
            print(f"Poll {i+1}/60...")
            
            poll_response = requests.get(poll_url, headers={"x-goog-api-key": api_key}, timeout=30)
            poll_result = poll_response.json()
            
            if poll_result.get('done'):
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Tainted flow: 'video_uri' from requests.get (line 132, network input) → requests.get (network output)

Medium
Category
Data Flow
Content
video_uri = outputs[0].get('video', {}).get('uri')
                    if video_uri:
                        print(f"Downloading video from: {video_uri[:60]}...")
                        video_response = requests.get(video_uri, headers={"x-goog-api-key": api_key}, timeout=60)
                        with open(output_path, 'wb') as f:
                            f.write(video_response.content)
                        print(f"✅ Video saved to: {output_path}")
Confidence
89% confidence
Finding
The code downloads a URL taken from a network response without validating its host or scheme, and includes the Google API key in the request headers. If the upstream response is compromised or malformed, this could turn into SSRF-like behavior and leak the API key to an attacker-controlled endpoint.

Static analysis

No suspicious patterns detected.