Back to skill

Security audit

Telegram Voice Messaging Recovery

Security checks for vulnerabilities and agentic risk

Overview

The skill is a plausible voice-messaging recovery helper, but it has serious unsafe execution and under-disclosed remote TTS/data-retention behavior that users should review before installing.

Install only if you are comfortable with network downloads, a hosted Edge/Microsoft TTS service receiving text for synthesis, local caching of generated audio, and scripts living under OpenClaw voice directories. Do not run this as root or against untrusted audio paths until the command-injection and unsafe-temp-file issues are fixed, dependencies are pinned, and the missing transcribe-audio artifact is resolved.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/voice_handler.py:32
Finding
Command and Python Code Injection Through an Attacker-Controlled Audio Path<![CDATA[ ## Vulnerability Details **File Location**: `scripts/voice_handler.py`, lines 32-49 **Vulnerability Type**: Shell command injection and dynamically generated Python code injection **Risk Level**: High ### Vulnerable Code ```python if audio_file.endswith('.ogg'): wav_file = tempfile.mktemp(suffix=".wav") cmd = f"ffmpeg -i '{audio_file}' -ar 16000 -ac 1 '{wav_file}' -y 2>/dev/null" subprocess.run(cmd, shell=True, check=True) audio_file = wav_file # Transcribe with faster-whisper cmd = [ sys.executable, "-c", """ from faster_whisper import WhisperModel import sys model = WhisperModel('%s', device='cpu', compute_type='int8') segments, info = model.transcribe('%s', beam_size=5) text = ' '.join(segment.text for segment in segments) print(json.dumps({'text': text, 'language': info.language, 'probability': info.language_probability})) """ % (self.stt_model, audio_file) ] result = subprocess.run(cmd, capture_output=True, text=True) ``` ### Technical Analysis The `audio_file` value is embedded into two executable contexts without escaping: 1. For OGG files, it is interpolated into a shell command passed to `subprocess.run(..., shell=True)`. Single quotes in the path can terminate the intended shell argument and introduce shell operators and arbitrary commands. 2. The resulting path is interpolated into source code supplied to `python -c`. A single quote can terminate the Python string literal and append arbitrary Python statements. Using single quotes around the shell argument does not make the operation safe when the interpolated value itself can contain a single quote. Likewise, passing the generated Python program as an argument array only protects the outer process invocation; it does not prevent injection into the dynamically constructed source code. ### Attack Path 1. An attacker causes the voice-processing entry point to receive an audio path containing shell or Python metacharacters. This requires influence over the path pas ...[truncated 1264 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove `shell=True` and pass ffmpeg arguments as a list: ```python subprocess.run( [ "ffmpeg", "-i", audio_file, "-ar", "16000", "-ac", "1", wav_file, "-y", ], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) ``` - Do not generate Python source with string interpolation. Import and invoke `WhisperModel` directly in the current process: ```python from faster_whisper import WhisperModel model = WhisperModel( self.stt_model, device="cpu", compute_type="int8", ) segments, info = model.transcribe(audio_file, beam_size=5) text = " ".join(segment.text for segment in segments) ``` - Canonicalize input paths with `Path.resolve()` and enforce that they remain under an approved media directory. - Reject non-regular files, symbolic links, unexpected extensions, and paths exceeding reasonable length limits. - Run media processing under a dedicated unprivileged account with narrowly scoped filesystem access. - Add regression tests using filenames containing quotes, semicolons, command substitutions, newlines, and Python syntax. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/install.sh:76
Finding
Unpinned Third-Party Packages Are Downloaded and Installed at Runtime<![CDATA[ ## Vulnerability Details **File Location**: `scripts/install.sh`, lines 76-79 **Vulnerability Type**: Unverified and unpinned dependency installation **Risk Level**: Medium ### Vulnerable Code ```bash # Upgrade pip first pip install --upgrade pip # Install required packages pip install faster-whisper edge-tts soundfile ``` ### Technical Analysis The installer retrieves the latest available versions of `pip`, `faster-whisper`, `edge-tts`, `soundfile`, and their transitive dependencies. No exact versions, lock file, package hashes, or trusted artifact verification are used. Python package installation may execute package build backends and installation-related code. Because the effective package set can change after the Skill has been reviewed, a compromised package release, transitive dependency, package index, or network trust chain could introduce arbitrary code into the installation process. The unqualified `pip` command also relies on the active environment and executable search path. Although the script activates a virtual environment earlier, explicitly invoking the virtual environment’s interpreter would provide stronger assurance that the intended installer is used. ### Attack Path 1. An administrator or automated process runs `scripts/install.sh`. 2. The script contacts the configured Python package index and resolves mutable latest package versions. 3. An attacker compromises a package release, a transitive dependency, the configured package repository, or another relevant supply-chain component. 4. The installer downloads the malicious artifact because neither its expected version nor its cryptographic hash is constrained. 5. Malicious build or runtime code executes with the installer’s privileges, or remains in the virtual environment and executes when the voice helper is subsequently used. ### Impact Assessment The immediate privileges are those of the user running the installer. The script also invokes `apt-get` when dependencie ...[truncated 426 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin every direct dependency to a reviewed exact version. - Generate and commit a lock file that includes transitive dependencies. - Require cryptographic hashes, for example through a hash-locked requirements file and: ```bash "$VENV_DIR/bin/python" -m pip install \ --require-hashes \ -r "$SKILL_DIR/requirements.lock" ``` - Avoid an unconditional `pip install --upgrade pip`. Pin and verify the installer version when an upgrade is necessary. - Use `"$VENV_DIR/bin/python" -m pip` rather than relying on the unqualified `pip` executable. - Use a controlled package mirror or repository with artifact retention, access controls, and integrity monitoring. - Perform installation as an unprivileged deployment account where possible. Separate privileged system-package installation from Python dependency installation. - Add dependency vulnerability and provenance scanning to the release process. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/voice_handler.py:32
Finding
Unsafe Temporary File Creation in the Voice Handler<![CDATA[ ## Vulnerability Details **File Location**: `scripts/voice_handler.py`, lines 32 and 72 **Vulnerability Type**: Predictable temporary filename and time-of-check/time-of-use race **Risk Level**: Medium ### Vulnerable Code ```python if audio_file.endswith('.ogg'): wav_file = tempfile.mktemp(suffix=".wav") cmd = f"ffmpeg -i '{audio_file}' -ar 16000 -ac 1 '{wav_file}' -y 2>/dev/null" subprocess.run(cmd, shell=True, check=True) audio_file = wav_file ``` ```python if output_file is None: output_file = tempfile.mktemp(suffix=".wav", prefix="tts_") cmd = [sys.executable, self.tts_script, text, output_file] result = subprocess.run(cmd, capture_output=True, text=True) ``` ### Technical Analysis `tempfile.mktemp()` returns an unused pathname but does not atomically create and reserve the file. A separate local process can create a file or symbolic link at that pathname before ffmpeg or the TTS wrapper opens it. This creates a time-of-check/time-of-use race. The later writer does not verify that the destination is still a newly created regular file owned by the current user. Temporary artifacts are also not removed reliably, potentially leaving transcriptions or generated speech in the shared temporary directory. ### Attack Path 1. A local attacker monitors the shared temporary directory for candidate `tmp*.wav` or `tts_*.wav` names, or repeatedly creates likely names. 2. After `mktemp()` selects a pathname but before the consuming process opens it, the attacker creates a symbolic link at that path. 3. ffmpeg or the TTS process follows the attacker-controlled link when writing output. 4. If the helper has greater filesystem privileges than the attacker, the write can target a file the attacker could not modify directly. 5. Alternatively, the attacker can substitute or read temporary audio data where filesystem permissions permit. This attack requires local access and successful timing of the race, but repeated voice processing can pro ...[truncated 488 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Replace every `tempfile.mktemp()` call with an API that atomically creates the file, such as `NamedTemporaryFile` or `mkstemp`. - Keep restrictive permissions and explicitly pass the securely created path to downstream tools: ```python fd, wav_file = tempfile.mkstemp(suffix=".wav") os.close(fd) try: subprocess.run( ["ffmpeg", "-i", audio_file, "-ar", "16000", "-ac", "1", wav_file, "-y"], check=True, ) # Process wav_file here. finally: try: os.unlink(wav_file) except FileNotFoundError: pass ``` - For generated output that must survive after the method returns, create a private runtime directory with mode `0700` and create files inside it atomically. - Reject symbolic links and verify file ownership and type before consuming an existing destination. - Ensure all temporary files are removed in `finally` blocks. - Avoid running audio conversion as `root`. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/tts_edge_wrapper.py:43
Finding
Unsafe Temporary File Creation in the Edge TTS Wrapper<![CDATA[ ## Vulnerability Details **File Location**: `scripts/tts_edge_wrapper.py`, lines 43-46 **Vulnerability Type**: Predictable temporary filename and symbolic-link race **Risk Level**: Medium ### Vulnerable Code ```python tmp_out = Path(output_file) if output_file else Path(tempfile.mktemp(suffix='.mp3', prefix='edge_tts_')) asyncio.run(_synthesize(text, tmp_out, voice, rate, pitch, volume)) tmp_out.parent.mkdir(parents=True, exist_ok=True) subprocess.run(['cp', str(tmp_out), str(cache_path)], check=True) ``` ### Technical Analysis When no output path is supplied, `tempfile.mktemp()` chooses a temporary pathname without atomically creating it. Another local process can occupy that pathname with a regular file or symbolic link before `Communicate.save()` opens it. The wrapper then copies the resulting path into the persistent cache. Consequently, a successful race can potentially redirect the synthesis write or cause attacker-controlled content to be copied into the cache. The temporary output is not deleted after it is cached. ### Attack Path 1. A local attacker monitors or races candidate `/tmp/edge_tts_*.mp3` names. 2. The wrapper obtains a pathname through `mktemp()` without reserving it. 3. Before `_synthesize()` opens the destination, the attacker creates a symbolic link or replacement file at that path. 4. Edge TTS writes through the attacker-selected filesystem object, or the wrapper later copies substituted content to the deterministic cache path. 5. The resulting overwrite or cache poisoning occurs with the privileges of the TTS process. The attacker must have local access and win the race window. Repeated TTS requests increase the number of opportunities. ### Impact Assessment Potential consequences include overwriting a file writable by the privileged TTS process, poisoning cached audio returned for later requests, output substitution, and unnecessary retention of generated speech in a shared temporary directory. If the process runs as ...[truncated 59 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Atomically create the temporary output and ensure it is removed after caching: ```python fd, tmp_name = tempfile.mkstemp(suffix=".mp3", prefix="edge_tts_") os.close(fd) tmp_out = Path(tmp_name) try: asyncio.run(_synthesize(text, tmp_out, voice, rate, pitch, volume)) cache_path.parent.mkdir(parents=True, exist_ok=True) os.replace(tmp_out, cache_path) finally: tmp_out.unlink(missing_ok=True) ``` - If the synthesis library cannot safely write to an already-created file, create a private temporary directory with `tempfile.TemporaryDirectory()` and mode-restricted access, then write a fixed filename inside it. - Create destination parent directories before synthesis rather than afterward. - Use Python filesystem APIs such as `shutil.copyfile` or `os.replace` instead of spawning `cp`. - Apply restrictive permissions to the cache directory and cached files. - For concurrent requests, use per-cache-key locking and atomically publish the completed cache entry to prevent partial or poisoned cache files. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (27)

Tool Parameter Abuse

High
Category
Tool Misuse
Content
PY
    then
        log_info "✓ TTS test passed"
        rm -f "/tmp/test_install.mp3"
    else
        log_error "TTS test failed"
        return 1
Confidence
95% 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).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
if audio_file.endswith('.ogg'):
                wav_file = tempfile.mktemp(suffix=".wav")
                cmd = f"ffmpeg -i '{audio_file}' -ar 16000 -ac 1 '{wav_file}' -y 2>/dev/null"
                subprocess.run(cmd, shell=True, check=True)
                audio_file = wav_file
            
            # Transcribe with faster-whisper
Confidence
99% confidence
Finding
This is a true tool-parameter-abuse issue because untrusted input is passed into a shell command string. In the context of a voice handler, audio file paths may come from external sources or other components, making arbitrary command execution especially dangerous if the agent runs with elevated privileges or broad filesystem access.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The description mandates `messages.tts.providers.microsoft.voice = en-IE-ConnorNeural` and `messages.tts.providers.microsoft.lang = en-IE`, which forces a specific language/locale configuration. This is a natural-language locale policy concern because the file does not present this as optional, user-selectable, or justified as region-specific compliance.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The sample configuration sets `voice` to `en-IE-ConnorNeural` and `lang` to `en-IE`, presenting a single locale as the prescribed setup. Because the markdown does not mention alternatives or user choice, it risks violating language/locale policy expectations.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The script presents itself as an offline/local voice installer, but later explicitly states it uses the hosted Edge TTS service. This mismatch can mislead operators about privacy and data-flow expectations, causing them to install a component that sends text to a remote provider when they believed processing would remain local.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The installer automatically runs apt-get update and installs system packages if pip3 or ffmpeg are missing, but it does not clearly warn the user beforehand that it will perform network access and modify the base system. Silent or surprise system changes increase supply-chain and operational risk, especially when users may run the script with elevated privileges.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The script upgrades pip and installs Python packages directly from the network without an explicit warning, pinning, or integrity controls. This exposes users to avoidable supply-chain risk and may violate expectations in environments where external code downloads require review.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The installation test invokes Edge TTS functionality without clearly warning that the test may contact a remote service. Even though the sample text is benign, hidden outbound traffic during installation is risky in restricted or privacy-sensitive environments and reinforces the misleading 'offline' framing.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The installation summary tells users it restores local/helper voice capabilities while the configured TTS provider is a hosted service. In a voice-processing skill, that discrepancy is security-relevant because users may expose sensitive prompt or reply content to a third party without informed consent.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
print("\nTesting ffmpeg...")
    
    try:
        result = subprocess.run(["ffmpeg", "-version"], capture_output=True, text=True)
        if result.returncode == 0:
            # Extract version
            version_line = result.stdout.split('\n')[0]
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
print("⚠ Some tests failed. See above for details.")
        print("\nCommon issues:")
        print("1. Run install.sh to install dependencies")
        print("2. Ensure ffmpeg is installed: sudo apt-get install ffmpeg")
        print("3. Check Python packages: pip install faster-whisper piper-tts soundfile")
        return 1
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The function sends arbitrary input text to `edge_tts`, which relies on an external network service, creating a confidentiality risk if callers pass secrets, personal data, or internal content. There is no disclosure, consent flow, or guardrail in the code to prevent sensitive text from being exfiltrated to a third-party service.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
cache_path = _cache_key(text, voice, rate, pitch, volume)
    if cache_path.exists():
        if output_file:
            subprocess.run(['cp', str(cache_path), output_file], check=True)
            return output_file
        return str(cache_path)
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
tmp_out = Path(output_file) if output_file else Path(tempfile.mktemp(suffix='.mp3', prefix='edge_tts_'))
    asyncio.run(_synthesize(text, tmp_out, voice, rate, pitch, volume))
    tmp_out.parent.mkdir(parents=True, exist_ok=True)
    subprocess.run(['cp', str(tmp_out), str(cache_path)], check=True)
    if output_file and Path(output_file) != cache_path:
        return str(output_file)
    return str(cache_path)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Intent-Code Divergence

Medium
Confidence
80% confidence
Finding
The file-level documentation explicitly describes text-to-speech as "Edge TTS (local)". However, the code at L017 and L074 invokes an external wrapper script for Edge TTS rather than showing a clearly local synthesis implementation in this module, creating an intent/documentation mismatch about how TTS is actually performed.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if audio_file.endswith('.ogg'):
                wav_file = tempfile.mktemp(suffix=".wav")
                cmd = f"ffmpeg -i '{audio_file}' -ar 16000 -ac 1 '{wav_file}' -y 2>/dev/null"
                subprocess.run(cmd, shell=True, check=True)
                audio_file = wav_file
            
            # Transcribe with faster-whisper
Confidence
99% confidence
Finding
The ffmpeg command is built with string interpolation from the user-controlled audio_file path and executed with shell=True. An attacker can supply a filename containing shell metacharacters or unmatched quotes to break out of the quoting context and execute arbitrary commands as the current user.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
""" % (self.stt_model, audio_file)
            ]
            
            result = subprocess.run(cmd, capture_output=True, text=True)
            if result.returncode == 0:
                data = json.loads(result.stdout.strip())
                return data['text'].strip()
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
""" % (self.stt_model, audio_file)
            ]
            
            result = subprocess.run(cmd, capture_output=True, text=True)
            if result.returncode == 0:
                data = json.loads(result.stdout.strip())
                return data['text'].strip()
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
80% confidence
Finding
This code invokes a separate Edge TTS wrapper with user-provided text, but the surrounding docstring and comments do not disclose any privacy or network implications of that operation. For code files, safety-relevant external processing should have some visible disclosure unless clearly warned elsewhere or obviously inherent to the skill's stated purpose.

Missing User Warnings

Low
Confidence
91% confidence
Finding
This is a markdown file, so SQP-2 applies to missing warnings in the skill description. Line L136 states that faster-whisper automatically downloads from HuggingFace, but the document does not warn users that using the feature will initiate network access and transfer system/request data to an external service.

Missing User Warnings

Low
Confidence
82% confidence
Finding
The script creates directories, copies executables, and writes `config.json` into the installation path, all of which modify user files. While the script logs individual steps, it does not provide an upfront disclosure that running the installer will create and overwrite files under the chosen install directory.

Natural-Language Policy Violations

Low
Confidence
94% confidence
Finding
The code defaults to the hard-coded voice 'en-IE-ConnorNeural', which imposes a specific language and locale when no user choice is provided. This is a natural-language policy concern because the skill selects an English (Ireland) voice automatically rather than offering a neutral default or explicit opt-in.

Intent-Code Divergence

Low
Confidence
96% confidence
Finding
The file is documented and implemented as a test script for the 'edge_tts_voice_system', and earlier checks import and exercise 'edge_tts' and 'tts_edge_wrapper'. However, the remediation text tells users to install 'piper-tts', which contradicts the tested TTS stack and can mislead operators about the skill's actual dependencies.

Missing User Warnings

Low
Confidence
82% confidence
Finding
The script persistently stores generated audio in `/root/.openclaw/tts/cache`, which can retain sensitive spoken content beyond the user session. If the synthesized text contains secrets or private information, the cached MP3 files create a local data-retention and disclosure risk, especially in shared or long-lived environments.

Natural-Language Policy Violations

Low
Confidence
87% confidence
Finding
The default configuration hard-codes `en-IE-ConnorNeural`, which imposes a specific language and regional voice when the user does not explicitly choose one. This is a natural-language locale choice without any opt-in flow or documented reason for restricting the default to that locale.

Static analysis

No suspicious patterns detected.