T09 · Insecure Skill Coding Practices
- Location
SKILL.md:154- Finding
API Credential Exfiltration Through an Unvalidated Download URI
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill generally does what it claims, but it uploads user images and prompts to Google and handles the Google API key in a way that could expose it.
Review before installing. Use only with images and prompts you are allowed to send to Google, avoid confidential or regulated media, and use a dedicated tightly restricted Google API key. The video download step should be hardened to validate the returned URI host and redirects before attaching the key.
SKILL.md:154API Credential Exfiltration Through an Unvalidated Download URI
SKILL.md:112Google API Key Embedded in Request URLs
The skill sends user-provided images and derived prompt content to external Google APIs, but the documentation does not clearly warn users that local content will leave the machine and be transmitted to third-party cloud services. This creates privacy and compliance risk, especially if users process sensitive, copyrighted, or internal images without realizing they are being uploaded.
The manifest documentation says the skill accepts an image as a local path or URL, implying broader input handling. In the actual runnable example, the script only checks for a timestamped local file under ~/.openclaw/workspace/tibetanProc/ and never downloads or parses a user-supplied URL, so the described behavior does not match the implementation.
The call to upload_file sends a local image to cloud-hosted Google infrastructure for analysis. That is expected behavior for this skill, but it is still cloud exfiltration of local data and can be dangerous if users assume processing is local or if the image contains sensitive information.
# Step 2: Analyze with Gemini
genai.configure(api_key=GOOGLE_API_KEY)
model = genai.GenerativeModel("gemini-2.5-flash")
image_file = genai.upload_file(str(IMAGE_PATH), mime_type="image/jpeg")
prompt = """Analyze for cinematic video: describe subject, setting, lighting, textures,
suggested camera movements (dolly, pan, orbit, zoom, rack focus)."""
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.
f.write(analysis)
print(f"✓ Analysis: {prompt_path.name}")
# Step 3: Create enhanced prompt
enhanced = f"""VIDEO GENERATION PROMPT
Duration: 5 seconds
Quality: High Definition
This code transmits generated prompts and image-derived content to an external Google API endpoint. In context, that is the intended function of the skill, but it is still a real data egress point that can expose sensitive user content if used on private images without clear consent or policy controls.
}
print("\n🎬 Calling Veo API...")
response = requests.post(VEO_URL, json=payload, timeout=60)
if response.status_code not in [200, 202]:
print(f"✗ API error: {response.json()}")
This is a duplicate cloud upload path in the detailed workflow, again sending a local image to an external service. The risk is contextual rather than overtly malicious, but it is still a true data-exposure concern because local media leaves the host environment.
model = genai.GenerativeModel("gemini-2.5-flash")
IMAGE_PATH = Path.home() / ".openclaw" / "workspace" / "tibetanProc" / "2604110411_input_image.jpg"
image_file = genai.upload_file(str(IMAGE_PATH), mime_type="image/jpeg")
analysis_prompt = """Analyze this image for cinematic video generation:
1. Main subject and focal point
This second API call site is another explicit external transmission point to Google's Veo service. While necessary for functionality, it still represents outbound transfer of potentially sensitive prompt and image data to a third party and therefore should be treated as a real privacy/security concern.
VEO_URL = f"https://generativelanguage.googleapis.com/v1beta/models/veo-3.0-generate-001:predictLongRunning?key={GOOGLE_API_KEY}"
response = requests.post(VEO_URL, json=payload, timeout=60)
result = response.json()
if response.status_code in [200, 202]:
The documentation lists 2604110411_veo_init_response.json as an output artifact, which indicates the skill persists the initial Veo API response. However, the code only parses response.json() in memory and does not write any init-response JSON file to disk, so the documented output contradicts actual behavior.
No suspicious patterns detected.