Back to skill

Security audit

Video Agent (Deprecated)

Security checks for vulnerabilities and agentic risk

Overview

This HeyGen skill is mostly a disclosed API reference, but it gives an agent broad account-changing capabilities without enough safeguards around deletion, uploads, URL fetching, and webhooks.

Review this skill before installing. Use it only with a HeyGen API key you are comfortable letting an agent use, require manual confirmation before any delete action, avoid uploading sensitive media, do not use arbitrary URL ingestion without strict allowlists and size limits, and secure any webhook endpoint before exposing it publicly.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
references/webhooks.md:35
Finding

Webhook Examples Process Unauthenticated, Attacker-Controlled Events

Content
View full analysis
{ // Acknowledge receipt immediately res.status(200).send("OK"); // Process event asynchronously processWebhookEvent(req.body).catch(console.error); }); async function processWebhookEvent(event: HeyGenWebhookEvent) { console.log(`Received event: ${event.event_type}`); switch (event.event_type) { case "avatar_video.success": await handleVideoSuccess(event); break; case "avatar_video.fail": await handleVideoFailure(event); break; case "video_translate.success": await handleTranslationSuccess(event); break; default: console.log(`Unknown event type: ${event.event_type}`); } } app.listen(3000, () => { console.log("Webhook server running on port 3000"); }); ``` ```python @app.route("/webhook/heygen", methods=["POST"]) def heygen_webhook(): event = request.json # Acknowledge immediately response = jsonify({"status": "received"}) # Process asynchronously thread = threading.Thread( target=process_webhook_event, args=(event,) ) thread.start() return response, 200 def process_webhook_event(event): event_type = event.get("event_type") print(f"Received event: {event_type}") if event_type == "avatar_video.success": handle_video_success(event) elif event_type == "avatar_video.fail": handle_video_failure(event) elif event_type == "video_translate.success": handle_translation_success(event) ``` ### Technical Analysis The primary Express.js and Flask examples accept arbitrary POST requests and pass their bodies directly to business-logic handlers. Neither implementation verifies a cryptographic si ...[truncated 2262 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
references/assets.md:180
Finding

Arbitrary HTTPS Fetch Can Be Used for SSRF and Third-Party Data Transfer

Content
View full analysis
{ // 1. Validate and download the file const url = new URL(sourceUrl); if (url.protocol !== "https:") { throw new Error("Only HTTPS URLs are supported"); } const sourceResponse = await fetch(sourceUrl); 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!, "Content-Type": contentType, }, body: buffer, }); const json: AssetUploadResponse = await response.json(); ``` ### Technical Analysis The helper accepts a caller-controlled `sourceUrl`, verifies only that its scheme is HTTPS, downloads the complete response into memory, and uploads the resulting bytes to HeyGen. HTTPS does not establish that a destination is safe. Internal services, loopback interfaces, cloud metadata services, and attacker-controlled redirectors can all be reachable through HTTPS. The implementation does not: - Restrict destination hostnames. - Resolve and reject private, loopback, link-local, multicast, or reserved IP addresses. - Revalidate the destination after DNS resolution. - Disable redirects or validate every redirect target. - Protect against DNS rebinding. - Enforce download size or streaming limits. - Set a request timeout. - Validate the response MIME type against the declared `contentType`. - Require explicit approval before transferring the retrieved content to HeyGen. The code therefore combines an SSRF primitive with an external data-transfer step. It also buffers the entire response, allowing a large or endless ...[truncated 2111 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
Findings (169)

YARA rule 'agent_skill_credential_exfiltration_webhook': AI agent skill credential harvesting followed by webhook or external exfiltration [agent_skills]

Critical
Category
YARA Match
Confidence
85% confidence
Finding

YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Content

Scanner excerpt · references/webhooks.md (reported line 186)May include surrounding context.

} }

text

## 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"]
  }'

TypeScript

typescript
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 Parameter Abuse

High
Category
Tool Misuse
Confidence
90% confidence
Finding

The v3 delete endpoint presents the same abuse path as the deprecated v2 endpoint: a model or tool caller can be manipulated into supplying an attacker-chosen resource identifier for a destructive API call. Because this skill is an operational integration for managing account videos, the context makes accidental or injected deletions more consequential than a purely informational reference.

Content

Scanner excerpt · SKILL.md (reported line 38)May include surrounding context.

md
| Generate video from prompt | `mcp__heygen__generate_video_agent` | `POST /v1/video_agent/generate` | `POST /v3/video-agents` |
| Check video status / get URL | `mcp__heygen__get_video` | `GET /v2/videos/{video_id}` | `GET /v3/videos/{video_id}` |
| List account videos | `mcp__heygen__list_videos` | `GET /v2/videos` | `GET /v3/videos` |
| Delete a video | `mcp__heygen__delete_video` | `DELETE /v2/videos/{video_id}` | `DELETE /v3/videos/{video_id}` |

If no HeyGen MCP tools are available, use direct HTTP API calls with `X-Api-Key: $HEYGEN_API_KEY` header as documented in the reference files.

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
90% confidence
Finding

The v3 delete endpoint presents the same abuse path as the deprecated v2 endpoint: a model or tool caller can be manipulated into supplying an attacker-chosen resource identifier for a destructive API call. Because this skill is an operational integration for managing account videos, the context makes accidental or injected deletions more consequential than a purely informational reference.

Content

Scanner excerpt · SKILL.md (reported line 38)May include surrounding context.

md
| Generate video from prompt | `mcp__heygen__generate_video_agent` | `POST /v1/video_agent/generate` | `POST /v3/video-agents` |
| Check video status / get URL | `mcp__heygen__get_video` | `GET /v2/videos/{video_id}` | `GET /v3/videos/{video_id}` |
| List account videos | `mcp__heygen__list_videos` | `GET /v2/videos` | `GET /v3/videos` |
| Delete a video | `mcp__heygen__delete_video` | `DELETE /v2/videos/{video_id}` | `DELETE /v3/videos/{video_id}` |

If no HeyGen MCP tools are available, use direct HTTP API calls with `X-Api-Key: $HEYGEN_API_KEY` header as documented in the reference files.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 69)May include surrounding context.

md
| Generate video with precise control | — | [video-generation.md](references/video-generation.md), [avatars.md](references/avatars.md), [voices.md](references/v

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 72)May include surrounding context.

md
| Generate video with precise control | — | [video-generation.md](references/video-generation.md), [avatars.md](references/avatars.md), [voices.md](references/v

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 87)May include surrounding context.

md
| Generate video with precise control | — | [video-generation.md](references/video-generation.md), [avatars.md](references/avatars.md), [voices.md](references/v

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · references/prompt-examples.md (reported line 21)May include surrounding context.

Tone: Confident CEO, data-backed

text

### Output Prompt

FORMAT: Bloomberg-style company report. 90 seconds. Fast-paced, data-dense.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill explicitly exposes video deletion capability but provides no requirement for user confirmation, ownership verification, or warning before invoking a destructive action. In an agent setting, this increases the risk that a vague or indirect prompt could cause unintended deletion of account assets.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The documentation instructs users to transmit raw local files and an API key to an external service without an explicit warning about that data leaving the local environment. In agent-integrated settings, this omission can cause users to unknowingly upload sensitive media or credentials-derived metadata to a third party.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/assets.md (reported line 41)May include surrounding context.

| data.meta | string | null | Asset metadata | | data.created_ts | number | Unix timestamp of creation |

curl

bash
curl -X POST "https://upload.heygen.com/v1/asset" \

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/assets.md (reported line 76)May include surrounding context.

md
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!,

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/assets.md (reported line 112)May include surrounding context.

md
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!,

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/photo-avatars.md (reported line 155)May include surrounding context.

md
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!,

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/assets.md (reported line 142)May include surrounding context.

md
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"],

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/photo-avatars.md (reported line 267)May include surrounding context.

md
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"],

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/photo-avatars.md (reported line 283)May include surrounding context.

md
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"],

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/video-agent.md (reported line 189)May include surrounding context.

md
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"],

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/video-generation.md (reported line 190)May include surrounding context.

md
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"],

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The 'upload from URL' example adds remote fetch behavior that materially changes the trust boundary: the code retrieves attacker- or user-controlled remote content and then forwards it to HeyGen. In an agent/skill context, this can enable SSRF-like access to internal resources or unreviewed third-party content exfiltration if callers can influence the URL.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The URL-upload workflow omits a warning that the code first downloads content from a supplied URL and then re-uploads it to HeyGen, creating two separate external transmissions. Without disclosure, users may not realize they are causing retrieval of third-party content and onward transfer to another provider.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
88% confidence
Finding

While the POST to HeyGen is expected, in this specific workflow it is fed by content fetched from a user-supplied URL, which broadens risk beyond a normal upload. If URL input is attacker-controlled, the code can act as a fetch-and-forward primitive for untrusted or internal content.

Content

Scanner excerpt · references/assets.md (reported line 190)May include surrounding context.

md
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!,

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The frontmatter description says this asset skill is for uploading images, videos, and audio for use in HeyGen video generation. However, the 'Complete Upload Workflow' code goes beyond asset upload/management and directly calls the HeyGen video generation API to create a video, which is a separate workflow from asset handling itself.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

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.

Content

Scanner excerpt · references/avatars.md (reported line 282)May include surrounding context.

md
default_voice_id: string | null;
}

interface AvatarGroupListResponse {
  error: null | string;
  data: {
    avatar_group_list: AvatarGroupItem[];

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

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.

Content

Scanner excerpt · references/avatars.md (reported line 291)May include surrounding context.

md
default_voice_id: string | null;
}

interface AvatarGroupListResponse {
  error: null | string;
  data: {
    avatar_group_list: AvatarGroupItem[];

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

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.

Content

Scanner excerpt · references/avatars.md (reported line 301)May include surrounding context.

md
default_voice_id: string | null;
}

interface AvatarGroupListResponse {
  error: null | string;
  data: {
    avatar_group_list: AvatarGroupItem[];

Static analysis

No suspicious patterns detected.