Back to skill

Security audit

Autonoannounce

Security checks for vulnerabilities and agentic risk

Overview

This skill mostly matches its local TTS purpose, but its playback and configuration scripts expose avoidable command-execution and file-write risks that users should review before installing.

Install only if you trust the skill package and are comfortable with it using your ElevenLabs API key, sending synthesis/preflight requests to ElevenLabs, playing local audio, and writing local config/audio state. Before routine use, restrict playback backends to a fixed allowlist, fix the PowerShell invocation to pass filenames as data, constrain earcon library writes to the skill state directory, and replace fixed /tmp response files with private temporary files.

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/play-local-audio.sh:71
Finding
Configuration-Controlled Arbitrary Command Execution Through Custom Playback Backend<![CDATA[ ## Vulnerability Details **File Location**: `scripts/play-local-audio.sh:25-42, 71-75` **Vulnerability Type**: Configuration-driven arbitrary command execution **Risk Level**: High ### Vulnerable Code ```bash if [[ -f "$CFG" && ( -z "$BACKEND" || -z "$DEVICE" ) ]]; then mapfile -t vals < <(python3 - "$CFG" <<'PY' import json,sys try: c=json.load(open(sys.argv[1])) p=c.get('playback',{}) print(p.get('backend','')) print(p.get('device','')) except Exception: print('') print('') PY ) [[ -z "$BACKEND" ]] && BACKEND="${vals[0]:-}" [[ -z "$DEVICE" ]] && DEVICE="${vals[1]:-}" fi ``` ```bash *) if command -v "$BACKEND" >/dev/null 2>&1; then exec "$BACKEND" "$FILE" fi echo "unknown/unavailable backend: $BACKEND" >&2 exit 1 ;; ``` ### Technical Analysis The playback backend can be loaded from `config/tts-queue.json`. Although known backends are handled by dedicated branches, the default branch permits any value that resolves through `command -v`. The selected executable is then launched as: ```bash exec "$BACKEND" "$FILE" ``` Shell metacharacters inside `BACKEND` are not directly interpreted because the variable is quoted. Nevertheless, the absence of an executable allowlist creates an arbitrary program execution primitive. An attacker can select an interpreter such as `bash`, `python3`, or another executable available through `PATH`. The supplied audio-file argument then becomes input to that executable. For example, if `playback.backend` is set to `bash` and the requested “audio file” is an existing shell script, the script is executed rather than played. ### Attack Path 1. The attacker obtains the ability to modify `config/tts-queue.json`, influence first-run setup values, or otherwise control the playback backend argument. 2. The attacker sets `playback.backend` to an interpreter or attacker-controlled executable available through `PATH`. 3. The attacker creates or selects an existing file that passes the script's ` ...[truncated 773 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove support for unrestricted custom backend executable names. 2. Enforce a strict allowlist containing only explicitly supported values: - `mpv` - `ffplay` - `paplay` - `afplay` - `powershell-soundplayer` 3. Reject all other backend values with a nonzero exit status. 4. Validate configuration when it is created and again when it is consumed. 5. If custom backends are required, define them through administrator-controlled configuration and require: - A canonical absolute executable path. - Ownership and permission checks. - Rejection of interpreters and writable executable paths. - A fixed argument template that cannot treat the audio file as source code. 6. Add tests confirming that values such as `bash`, `python3`, and arbitrary executables are rejected. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/play-local-audio.sh:65
Finding
PowerShell Command Injection Through an Audio Filename<![CDATA[ ## Vulnerability Details **File Location**: `scripts/play-local-audio.sh:65-67` **Vulnerability Type**: PowerShell command injection **Risk Level**: High ### Vulnerable Code ```bash powershell-soundplayer) exec powershell -NoProfile -Command "(New-Object Media.SoundPlayer '$FILE').PlaySync();" ;; ``` ### Technical Analysis The audio filename is inserted directly into PowerShell source code inside a single-quoted PowerShell string. The shell-level quoting does not make the generated PowerShell program safe. A filename containing a single quote can terminate the PowerShell string. Subsequent filename characters can then be parsed as PowerShell expressions or commands. Because the script only verifies that the specified path refers to an existing file, a maliciously named file can satisfy the validation and still alter the generated command. This is a source-code construction vulnerability: data is concatenated into a command-language program rather than being passed through a non-code argument channel. ### Attack Path 1. The attacker creates an existing file whose path contains a single quote and PowerShell syntax. 2. The attacker selects that path as the audio file. 3. The playback backend is selected as `powershell-soundplayer`, either through configuration or a command-line option. 4. `play-local-audio.sh` interpolates the path into the `-Command` string. 5. The embedded quote terminates the intended PowerShell string. 6. PowerShell parses and executes the injected syntax with the privileges of the Skill process. ### Impact Assessment Successful exploitation permits arbitrary PowerShell command execution under the current user's security context. An attacker could read or modify accessible files, inspect process environment credentials, start programs, or make network requests. No independent privilege escalation is present, but the execution scope includes all resources available to the user or service account running the Skill. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions Do not interpolate the filename into PowerShell source code. Pass it through a data-only channel to a fixed script block. One suitable approach is to use an environment variable while keeping the PowerShell program constant: ```bash powershell-soundplayer) AUDIO_FILE="$FILE" exec powershell -NoProfile -Command \ '$player = New-Object System.Media.SoundPlayer $env:AUDIO_FILE; $player.PlaySync()' ;; ``` Additional hardening should include: 1. Prefer `pwsh` or `powershell` only after explicit executable detection. 2. Resolve the audio path to a canonical path before playback. 3. Reject paths containing control characters. 4. Do not attempt to secure this construction by merely replacing quotes; avoid dynamic command construction entirely. 5. Add regression tests using filenames containing quotes, semicolons, spaces, dollar signs, and PowerShell metacharacters. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/earcon-library.sh:43
Finding
Arbitrary Filesystem Write Through Unrestricted Earcon Library Path<![CDATA[ ## Vulnerability Details **File Location**: `scripts/earcon-library.sh:43-52, 78-87` **Vulnerability Type**: Unrestricted configuration-controlled file write **Risk Level**: Medium ### Vulnerable Code ```bash mapfile -t cfgvals < <(read_cfg) LIB_PATH_RAW="${cfgvals[0]}" if [[ "$LIB_PATH_RAW" = /* ]]; then LIB_PATH="$LIB_PATH_RAW" else LIB_PATH="$ROOT/$LIB_PATH_RAW" fi ensure_lib() { [[ -f "$LIB_PATH" ]] || echo '{"version":1,"earcons":{}}' > "$LIB_PATH" } ``` ```python def awrite(path,obj): d=os.path.dirname(path) fd,tmp=tempfile.mkstemp(dir=d,prefix='.tmp-',text=True) with os.fdopen(fd,'w') as f: json.dump(obj,f,indent=2) f.write('\n') os.replace(tmp,path) awrite(cfgp,cfg) awrite(libp,lib) ``` ### Technical Analysis The `earcons.libraryPath` value is loaded from `config/tts-queue.json` and treated as a trusted filesystem destination. Absolute paths are explicitly accepted. Relative paths are appended to the project root without canonicalization, so traversal sequences can also escape the expected directory. The `init` operation creates the selected path if it does not exist. During metadata updates, Python writes a temporary file in the selected destination directory and atomically replaces the configured library file. Atomic replacement protects against partial writes but does not restrict the destination. Consequently, control over the configuration can be converted into creation or replacement of an attacker-selected file writable by the current user. Replacement requires the destination to contain JSON compatible with the preceding `json.load`, but that still includes many application configuration and state files. ### Attack Path 1. The attacker modifies `earcons.libraryPath` in `config/tts-queue.json`. 2. The attacker supplies either: - An absolute path outside the project, or - A relative path containing traversal components such as `../`. 3. The attacker invokes `earcon-library.sh init` to ...[truncated 815 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store the earcon library only under a fixed application-owned directory such as `$ROOT/.openclaw`. 2. Reject absolute paths from configuration. 3. Canonicalize the proposed destination and verify that it remains below the approved directory. 4. Reject traversal components and symbolic-link destinations. 5. Create the parent directory with restrictive permissions before writing. 6. Verify ownership and permissions of existing library files before replacing them. 7. Prefer storing only a filename in configuration rather than an arbitrary path. A canonical containment check should compare resolved paths, not string prefixes. For example, resolve both the approved directory and candidate parent, then require the candidate to be a descendant of the approved directory before calling `mkstemp` or performing redirection. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/elevenlabs-preflight.sh:47
Finding
Predictable Shared Temporary Files Allow Symlink-Based File Overwrite<![CDATA[ ## Vulnerability Details **File Location**: `scripts/elevenlabs-preflight.sh:47, 51-55` **Vulnerability Type**: Insecure predictable temporary-file usage **Risk Level**: Medium ### Vulnerable Code ```bash probe_get() { local ep="$1" local code if [[ "$MOCK_MODE" == "1" ]]; then case "$ep" in /v1/models|/v1/user/subscription) echo 200; return 0 ;; /v1/voices/*) echo 200; return 0 ;; *) echo 404; return 0 ;; esac fi code=$(curl -sS -o /tmp/el_preflight.json -w '%{http_code}' "${h[@]}" "$BASE$ep") echo "$code" } ``` ```bash probe_sfx_once() { local code code=$(curl -sS -o /tmp/el_sfx_pf.json -w '%{http_code}' -X POST "$BASE/v1/sound-generation" \ -H "xi-api-key: $KEY" -H 'Content-Type: application/json' \ -d '{"text":"short system chime","duration_seconds":1}') echo "$code" } ``` ### Technical Analysis The script writes HTTP responses to two fixed names in the globally shared `/tmp` directory: - `/tmp/el_preflight.json` - `/tmp/el_sfx_pf.json` These names are predictable and are not created with exclusive or symlink-safe semantics. On systems where another local user can create entries in `/tmp`, an attacker can pre-create either path as a symbolic link to a file writable by the victim. When `curl -o` opens the output path, it can follow the symbolic link and truncate or overwrite the link target with an ElevenLabs API response. Concurrent executions also overwrite each other's data. The response files are not removed after use. Depending on the system umask and existing file permissions, API response content may additionally remain readable to other local users. ### Attack Path 1. A local attacker predicts one of the fixed temporary filenames. 2. Before the victim runs the preflight script, the attacker creates that path as a symbolic link to a target writable by the victim account. 3. The victim runs `elevenlabs-preflight.sh`. 4. `curl` follows the attacker-created link when opening its outp ...[truncated 611 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create a private temporary directory with `mktemp -d`. 2. Restrict its permissions to the current user. 3. Store all response files inside that directory. 4. Register a cleanup trap so the directory is removed on normal exit, errors, and signals. 5. Do not reuse fixed paths across processes. 6. If response bodies are not needed, direct them to a safely opened disposable file or `/dev/null`. Example hardening pattern: ```bash TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/el-preflight.XXXXXX")" chmod 700 "$TMP_DIR" trap 'rm -rf -- "$TMP_DIR"' EXIT HUP INT TERM PREFLIGHT_RESPONSE="$TMP_DIR/preflight.json" SFX_RESPONSE="$TMP_DIR/sfx.json" ``` The resulting variables should replace the fixed `/tmp/el_preflight.json` and `/tmp/el_sfx_pf.json` paths. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (17)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The skill presents itself as an operational TTS pipeline but also exposes first-run/setup execution not described in the declared purpose. Undisclosed initialization or installation behavior can expand attack surface through environment changes, dependency setup, or filesystem modifications that reviewers did not expect.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The skill presents itself as an operational TTS pipeline but also exposes first-run/setup execution not described in the declared purpose. Undisclosed initialization or installation behavior can expand attack surface through environment changes, dependency setup, or filesystem modifications that reviewers did not expect.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
The skill presents itself as an operational TTS pipeline but also exposes first-run/setup execution not described in the declared purpose. Undisclosed initialization or installation behavior can expand attack surface through environment changes, dependency setup, or filesystem modifications that reviewers did not expect.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
Quick checks:
```bash
printenv ELEVENLABS_API_KEY | wc -c
printenv ELEVENLABS_VOICE_ID | wc -c
skills/autonoannounce/scripts/playback-probe.sh auto
skills/autonoannounce/scripts/playback-validate.sh
Confidence
70% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill advertises shell execution, environment-variable use, and file writes but does not declare an explicit tool scope such as permissions or allowed-tools. That omission weakens reviewability and least-privilege enforcement, making it easier for an agent to invoke broader capabilities than a user would reasonably infer from the metadata.

External Transmission

Medium
Category
Data Exfiltration
Content
ts=$(date +%s)
    out="$EARCON_DIR/${category}-${ts}.mp3"
    code=$(curl -sS -o "$out" -w '%{http_code}' -X POST "https://api.elevenlabs.io/v1/sound-generation" \
      -H "xi-api-key: ${ELEVENLABS_API_KEY}" \
      -H 'Content-Type: application/json' \
      -d "{\"text\":\"$prompt\",\"duration_seconds\":$duration}")
Confidence
70% 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
ts=$(date +%s)
    out="$EARCON_DIR/${category}-${ts}.mp3"
    code=$(curl -sS -o "$out" -w '%{http_code}' -X POST "https://api.elevenlabs.io/v1/sound-generation" \
      -H "xi-api-key: ${ELEVENLABS_API_KEY}" \
      -H 'Content-Type: application/json' \
      -d "{\"text\":\"$prompt\",\"duration_seconds\":$duration}")
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
94% confidence
Finding
The script sends the user-supplied or default prompt to ElevenLabs via an HTTP POST request. Although usage notes mention the API key requirement, the generate path itself does not disclose that prompt content is transmitted to a third-party service, which is a privacy-relevant network operation.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This shell script reads the ELEVENLABS_API_KEY from the environment and uses it in multiple curl requests to the ElevenLabs API, including a POST request that transmits request data. While the code handles missing credentials, it does not provide a user-facing warning, confirmation, or explanatory comment/docstring disclosing that it will use the credential and contact an external service.

External Transmission

Medium
Category
Data Exfiltration
Content
*) echo 404; return 0 ;;
    esac
  fi
  code=$(curl -sS -o /tmp/el_preflight.json -w '%{http_code}' "${h[@]}" "$BASE$ep")
  echo "$code"
}
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def detect_backend() -> str:
    script = SCRIPTS / "backend-detect.sh"
    try:
        out = subprocess.check_output([str(script)], text=True).strip()
        return out or "auto"
    except Exception:
        return "auto"
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def probe_devices(backend: str) -> list[str]:
    script = SCRIPTS / "playback-probe.sh"
    try:
        out = subprocess.check_output([str(script), backend], text=True)
    except Exception:
        return []
    devices = []
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
cmd = [str(SCRIPTS / "playback-test.sh"), "--backend", backend]
            if device:
                cmd += ["--device", device]
            subprocess.call(cmd)
            heard = prompt("Did you hear it? (y/n)", "y")
            if not heard.lower().startswith("y"):
                print("Tip: rerun setup and choose a different backend/device.")
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if cfg["earcons"]["enabled"] and generate and generate.lower().startswith("y"):
        gen_script = SCRIPTS / "earcon-library.sh"
        for cat in ["start", "end", "update", "important", "error"]:
            subprocess.call([str(gen_script), "generate", cat, f"{style} {cat} notification sound", "1"])
        print("Starter earcons generated (where API/key permits).")

    return 0
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Low
Confidence
80% confidence
Finding
The generate path creates an MP3 at a timestamped location and updates config/library metadata, which are file-writing operations. While the script prints a completion message after success, there is no prior warning, confirmation, or descriptive comment/docstring near the operation indicating that running generate will persist new files and modify configuration state.

Missing User Warnings

Low
Confidence
81% confidence
Finding
In noninteractive mode, the script assigns defaults and proceeds to write a configuration file containing playback settings and a voice ID sourced from CLI or environment variables, but it provides no prior disclosure before persisting that data. The file write is only announced after completion, and the noninteractive path skips the interactive prompts that otherwise provide context about what is being configured.

Missing User Warnings

Low
Confidence
95% confidence
Finding
This shell script overwrites or creates the file at "$ROOT/config/tts-queue.json" using a here-document, which is a file write affecting local configuration state. While the file is part of a test setup, there is no inline comment, prompt, or explicit warning near the write to disclose that the script modifies user or repository configuration.

Static analysis

No suspicious patterns detected.