T09 · Insecure Skill Coding Practices
- Location
scripts/photo_to_3d.py:181- Finding
Unvalidated Server-Provided Model URL Enables Arbitrary Network Requests
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This skill does what it says: it converts user-supplied images into 3D models using Gemini and Tripo3D, with no evidence of hidden persistence, deception, or unrelated data access.
Install only if you are comfortable sending selected images to Gemini and Tripo3D using your own API keys. Avoid sensitive, private, or regulated images unless those services' policies fit your needs. The publisher should narrow triggers, add explicit upload/third-party disclosure, and validate the returned model download URL before fetching it.
scripts/photo_to_3d.py:181Unvalidated Server-Provided Model URL Enables Arbitrary Network Requests
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
},
}
resp = requests.post(url, json=payload, timeout=120)
if resp.status_code != 200:
print(f"❌ Gemini API 错误: {resp.status_code}")
print(resp.text[:500])
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
img_path = Path(image_path)
with open(img_path, "rb") as f:
upload_resp = requests.post(
"https://api.tripo3d.ai/v2/openapi/upload",
headers=headers,
files={"file": (img_path.name, f, "image/png")},
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
print(f" 📤 图片已上传, token: {image_token[:20]}...")
# 创建 image-to-3d 任务
task_resp = requests.post(
"https://api.tripo3d.ai/v2/openapi/task",
headers={**headers, "Content-Type": "application/json"},
json={"type": "image_to_model", "file": {"type": "png", "file_token": image_token}},
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
start = time.time()
while time.time() - start < max_wait:
time.sleep(5)
status_resp = requests.get(
f"https://api.tripo3d.ai/v2/openapi/task/{task_id}",
headers=headers,
timeout=30,
The skill advertises behavior that requires environment access, file writes, and outbound network calls, but it does not declare any explicit tool scope or permissions. This creates a transparency and governance gap: an agent or reviewer cannot easily tell that local files, API keys, and uploaded images may be accessed or transmitted to third-party services.
The trigger phrases include broad terms like "generate 3D" and "convert to 3D model," which are likely to match many generic user requests beyond this skill's intended scope. Over-broad activation can cause the skill to run unexpectedly, increasing the chance that user images are sent to external services or files are created without clear user intent.
The skill description explains the pipeline but does not warn users that their input images are transmitted to external AI providers (Gemini and Tripo3D). This is a meaningful privacy and data-handling issue because users may provide sensitive, proprietary, or personal images without understanding that third-party services will process them.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
},
}
resp = requests.post(url, json=payload, timeout=120)
if resp.status_code != 200:
print(f"❌ Gemini API 错误: {resp.status_code}")
print(resp.text[:500])
The script uploads user images to external Gemini and Tripo3D services, but there is no explicit consent prompt or prominent warning that local images are transmitted to third parties. In a photo-processing skill, this matters because users may assume processing is local and may unknowingly send sensitive images off-device.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
img_path = Path(image_path)
with open(img_path, "rb") as f:
upload_resp = requests.post(
"https://api.tripo3d.ai/v2/openapi/upload",
headers=headers,
files={"file": (img_path.name, f, "image/png")},
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
img_path = Path(image_path)
with open(img_path, "rb") as f:
upload_resp = requests.post(
"https://api.tripo3d.ai/v2/openapi/upload",
headers=headers,
files={"file": (img_path.name, f, "image/png")},
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
img_path = Path(image_path)
with open(img_path, "rb") as f:
upload_resp = requests.post(
"https://api.tripo3d.ai/v2/openapi/upload",
headers=headers,
files={"file": (img_path.name, f, "image/png")},
timeout=60,
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
img_path = Path(image_path)
with open(img_path, "rb") as f:
upload_resp = requests.post(
"https://api.tripo3d.ai/v2/openapi/upload",
headers=headers,
files={"file": (img_path.name, f, "image/png")},
timeout=60,
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
img_path = Path(image_path)
with open(img_path, "rb") as f:
upload_resp = requests.post(
"https://api.tripo3d.ai/v2/openapi/upload",
headers=headers,
files={"file": (img_path.name, f, "image/png")},
timeout=60,
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
print(f" 📤 图片已上传, token: {image_token[:20]}...")
# 创建 image-to-3d 任务
task_resp = requests.post(
"https://api.tripo3d.ai/v2/openapi/task",
headers={**headers, "Content-Type": "application/json"},
json={"type": "image_to_model", "file": {"type": "png", "file_token": image_token}},
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
start = time.time()
while time.time() - start < max_wait:
time.sleep(5)
status_resp = requests.get(
f"https://api.tripo3d.ai/v2/openapi/task/{task_id}",
headers=headers,
timeout=30,
The code fetches model_url directly from a remote API response and immediately performs requests.get on it without validating the hostname, scheme, or content type. If the upstream service is compromised or returns an unexpected URL, this could trigger server-side request forgery to arbitrary destinations or download untrusted content to disk.
model_url = status_data.get("output", {}).get("model", "")
if model_url:
output_path = OUTPUT_DIR / f"{img_path.stem}_model.glb"
model_resp = requests.get(model_url, timeout=120)
output_path.write_bytes(model_resp.content)
print(f" 📦 模型已保存: {output_path}")
return str(output_path)
The file's natural-language strings and user-facing interface are entirely in Chinese, with no indication that language is selectable or intentionally limited to a specific audience. This can violate language/locale policy when a skill imposes a language without user opt-in.
No suspicious patterns detected.