Back to skill

Security audit

Avatar Video

Security checks for vulnerabilities and agentic risk

Overview

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.

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

T09 · Insecure Skill Coding Practices

Error
Location
references/assets.md:73
Finding

Arbitrary Local File Disclosure Through Unrestricted Asset Upload

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
references/assets.md:180
Finding

Server-Side Request Forgery and Data Relay in URL-Based Asset Upload

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, }); ``` ### Technical Analysis The helper accepts an arbitrary `sourceUrl` and checks only whether its parsed scheme is HTTPS. HTTPS does not establish that the destination is public or trusted. An HTTPS URL can resolve to loopback, private, link-local, or otherwise reserved addresses. A public URL may also redirect to an internal destination. The code does not: - Allowlist trusted media hosts. - Resolve and validate destination IP addresses. - Revalidate redirect destinations. - Limit redirects. - Apply a request timeout. - Limit response size. - Verify the returned content type. - Require confirmation before relaying the downloaded bytes. After fetching the URL, the response is buffered in memory and uploaded to HeyGen. Consequently, the behavior is not merely blind SSRF: data obtained from a reachable internal endpoint can be relayed to an external service. ### Attack Path 1. An attacker causes the Agent to call `uploadFromUrl` with an attacker-selected HTTPS URL. 2. The URL directly resolves to an internal address or redirects to one. 3. The Agent's network environment accesses the internal service us ...[truncated 1005 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
references/webhooks.md:22
Finding

Webhook Examples Process Unauthenticated and Forged Events

Content
View full analysis
{ // Acknowledge receipt immediately res.status(200).send("OK"); // Process event asynchronously processWebhookEvent(req.body).catch(console.error); }); ``` The corresponding Flask example similarly processes `request.json` without authentication. The registration example also omits the documented optional shared secret: ```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"] }' ``` The event handler demonstrates a security-sensitive state change based on unverified payload data: ```typescript async function handleVideoSuccess(event: VideoSuccessEvent) { const { video_id, video_url, callback_id } = event.event_data; if (callback_id) { // Look up your original request const order = await getOrderByCallbackId(callback_id); await updateOrderWithVideo(order.id, video_url); } } ``` ### Technical Analysis The primary Express and Flask examples acknowledge and process arbitrary request bodies without verifying that HeyGen sent them. Anyone able to reach the endpoint can submit a fabricated event. A later section presents HMAC verification conditionally, but it is not integrated into the main examples. Moreover, the main Express configuration parses JSON before verification. Secure webhook verification generally requires computing the signature over the exact raw re ...[truncated 1782 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (202)

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
80% confidence
Finding

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).

Content

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

md
|------|----------|----------------------|
| 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.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
ote its `id` (this is the `avatar_id`) and `default_voice_id`. See [avatars.md](references/avatars.md)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
ote its `id` (this is the `avatar_id`) and `default_voice_id`. See [avatars.md](references/avatars.md)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
e_id`, `script`, and optional `background` per scene. See [video-generation.md](references/video-generation.md)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
e_id`, `script`, and optional `background` per scene. See [video-generation.md](references/video-generation.md)

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

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.

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/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/avatars.md (reported line 540)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 192)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 198)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/voices.md (reported line 198)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
91% confidence
Finding

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.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
90% confidence
Finding

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.

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

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

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.

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/dimensions.md (reported line 66)May include surrounding context.

};

text

### curl

```bash
# Landscape 1080p

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

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.

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/photo-avatars.md (reported line 30)May include surrounding context.

  • URL variant: { "type": "url", "url": "https://example.com/photo.jpg" }
  • Asset ID variant: { "type": "asset_id", "asset_id": "uploaded_asset_id" }

curl Example

bash
curl -X POST "https://api.heygen.com/v3/videos" \

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 312)May include surrounding context.

md
}

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

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/avatars.md (reported line 495)May include surrounding context.

md
}

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

Static analysis

No suspicious patterns detected.