Back to skill

Security audit

AI Music Video

Security checks for vulnerabilities and agentic risk

Overview

The skill is meant to make AI music videos, but unsafe script argument handling could let crafted inputs run local code.

Review carefully before installing. Use it only in a constrained workspace with non-sensitive prompts and limited API keys, confirm each paid generation step, avoid passing untrusted text into script options, and consider fixing the python3 -c interpolation issues before running it with real credentials.

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
scripts/gen_visuals.sh:72
Finding
Arbitrary Python Code Execution Through Visual Generation Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/gen_visuals.sh:72-101`, with additional unsafe interpolation at `scripts/gen_visuals.sh:143-160`, `170-181`, and `476-533` **Vulnerability Type**: Command/code injection through dynamically constructed Python source **Risk Level**: High ### Vulnerable Code ```bash get_image_cost() { local provider="$1" quality="$2" size="$3" case "$provider" in openai) # Token-based calculation for OpenAI models # Output tokens by quality: low=272, medium=1056, high=4160 # Size multiplier: 1024x1024=1x, 1536x1024/1024x1536=1.5x python3 -c " model = '$IMAGE_MODEL' quality = '$quality' size = '$size' # Image output token rates (per 1M tokens) rates = { 'gpt-image-1': {'text_in': 5.00, 'img_out': 40.00}, 'gpt-image-1-mini': {'text_in': 2.00, 'img_out': 8.00}, } # Output tokens by quality (measured empirically for 1024x1024) output_tokens = {'low': 272, 'medium': 1056, 'high': 4160} # Size multiplier for output tokens size_mult = 1.5 if size != '1024x1024' else 1.0 r = rates.get(model, rates['gpt-image-1-mini']) text_tokens = 80 # typical prompt, negligible img_tokens = int(output_tokens.get(quality, 1056) * size_mult) cost = (text_tokens * r['text_in'] + img_tokens * r['img_out']) / 1_000_000 print(f'{cost:.6f}') " ;; ``` Another affected block writes attacker-controlled values into Python source: ```bash if [[ "$DRY_RUN" = true ]]; then # Write estimate to JSON python3 -c " import json est = { 'mode': '$MODE', 'num_images': $NUM_IMAGES, 'num_videos': $NUM_VIDEOS, 'image_provider': '$IMAGE_PROVIDER', 'image_model': '$IMAGE_MODEL', 'video_provider': '$VIDEO_PROVIDER', 'image_quality': '$IMAGE_QUALITY', 'image_size': '$IMAGE_SIZE', 'image_cost_each': $IMG_COST, 'video_cost_each': $VID_COST, 'total_image_cost': $TOTAL_IMG, 'total_video_cost': $TOTAL_VID, 'total_cost': $TOTAL, 'pricing_method': 'token-based' } with open('$OUTDIR/co ...[truncated 2257 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Never interpolate command-line data into a `python3 -c` program. 2. Pass values as positional arguments and read them through `sys.argv`, for example: ```bash python3 - "$IMAGE_MODEL" "$quality" "$size" <<'PY' import sys model, quality, size = sys.argv[1:4] # Process values strictly as data. PY ``` 3. Pass complex request data through JSON files, stdin, or environment variables rather than embedding it in Python syntax. 4. Enforce explicit allowlists before any processing: - Models: `gpt-image-1`, `gpt-image-1-mini` - Modes: `slideshow`, `video`, `hybrid` - Image providers: `openai`, `seedream`, `google-together` - Quality: `low`, `medium`, `high` - Sizes: the documented supported dimensions - Video providers: the documented provider identifiers 5. Supply output paths through `sys.argv` and use `pathlib.Path`; do not place paths inside Python literals. 6. Apply the same correction to every `python3 -c` block at lines `143-160`, `170-181`, and `476-533`. 7. Add regression tests containing single quotes, triple quotes, backslashes, newlines, semicolons, and Python-like payload text. Verify that these values are rejected or treated only as inert data. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/suno_music.sh:123
Finding
Arbitrary Python Code Execution Through Music and Persona Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/suno_music.sh:123-155`, with additional unsafe interpolation at `scripts/suno_music.sh:217-294`, `496-507`, and `528-539` **Vulnerability Type**: Command/code injection through dynamically constructed Python source **Risk Level**: High ### Vulnerable Code ```bash BODY=$(python3 -c " import json, os with open('$PROMPT_FILE') as f: prompt = f.read() with open('$STYLE_FILE') as f: style = f.read() with open('$TITLE_FILE') as f: title = f.read() with open('$NEGTAGS_FILE') as f: neg_tags = f.read() body = { 'prompt': prompt, 'model': '$MODEL', 'instrumental': $( [ "$INSTRUMENTAL" = true ] && echo 'True' || echo 'False'), 'customMode': $( [ "$CUSTOM_MODE" = true ] && echo 'True' || echo 'False'), } if $( [ "$CUSTOM_MODE" = true ] && echo 'True' || echo 'False'): if style: body['style'] = style if title: body['title'] = title vocal = '$VOCAL_GENDER' if vocal: body['vocalGender'] = vocal if neg_tags: body['negativeTags'] = neg_tags persona_id = '$PERSONA_ID' if persona_id: body['personaId'] = persona_id body['personaModel'] = 'style_persona' cb_url = os.environ.get('SUNO_CALLBACK_URL', 'https://localhost/noop') # Validate callback URL scheme (only https allowed to prevent exfiltration) if cb_url and not cb_url.startswith('https://'): cb_url = 'https://localhost/noop' body['callBackUrl'] = cb_url print(json.dumps(body, ensure_ascii=False)) ") ``` Persona fields are also embedded directly into triple-quoted Python literals: ```bash PERSONA_BODY_FILE=$(mktemp) python3 -c " import json body = { 'taskId': '$TASK_ID', 'audioId': '$AUDIO_ID_P', 'name': '''$P_NAME''', 'description': '''$P_DESC''', } style = '''$P_STYLE_VAL''' if style: body['style'] = style print(json.dumps(body, ensure_ascii=False)) " > "$PERSONA_BODY_FILE" ``` ### Technical Analysis Although prompts, styles, titles, and negative tags are initially placed in temporary files, se ...[truncated 2035 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace every dynamic `python3 -c` source construction with a fixed Python program receiving data through `sys.argv`, stdin, environment variables, or JSON files. 2. Continue using temporary files for multiline user text, but pass each temporary filename as a positional argument rather than embedding its path into Python source. 3. Build persona JSON safely, for example: ```bash python3 - "$TASK_ID" "$AUDIO_ID_P" "$P_NAME" "$P_DESC" "$P_STYLE_VAL" <<'PY' > "$PERSONA_BODY_FILE" import json import sys task_id, audio_id, name, description, style = sys.argv[1:6] body = { "taskId": task_id, "audioId": audio_id, "name": name, "description": description, } if style: body["style"] = style json.dump(body, sys.stdout, ensure_ascii=False) PY ``` 4. Allowlist `MODEL` against the documented model identifiers. 5. Restrict `VOCAL_GENDER` to `m`, `f`, or an empty value. 6. Validate task, audio, and persona identifiers against the provider's documented identifier format. 7. Pass output directories through `sys.argv` and use standard path APIs. 8. Correct all related interpolation sites at lines `217-294`, `496-507`, and `528-539`. 9. Add negative tests using quotes, triple quotes, newline characters, and Python statement fragments in every user-controlled option. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/suno_music.sh:79
Finding
Dry-Run Mode Performs an Unexpected Authenticated Network Request<![CDATA[ ## Vulnerability Details **File Location**: `scripts/suno_music.sh:79-109` **Vulnerability Type**: Unnecessary credential transmission and violation of dry-run expectations **Risk Level**: Low ### Vulnerable Code ```bash AUTH="Authorization: Bearer $SUNO_API_KEY" CT="Content-Type: application/json" # Check credits (may not be supported by all sunoapi instances) echo "🔍 Checking Suno credits..." CREDITS_RESP=$(curl -s -H "$AUTH" "${API_BASE}/get-credits" 2>/dev/null) CREDITS=$(echo "$CREDITS_RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('data',0))" 2>/dev/null || echo "unknown") if [[ "$CREDITS" == "unknown" || "$CREDITS" == "0" ]]; then CREDITS="N/A (credit API not available)" fi echo "💰 Credits: $CREDITS" # Cost estimate echo "" echo "📊 Cost Estimate" echo "━━━━━━━━━━━━━━━━━━━" echo " Model: $MODEL" echo " Mode: $([ "$CUSTOM_MODE" = true ] && echo 'Custom' || echo 'Simple')" echo " Instrumental: $INSTRUMENTAL" echo " Music Video: $MUSIC_VIDEO" if [[ -n "$PERSONA_ID" ]]; then echo " Persona: $PERSONA_ID" fi echo " Create Persona: $CREATE_PERSONA" echo " Est. credits: ~10 per generation (2 tracks)" echo " Credits: $CREDITS" echo "━━━━━━━━━━━━━━━━━━━" if [[ "$DRY_RUN" = true ]]; then echo "DRY_RUN: exiting without generation" exit 0 fi ``` ### Technical Analysis The script constructs an authorization header and calls the Suno credit endpoint before evaluating the `DRY_RUN` flag. Therefore, `--dry-run` is not network-free and transmits the bearer credential to `api.sunoapi.org`. This conflicts with the test-suite description that dry-run tests involve “no API calls, no cost.” Although `api.sunoapi.org` is the declared music provider and ordinary generation requires this network access, an authenticated request is not necessary to calculate the script's static cost estimate. This is not hidden exfiltration to an unrelated host, but it exceeds the minimum network activity needed for cost-only execution. ...[truncated 885 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Move the `DRY_RUN` branch before construction or use of the authenticated credit request. 2. Calculate and display the static estimate locally in dry-run mode. 3. If live credit information is desirable, require an explicit option such as `--check-credits`. 4. Permit dry-run execution without `SUNO_API_KEY` when no network request will occur. 5. Add a test that runs dry-run with network access blocked and confirms successful local completion. 6. Update documentation and test descriptions so they accurately distinguish local-only estimates from optional online credit checks. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (65)

External Script Fetching

High
Category
Supply Chain
Content
'width': 1536, 'height': 1024
}, ensure_ascii=False))
" > "$body_file"
  resp=$(curl -s -X POST "https://api.together.xyz/v1/images/generations" \
    -H "Authorization: Bearer $TOGETHER_API_KEY" \
    -H "Content-Type: application/json" \
    -d @"$body_file")
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
if [[ -z "$url" || "$url" == ERROR* ]]; then
    echo "  ❌ Image gen failed" >&2; return 1
  fi
  curl -s -o "$outpath" "$url"
  echo "  ✅ $(basename "$outpath")"
}
Confidence
90% confidence
Finding
Although labeled as script fetching, the real issue is unvalidated remote content download from a URL supplied by an external service. This creates a trust-boundary problem where compromised API output could cause arbitrary remote retrieval and storage, which is more dangerous in an automation context that may run in CI or on hosts with network reachability.

External Script Fetching

High
Category
Supply Chain
Content
echo "  ❌ Video timeout" >&2; return 1
    fi
    local poll
    poll=$(curl -s "https://api.together.xyz/v2/videos/${video_id}" \
      -H "Authorization: Bearer $TOGETHER_API_KEY")
    status=$(echo "$poll" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status','unknown'))" 2>/dev/null)
    echo "    [${attempts}] $status"
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
# Check credits (may not be supported by all sunoapi instances)
echo "🔍 Checking Suno credits..."
CREDITS_RESP=$(curl -s -H "$AUTH" "${API_BASE}/get-credits" 2>/dev/null)
CREDITS=$(echo "$CREDITS_RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('data',0))" 2>/dev/null || echo "unknown")
if [[ "$CREDITS" == "unknown" || "$CREDITS" == "0" ]]; then
  CREDITS="N/A (credit API not available)"
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
echo ""
echo "🎵 Generating music..."
GEN_RESP=$(curl -s -X POST "${API_BASE}/generate" \
  -H "$AUTH" -H "$CT" \
  -d "$BODY")
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
fi

  sleep 15
  POLL_RESP=$(curl -s -H "$AUTH" "${API_BASE}/generate/record-info?taskId=${TASK_ID}")
  STATUS=$(echo "$POLL_RESP" | python3 -c "
import sys, json
d = json.load(sys.stdin)
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
import json
print(json.dumps({'taskId': '$TASK_ID', 'audioId': '$AUDIO_ID'}))
")
    LYRICS_RESP=$(curl -s -X POST "${API_BASE}/generate/get-timestamped-lyrics" \
      -H "$AUTH" -H "$CT" \
      -d "$LYRICS_BODY")
Confidence
90% confidence
Finding
The lyrics response is embedded into a Python command via shell substitution inside a quoted -c string. Although the code tries to escape single quotes, adversarial API content can still make this fragile and may lead to command or parser breakage, turning untrusted remote data into part of executable interpreter input.

External Script Fetching

High
Category
Supply Chain
Content
'callBackUrl': 'https://localhost/noop',
}))
")
    MV_RESP=$(curl -s -X POST "${API_BASE}/mp4/generate" \
      -H "$AUTH" -H "$CT" \
      -d "$MV_BODY")
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
fi
        sleep 20

        MV_POLL=$(curl -s -H "$AUTH" "${API_BASE}/mp4/record-info?taskId=${MV_TASK_ID}")
        MV_STATUS=$(echo "$MV_POLL" | python3 -c "import sys,json; print(json.load(sys.stdin).get('data',{}).get('successFlag','PENDING'))" 2>/dev/null || echo "PENDING")
        MV_VIDEO_URL=$(echo "$MV_POLL" | python3 -c "
import sys, json
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
print(json.dumps(body, ensure_ascii=False))
" > "$PERSONA_BODY_FILE"

    PERSONA_RESP=$(curl -s -X POST "${API_BASE}/generate/generate-persona" \
      -H "$AUTH" -H "$CT" \
      -d @"$PERSONA_BODY_FILE")
    rm -f "$PERSONA_BODY_FILE"
Confidence
90% confidence
Finding
User-controlled values derived from TITLE, PROMPT, STYLE, PERSONA_NAME, and PERSONA_DESC are interpolated directly into a python3 -c string using triple-quoted literals. A crafted value containing quote sequences can break out of the Python string and inject arbitrary Python code, leading to local code execution under the script's privileges.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
@pytest.fixture(scope="session")
def skill_env():
    """Environment with API keys from environment variables."""
    return os.environ.copy()


@pytest.fixture
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
@pytest.fixture(scope="session")
def skill_env():
    """Environment with API keys from environment variables."""
    return os.environ.copy()


@pytest.fixture
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Content
img_dir = os.path.join(work_dir, "images")
        os.makedirs(img_dir, exist_ok=True)
        for i in range(3):
            os.system(
                f'ffmpeg -y -f lavfi -i color=c=blue:s=1024x1024:d=1 '
                f'"{img_dir}/scene_{i:03d}.png" 2>/dev/null'
            )
Confidence
85% confidence
Finding
os.system() and os exec-family calls run shell commands with the process's full privileges, enabling arbitrary command execution.

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Content
img_dir = os.path.join(work_dir, "images")
        os.makedirs(img_dir, exist_ok=True)
        for i in range(3):
            os.system(
                f'ffmpeg -y -f lavfi -i color=c=blue:s=1024x1024:d=1 '
                f'"{img_dir}/scene_{i:03d}.png" 2>/dev/null'
            )
Confidence
85% confidence
Finding
os.system() and os exec-family calls run shell commands with the process's full privileges, enabling arbitrary command execution.

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Content
)
        img_dir = os.path.join(work_dir, "images")
        os.makedirs(img_dir, exist_ok=True)
        os.system(
            f'ffmpeg -y -f lavfi -i color=c=blue:s=1024x1024:d=1 '
            f'"{img_dir}/scene_000.png" 2>/dev/null'
        )
Confidence
85% confidence
Finding
os.system() and os exec-family calls run shell commands with the process's full privileges, enabling arbitrary command execution.

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Content
"""
        # Arrange: create synthetic audio (10s silence)
        audio_path = os.path.join(work_dir, "test_audio.mp3")
        os.system(
            f'ffmpeg -y -f lavfi -i anullsrc=r=44100:cl=stereo -t 10 '
            f'-q:a 9 "{audio_path}" 2>/dev/null'
        )
Confidence
85% confidence
Finding
os.system() and os exec-family calls run shell commands with the process's full privileges, enabling arbitrary command execution.

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Content
"""
        # Arrange: create synthetic audio (10s silence)
        audio_path = os.path.join(work_dir, "test_audio.mp3")
        os.system(
            f'ffmpeg -y -f lavfi -i anullsrc=r=44100:cl=stereo -t 10 '
            f'-q:a 9 "{audio_path}" 2>/dev/null'
        )
Confidence
85% confidence
Finding
os.system() and os exec-family calls run shell commands with the process's full privileges, enabling arbitrary command execution.

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Content
"""
        # Arrange: create synthetic audio (10s silence)
        audio_path = os.path.join(work_dir, "test_audio.mp3")
        os.system(
            f'ffmpeg -y -f lavfi -i anullsrc=r=44100:cl=stereo -t 10 '
            f'-q:a 9 "{audio_path}" 2>/dev/null'
        )
Confidence
85% confidence
Finding
os.system() and os exec-family calls run shell commands with the process's full privileges, enabling arbitrary command execution.

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Content
"""
        # Arrange: create synthetic audio (10s silence)
        audio_path = os.path.join(work_dir, "test_audio.mp3")
        os.system(
            f'ffmpeg -y -f lavfi -i anullsrc=r=44100:cl=stereo -t 10 '
            f'-q:a 9 "{audio_path}" 2>/dev/null'
        )
Confidence
85% confidence
Finding
os.system() and os exec-family calls run shell commands with the process's full privileges, enabling arbitrary command execution.

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Content
colors = ["red", "green", "blue"]
        for i, color in enumerate(colors):
            img_path = os.path.join(img_dir, f"scene_{i:03d}.png")
            os.system(
                f'ffmpeg -y -f lavfi -i color=c={color}:s=1024x1024:d=1 '
                f'-frames:v 1 "{img_path}" 2>/dev/null'
            )
Confidence
85% confidence
Finding
os.system() and os exec-family calls run shell commands with the process's full privileges, enabling arbitrary command execution.

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Content
"""Assemble slideshow with crossfade transitions."""
        # Arrange
        audio_path = os.path.join(work_dir, "audio.mp3")
        os.system(
            f'ffmpeg -y -f lavfi -i "sine=f=440:d=12" '
            f'-q:a 9 "{audio_path}" 2>/dev/null'
        )
Confidence
85% confidence
Finding
os.system() and os exec-family calls run shell commands with the process's full privileges, enabling arbitrary command execution.

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Content
os.makedirs(img_dir, exist_ok=True)
        for i in range(3):
            img_path = os.path.join(img_dir, f"scene_{i:03d}.png")
            os.system(
                f'ffmpeg -y -f lavfi -i color=c=0x{i*80:02x}{i*40:02x}FF:s=1920x1080:d=1 '
                f'-frames:v 1 "{img_path}" 2>/dev/null'
            )
Confidence
85% confidence
Finding
os.system() and os exec-family calls run shell commands with the process's full privileges, enabling arbitrary command execution.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill documentation declares required binaries and environment variables and clearly instructs running shell scripts that perform network access, file reads/writes, and media assembly, but it does not declare an explicit tool scope such as permissions or allowed-tools. This creates an authorization ambiguity where an agent platform may expose broader capabilities than users expect, increasing the risk of unintended command execution, filesystem access, or outbound requests.

Vague Triggers

Medium
Confidence
90% confidence
Finding
The Quick Start examples are broad natural-language triggers for expensive, networked generation actions and do not define clear activation boundaries, confirmation requirements, or exclusions. In an agent setting, ambiguous triggers can cause the skill to activate unintentionally, leading to unreviewed API usage, third-party data transmission, and unexpected spending.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The documentation instructs users to provide API keys and describes callback behavior and third-party providers, but it lacks a prominent user-facing warning that prompts, lyrics, generated media, and callback metadata may be transmitted to external services. This can lead users to unknowingly send potentially sensitive creative content or metadata off-platform without informed consent.

Static analysis

No suspicious patterns detected.