Back to skill

Security audit

acestep-lyrics-transcription

Security checks for vulnerabilities and agentic risk

Overview

This transcription skill is mostly purpose-aligned, but it has serious implementation weaknesses that could expose API keys or run unintended local code with crafted inputs.

Review this skill before installing. It does what it says at a high level, but only use it with non-sensitive audio and disposable or tightly scoped API keys until the output-path, jq, and credential-handling issues are fixed. Avoid custom output paths containing special characters, do not rely on config --get for secrets, and ensure any stored config file is protected.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/acestep-lyrics-transcription.sh:147
Finding
Arbitrary Python Code Execution Through Crafted Output Path<![CDATA[ ## Vulnerability Details **File Location**: `scripts/acestep-lyrics-transcription.sh:147-197` and `scripts/acestep-lyrics-transcription.sh:210-258` **Vulnerability Type**: Python source injection through unsafe interpolation **Risk Level**: High ### Vulnerable Code The LRC conversion function directly embeds the output path into Python source: ```bash words_to_lrc() { local json_file="$(to_python_path "$1")" local output_file="$(to_python_path "$2")" local line_gap="${3:-1.5}" find_python $PYTHON_CMD -c " import json, sys, unicodedata # ... with open('$json_file', 'r', encoding='utf-8') as f: words = json.load(f) # ... with open('$output_file', 'w', encoding='utf-8') as f: for line in lines: f.write(line + '\n') " } ``` The SRT conversion function repeats the same pattern: ```bash words_to_srt() { local json_file="$(to_python_path "$1")" local output_file="$(to_python_path "$2")" local line_gap="${3:-1.5}" find_python $PYTHON_CMD -c " import json, sys # ... with open('$json_file', 'r', encoding='utf-8') as f: words = json.load(f) # ... with open('$output_file', 'w', encoding='utf-8') as f: for idx, (s, e, text) in enumerate(lines, 1): f.write(f'{idx}\n') f.write(f'{fmt(s)} --> {fmt(e)}\n') f.write(f'{text}\n') f.write('\n') " } ``` The value originates from the user-controlled `--output` argument: ```bash --output|-o) output="$2"; shift 2 ;; ``` It is subsequently passed to the vulnerable conversion function: ```bash case "$format" in lrc) words_to_lrc "$words_file" "$output" ;; srt) words_to_srt "$words_file" "$output" ;; json) cp "$words_file" "$output" ;; esac ``` ### Technical Analysis Shell quoting protects the output path while it is handled by Bash, but it does not make the value safe when it is inserted into dynamically constructed Python source code. A path containin ...[truncated 1831 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Do not interpolate paths or numeric values into Python source. Pass all data as command-line arguments: ```bash "$PYTHON_CMD" -c ' import json import sys json_file = sys.argv[1] output_file = sys.argv[2] line_gap = float(sys.argv[3]) with open(json_file, "r", encoding="utf-8") as f: words = json.load(f) # Conversion logic... with open(output_file, "w", encoding="utf-8") as f: # Write converted data... pass ' "$json_file" "$output_file" "$line_gap" ``` Additional hardening should include: 1. Validate `line_gap` as a numeric value before passing it to Python. 2. Normalize and validate output paths according to the intended write policy. 3. Use a standalone Python file rather than a dynamically generated `python -c` program. 4. Add tests using paths containing single quotes, double quotes, newlines, backslashes, Unicode characters, and shell/Python metacharacters. 5. Run the conversion component with only the filesystem permissions necessary to create the requested output. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/acestep-lyrics-transcription.sh:118
Finding
API Keys Are Exposed Through Command Arguments and Unmasked Output<![CDATA[ ## Vulnerability Details **File Location**: `scripts/acestep-lyrics-transcription.sh:118-126`, `scripts/acestep-lyrics-transcription.sh:479-489`; documentation at `SKILL.md:17-19`, `SKILL.md:41-43`, `SKILL.md:66-69`, and `SKILL.md:139-140` **Vulnerability Type**: Sensitive credential disclosure **Risk Level**: Medium ### Vulnerable Code The configuration setter prints the complete value after writing it: ```bash set_config() { local key="$1" local value="$2" ensure_config local tmp_file="${CONFIG_FILE}.tmp" local jq_path=".${key}" if [ "$value" = "true" ] || [ "$value" = "false" ]; then jq "$jq_path = $value" "$CONFIG_FILE" > "$tmp_file" elif [[ "$value" =~ ^-?[0-9]+$ ]] || [[ "$value" =~ ^-?[0-9]+\.[0-9]+$ ]]; then jq "$jq_path = $value" "$CONFIG_FILE" > "$tmp_file" else jq "$jq_path = \"$value\"" "$CONFIG_FILE" > "$tmp_file" fi mv "$tmp_file" "$CONFIG_FILE" echo "Set $key = $value" } ``` The generic getter also returns API keys without masking: ```bash "get") [ -z "$key" ] && { echo -e "${RED}Error: --get requires KEY${NC}"; exit 1; } local result=$(get_config "$key") [ -n "$result" ] && echo "$key = $result" || echo "Key not found: $key" ;; ``` The documentation instructs users or Agents to place keys directly in command arguments: ```bash bash ./scripts/acestep-lyrics-transcription.sh config --set <provider>.api_key <KEY> ``` ```bash ./scripts/acestep-lyrics-transcription.sh config --set openai.api_key sk-... ./scripts/acestep-lyrics-transcription.sh config --set elevenlabs.api_key ... ``` This conflicts with the documentation's security requirement: ```markdown **NEVER read or display the user's API key content.** Do not use `config --get` on key fields or read `config.json` directly. ``` ### Technical Analysis Passing an API key as a command-line argument can expose it through shell history, command auditing, process inspection, terminal recording, o ...[truncated 1750 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Never print configuration values when the key name identifies a secret: ```bash if [[ "$key" == *.api_key ]]; then echo "Set $key = ***" else echo "Set $key = $value" fi ``` 2. Reject direct retrieval of secret fields: ```bash if [[ "$key" == "openai.api_key" || "$key" == "elevenlabs.api_key" ]]; then echo "Access to secret configuration values is not permitted." >&2 exit 1 fi ``` 3. Replace command-line key arguments with one of the following: - A silent interactive prompt using `read -r -s`. - Standard input with clear controls preventing logging. - Provider-specific environment variables. - An operating-system credential store or secret manager. 4. If environment variables are supported, avoid printing them and clear temporary shell variables after use where practical. 5. Update `SKILL.md` so the documented setup flow does not place credentials in command arguments. 6. Rotate any keys that may already have appeared in shell history, process logs, Agent transcripts, or CI output. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/acestep-lyrics-transcription.sh:87
Finding
Plaintext API-Key Configuration Is Created Without Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/acestep-lyrics-transcription.sh:17`, `scripts/acestep-lyrics-transcription.sh:87-99`, and `scripts/acestep-lyrics-transcription.sh:118-125` **Vulnerability Type**: Insecure local storage of sensitive credentials **Risk Level**: Medium ### Vulnerable Code The configuration file is stored alongside the script: ```bash CONFIG_FILE="${SCRIPT_DIR}/config.json" ``` It is created by copying an example file or by ordinary shell redirection: ```bash ensure_config() { if [ ! -f "$CONFIG_FILE" ]; then local example="${SCRIPT_DIR}/config.example.json" if [ -f "$example" ]; then cp "$example" "$CONFIG_FILE" echo -e "${YELLOW}Config file created from config.example.json. Please configure your settings:${NC}" echo -e " ${CYAN}./scripts/acestep-lyrics-transcription.sh config --set provider <openai|elevenlabs>${NC}" echo -e " ${CYAN}./scripts/acestep-lyrics-transcription.sh config --set <provider>.api_key <key>${NC}" else echo "$DEFAULT_CONFIG" > "$CONFIG_FILE" fi fi } ``` Configuration changes use a predictable temporary filename and replace the original file without enforcing its mode: ```bash set_config() { local key="$1" local value="$2" ensure_config local tmp_file="${CONFIG_FILE}.tmp" local jq_path=".${key}" if [ "$value" = "true" ] || [ "$value" = "false" ]; then jq "$jq_path = $value" "$CONFIG_FILE" > "$tmp_file" elif [[ "$value" =~ ^-?[0-9]+$ ]] || [[ "$value" =~ ^-?[0-9]+\.[0-9]+$ ]]; then jq "$jq_path = $value" "$CONFIG_FILE" > "$tmp_file" else jq "$jq_path = \"$value\"" "$CONFIG_FILE" > "$tmp_file" fi mv "$tmp_file" "$CONFIG_FILE" echo "Set $key = $value" } ``` ### Technical Analysis The script persists OpenAI and ElevenLabs API keys as plaintext JSON. It does not set a restrictive `umask`, create the file with mode `0600`, ver ...[truncated 1484 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Set an owner-only umask before creating any credential-bearing file: ```bash umask 077 ``` 2. Enforce permissions after creation and every replacement: ```bash chmod 600 "$CONFIG_FILE" ``` 3. Create update files securely with `mktemp` in a trusted directory: ```bash tmp_file=$(mktemp "${CONFIG_FILE}.tmp.XXXXXX") chmod 600 "$tmp_file" ``` 4. Install cleanup traps so temporary files containing credentials are removed on failure: ```bash trap 'rm -f "$tmp_file"' EXIT ``` 5. Verify that the configuration path is a regular file owned by the current user before reading or replacing it. 6. Prefer environment variables, an operating-system keychain, or a dedicated secret manager over plaintext project-local storage. 7. Exclude `scripts/config.json` and temporary configuration files from version control, archives, diagnostics, and artifact collection. 8. Document the plaintext-storage behavior and advise users to rotate credentials if the file was ever stored with permissive permissions. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/acestep-lyrics-transcription.sh:102
Finding
Configuration Key and Value Injection Into jq Programs<![CDATA[ ## Vulnerability Details **File Location**: `scripts/acestep-lyrics-transcription.sh:102-126` and `scripts/acestep-lyrics-transcription.sh:479-489` **Vulnerability Type**: jq expression injection and configuration tampering **Risk Level**: Medium ### Vulnerable Code Configuration keys are converted directly into jq path expressions: ```bash get_config() { local key="$1" ensure_config local jq_path=".${key}" local value value=$(jq -r "$jq_path" "$CONFIG_FILE" 2>/dev/null) if [ "$value" = "null" ]; then echo "" else echo "$value" | tr -d '\r\n' fi } ``` Both the key and string value are interpolated into an executable jq program: ```bash set_config() { local key="$1" local value="$2" ensure_config local tmp_file="${CONFIG_FILE}.tmp" local jq_path=".${key}" if [ "$value" = "true" ] || [ "$value" = "false" ]; then jq "$jq_path = $value" "$CONFIG_FILE" > "$tmp_file" elif [[ "$value" =~ ^-?[0-9]+$ ]] || [[ "$value" =~ ^-?[0-9]+\.[0-9]+$ ]]; then jq "$jq_path = $value" "$CONFIG_FILE" > "$tmp_file" else jq "$jq_path = \"$value\"" "$CONFIG_FILE" > "$tmp_file" fi mv "$tmp_file" "$CONFIG_FILE" echo "Set $key = $value" } ``` The arguments are accepted without an allowlist: ```bash --get) action="get"; key="$2"; shift 2 ;; --set) action="set"; key="$2"; value="$3"; shift 3 ;; ``` ### Technical Analysis `jq` treats its filter argument as program source. Concatenating untrusted data into that source permits a crafted key or quoted value to terminate the intended path or string expression and append additional jq filters. This is not shell-command injection: jq does not inherently execute arbitrary operating-system commands. However, it permits unintended reads and transformations of configuration data and can overwrite fields outside the nominal key selected by the caller. The configuration includes `api_url`, `api_key`, `provid ...[truncated 1522 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Allowlist supported configuration keys: ```bash case "$key" in provider|output_format|openai.api_key|openai.api_url|openai.model|\ elevenlabs.api_key|elevenlabs.api_url|elevenlabs.model) ;; *) echo "Unsupported configuration key" >&2 exit 1 ;; esac ``` 2. Pass values with `--arg` or `--argjson` instead of concatenating them into jq source: ```bash jq --arg key "$key" --arg value "$value" \ 'setpath($key | split("."); $value)' \ "$CONFIG_FILE" > "$tmp_file" ``` 3. Use `getpath` for reads: ```bash jq -r --arg key "$key" 'getpath($key | split(".")) // empty' "$CONFIG_FILE" ``` 4. Apply explicit type validation after identifying the field. Validate `provider` and `output_format` against fixed enumerations. 5. Validate API URLs before storing or using them: - Require HTTPS. - Restrict hosts to approved provider domains unless custom endpoints are an explicit trusted feature. - Do not attach credentials when the destination is outside an approved origin. 6. Validate the resulting JSON against a fixed schema before replacing the active configuration. 7. Write updates to a securely created temporary file and perform an atomic replacement only after all validation succeeds. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (15)

Chaining Abuse

High
Category
Tool Misuse
Content
case "$provider" in
        openai)   transcribe_openai "$audio" "$language" "$words_file" ;;
        elevenlabs) transcribe_elevenlabs "$audio" "$language" "$words_file" ;;
        *) echo -e "${RED}Error: unknown provider: $provider${NC}"; echo "Supported: openai, elevenlabs"; rm -f "$words_file"; exit 1 ;;
    esac

    # Check if we got words
Confidence
75% confidence
Finding
Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
```
6. After configuring, re-run `config --check-key` to verify the key is set before proceeding.

**If the API key is already configured**, proceed directly to transcription without asking.

## Quick Start
Confidence
75% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Description-Behavior Mismatch

Medium
Confidence
92% confidence
Finding
The manifest presents the capability as transcription, but the workflow goes further by performing semantic correction using external lyric references and rewriting the output file. This mismatch can cause an agent or user to trust the skill as a simple transform of the audio when it is actually incorporating additional data sources and making substantive content edits.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The skill is described as audio transcription, but it instructs the agent to read an ACE-Step output JSON file containing original lyrics and use that external content to modify the transcription. This expands access beyond the user-provided audio input and can pull in unrelated or copyrighted source material, violating least-privilege expectations for a transcription skill.

External Transmission

Medium
Category
Data Exfiltration
Content
"output_format": "lrc",
  "openai": {
    "api_key": "",
    "api_url": "https://api.openai.com/v1",
    "model": "whisper-1"
  },
  "elevenlabs": {
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
"output_format": "lrc",
  "openai": {
    "api_key": "",
    "api_url": "https://api.openai.com/v1",
    "model": "whisper-1"
  },
  "elevenlabs": {
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
"output_format": "lrc",
  "openai": {
    "api_key": "",
    "api_url": "https://api.openai.com/v1",
    "model": "whisper-1"
  },
  "elevenlabs": {
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
"output_format": "lrc",
  "openai": {
    "api_key": "",
    "api_url": "https://api.openai.com/v1",
    "model": "whisper-1"
  },
  "elevenlabs": {
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
},
  "elevenlabs": {
    "api_key": "",
    "api_url": "https://api.elevenlabs.io/v1",
    "model": "scribe_v2"
  }
}'
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
},
  "elevenlabs": {
    "api_key": "",
    "api_url": "https://api.elevenlabs.io/v1",
    "model": "scribe_v2"
  }
}'
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
},
  "elevenlabs": {
    "api_key": "",
    "api_url": "https://api.elevenlabs.io/v1",
    "model": "scribe_v2"
  }
}'
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
},
  "elevenlabs": {
    "api_key": "",
    "api_url": "https://api.elevenlabs.io/v1",
    "model": "scribe_v2"
  }
}'
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The script uploads user-supplied audio to the OpenAI transcription API, but it does not provide an explicit warning or confirmation at the point of transmission. In a skill context, this can cause unintended disclosure of sensitive audio content because users may assume processing is local unless clearly told otherwise.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The ElevenLabs path sends the provided audio file to a third-party speech-to-text service without an explicit warning immediately before transmission. This creates a privacy risk because song files, recordings, or embedded personal audio may be exfiltrated off-host without sufficiently informed user consent.

Natural-Language Policy Violations

Low
Confidence
98% confidence
Finding
The script unconditionally sets LANG and LC_ALL to en_US.UTF-8 when they are unset, which imposes a specific locale choice in natural-language-related processing. This can violate language/locale policy when users are not offered a choice or informed of the override.

Static analysis

No suspicious patterns detected.