T09 · Insecure Skill Coding Practices
- Location
references/assets.md:73- Finding
Arbitrary Local File Disclosure Through Unrestricted Asset Upload
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill appears to be legitimate HeyGen video-generation guidance, but it needs Review because some examples can upload arbitrary local or fetched content and include insecure webhook patterns.
Install only if you are comfortable giving the agent access to a HeyGen API key and sending selected scripts, photos, images, video, and audio to HeyGen. Require confirmation before any upload or deletion, restrict uploads to a known media folder, avoid arbitrary URL relay, and do not copy the webhook examples into production without strong signature verification on the raw request body, replay protection, and rate limits.
references/assets.md:73Arbitrary Local File Disclosure Through Unrestricted Asset Upload
references/assets.md:180Server-Side Request Forgery and Data Relay in URL-Based Asset Upload
references/webhooks.md:22Webhook Examples Process Unauthenticated and Forged Events
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).
} }
## Registering a Webhook URL
Configure your webhook URL through the HeyGen dashboard or API:
### Request Fields
| Field | Type | Req | Description |
|-------|------|:---:|-------------|
| `url` | string | ✓ | Your webhook endpoint URL |
| `events` | array | ✓ | Event types to subscribe to |
| `secret` | string | | Shared secret for signature verification |
### Via API
```bash
curl -X POST "https://api.heygen.com/v1/webhook/endpoint.add" \
-H "X-Api-Key: $HEYGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-domain.com/webhook/heygen",
"events": ["avatar_video.success", "avatar_video.fail"]
}'
interface WebhookConfig {
url: string; // Required
events: string[]; // Required
secret?: string;
}
async function registerWebhook(config: WebhookConfig): Promise<void> {
const response = await fetch("https://api.heygen.com/v1/webh
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).
|------|----------|----------------------|
| Check video status / get URL | `mcp__heygen__get_video` | `GET /v3/videos/{video_id}` |
| List account videos | `mcp__heygen__list_videos` | `GET /v3/videos` |
| Delete a video | `mcp__heygen__delete_video` | `DELETE /v3/videos/{video_id}` |
Video generation (`POST /v3/videos`) and avatar/voice listing are done via direct API calls — see reference files below.
Referenced artifact was not completely inspected
ote its `id` (this is the `avatar_id`) and `default_voice_id`. See [avatars.md](references/avatars.md)
Referenced artifact was not completely inspected
ote its `id` (this is the `avatar_id`) and `default_voice_id`. See [avatars.md](references/avatars.md)
Referenced artifact was not completely inspected
e_id`, `script`, and optional `background` per scene. See [video-generation.md](references/video-generation.md)
Referenced artifact was not completely inspected
e_id`, `script`, and optional `background` per scene. See [video-generation.md](references/video-generation.md)
The skill manifest describes a narrowly scoped capability centered on creating HeyGen avatar videos via the v3 /videos API. This file documents and implements a separate asset-upload subsystem, including direct uploads to upload.heygen.com, asset hosting URLs, and reusable asset management patterns, which materially broadens the operational scope beyond just constructing avatar-video requests.
The documentation describes uploading local or remote files to an external service but does not clearly warn that file contents will leave the local environment and be transmitted to HeyGen. In an agent context, omission of this disclosure can cause users or downstream systems to expose private images, videos, or audio unintentionally.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
| data.meta | string | null | Asset metadata |
| data.created_ts | number | Unix timestamp of creation |
curl -X POST "https://upload.heygen.com/v1/asset" \
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
const resolvedPath = path.resolve(filePath);
const fileBuffer = fs.readFileSync(resolvedPath);
const response = await fetch("https://upload.heygen.com/v1/asset", {
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY!,
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
const resolvedPath = path.resolve(filePath);
const fileBuffer = fs.readFileSync(resolvedPath);
const response = await fetch("https://upload.heygen.com/v1/asset", {
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY!,
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
def upload_asset(file_path: str, content_type: str) -> dict:
with open(file_path, "rb") as f:
response = requests.post(
"https://upload.heygen.com/v1/asset",
headers={
"X-Api-Key": os.environ["HEYGEN_API_KEY"],
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
def upload_asset(file_path: str, content_type: str) -> dict:
with open(file_path, "rb") as f:
response = requests.post(
"https://upload.heygen.com/v1/asset",
headers={
"X-Api-Key": os.environ["HEYGEN_API_KEY"],
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
def upload_asset(file_path: str, content_type: str) -> dict:
with open(file_path, "rb") as f:
response = requests.post(
"https://upload.heygen.com/v1/asset",
headers={
"X-Api-Key": os.environ["HEYGEN_API_KEY"],
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
def upload_asset(file_path: str, content_type: str) -> dict:
with open(file_path, "rb") as f:
response = requests.post(
"https://upload.heygen.com/v1/asset",
headers={
"X-Api-Key": os.environ["HEYGEN_API_KEY"],
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
def upload_asset(file_path: str, content_type: str) -> dict:
with open(file_path, "rb") as f:
response = requests.post(
"https://upload.heygen.com/v1/asset",
headers={
"X-Api-Key": os.environ["HEYGEN_API_KEY"],
The uploadFromUrl example fetches an arbitrary user-supplied remote URL and then forwards the downloaded content to HeyGen. This creates a data-flow from untrusted external locations into a third-party service without strong allowlisting, size/content validation, or user-consent language, which can enable SSRF-like access patterns, unexpected data ingestion, or transmission of sensitive remote content.
This request is the second stage of a workflow that first downloads arbitrary remote content and then uploads it to HeyGen. In context, the danger is not the POST alone but that it forwards untrusted externally fetched data to a third party, compounding the risk of mishandled sensitive content or abuse of remote-fetch functionality.
const buffer = Buffer.from(await sourceResponse.arrayBuffer());
// 2. Upload directly to HeyGen
const response = await fetch("https://upload.heygen.com/v1/asset", {
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY!,
Line L752 instructs users to 'always use the look's default_voice_id or match genders manually,' which embeds a natural-language gender-matching requirement. This is a policy concern because it prescribes a gender-based constraint without user opt-in or a clearly documented compliance justification.
The checklist item at L760 states 'Voice gender matches avatar gender' as a mandatory pre-generation condition. This is a natural-language policy issue because it enforces a gender-based rule rather than presenting it as an optional preference or technical recommendation.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
};
### curl
```bash
# Landscape 1080p
The document instructs users to upload photos, scripts, and related content to HeyGen endpoints without clearly warning that potentially sensitive biometric-like imagery and text are transmitted to a third-party service. In a skill context, this can cause unintentional disclosure of personal, confidential, or regulated data because users may assume local processing unless told otherwise.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
{ "type": "url", "url": "https://example.com/photo.jpg" }{ "type": "asset_id", "asset_id": "uploaded_asset_id" }curl -X POST "https://api.heygen.com/v3/videos" \
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
}
async function createPhotoVideo(config: PhotoVideoRequest): Promise<string> {
const response = await fetch("https://api.heygen.com/v3/videos", {
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY!,
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
}
async function createPhotoVideo(config: PhotoVideoRequest): Promise<string> {
const response = await fetch("https://api.heygen.com/v3/videos", {
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY!,
No suspicious patterns detected.