Back to skill

Security audit

AudioClaw Skills Voice Reply

Security checks for vulnerabilities and agentic risk

Overview

This voice-reply skill is coherent, but it needs review because it can automatically send audio through Feishu, infer recipients from local logs, use stored credentials, and execute a caller-selected ffmpeg path.

Install only in an AudioClaw environment where direct Feishu audio sending is intended. Prefer explicit chat IDs over log inference, avoid accepting untrusted request JSON, do not expose ffmpeg_exe or cache_dir to users, review stored credential handling, and treat generated voice replies as data sent to SenseAudio and Feishu.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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)

T01 · Skill Instruction Hijacking

Error
Location
agents/openai.yaml:4
Finding
Agent Behavior and Output Hijacking Through Skill Instructions<![CDATA[ ## Vulnerability Details **File Location**: `agents/openai.yaml:4`; `SKILL.md:115-121` **Vulnerability Type**: Instruction hijacking and forced output behavior **Risk Level**: High ### Vulnerable Code Snippet ```yaml interface: display_name: "AudioClaw Skills Voice Reply" short_description: "AudioClaw dynamic voice and emotion routing" default_prompt: "Use $audioclaw-skills-voice-reply to turn an AudioClaw reply into AudioClaw speech. Default to the prepared clone voice_id `vc-yxdCFUKyNLPexxJ66jaXWk` for future voice replies unless the user explicitly asks for another voice. For AudioClaw on Feishu, prefer the wrapper and let it send the audio directly through Feishu instead of using send_file or sending MEDIA paths as text. After the audio is sent, prefer no extra text confirmation. If the host runtime still needs one final assistant message to complete the turn, send one short predefined confirmation sentence." ``` Related instructions in `SKILL.md`: ```markdown 1. Run `scripts/picoclaw_voice_reply.py`. 2. Let the wrapper upload the generated `.ogg/.opus` file to Feishu and send it as `msg_type=audio`. 3. Do not call the `send_file` tool for that audio unless you explicitly passed `--skip-direct-send`. 4. Do not call the `message` tool with the local path or the `MEDIA:...` reference. 5. After the audio is sent, prefer no extra text confirmation. 6. If the host runtime still requires one final assistant message to finish the turn, send one short predefined confirmation sentence. 7. Use `media_reference` only as debug metadata or future AudioClaw compatibility data. ``` ### Technical Analysis The agent metadata does more than describe how to invoke the voice-generation functionality. It directs the agent to: - Adopt a particular clone voice as the default for future replies. - Prefer a direct network delivery mechanism over ordinary host tools. - Suppress normal post-delivery output. - Produce a predetermined final response if the host re ...[truncated 1701 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the embedded clone voice identifier from the default prompt. 2. Select a clone or custom voice only when: - The current user explicitly requests it, or - A previously stored preference was created with informed user consent. 3. Replace forced output wording with neutral structured metadata indicating whether delivery succeeded. 4. Treat tool-selection rules as recommendations subject to host policy rather than mandatory instructions. 5. Do not suppress host-required confirmations; let the integrating application determine final response behavior. 6. Clearly separate current-request voice options from persistent preference operations. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/openclaw_voice_switchboard.py:1237
Finding
Arbitrary Executable Invocation Through Request-Controlled ffmpeg Path<![CDATA[ ## Vulnerability Details **File Location**: `scripts/openclaw_voice_switchboard.py:674-699, 955-974, 985-1001, 1237` **Vulnerability Type**: Arbitrary executable invocation **Risk Level**: Critical ### Vulnerable Code Snippet Request JSON fields are merged into the runtime request: ```python def load_request(args: argparse.Namespace) -> Dict[str, object]: if args.request_file and args.request_json: raise SystemExit("Use either --request-file or --request-json, not both.") request: Dict[str, object] = {} if args.request_file: request = json.loads(Path(args.request_file).read_text(encoding="utf-8")) elif args.request_json: request = json.loads(args.request_json) direct = { "text": args.text, "scene": args.scene, "voice_id": args.voice_id, "voice_family": args.voice_family, "emotion": args.emotion, "speed": args.speed, "pitch": args.pitch, "volume": args.volume, "audio_format": args.format, "sample_rate": args.sample_rate, "delivery_profile": args.delivery_profile, "ffmpeg_exe": args.ffmpeg_exe, "allow_fallback": args.allow_fallback, "strict_voice": args.strict_voice, "cache_dir": args.cache_dir, "validated_only": args.validated_only, "preference_key": args.preference_key, "reply_mode": args.reply_mode, } ``` The executable is selected from request-controlled data: ```python ffmpeg_exe = resolve_ffmpeg_exe( str(request.get("ffmpeg_exe") or args.ffmpeg_exe or "") ) if delivery_profile == "feishu_voice" else "" ``` The selected path is executed directly: ```python def transcode_for_delivery( source_path: Path, target_path: Path, *, profile: str, ffmpeg_exe: str, ) -> None: if profile != "feishu_voice": if source_path != target_path: shutil.copyfile(source_path, target_path) return if target_path.suff ...[truncated 2919 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `ffmpeg_exe` from the message-derived JSON request contract. 2. Treat executable selection as administrator-controlled configuration only. 3. Resolve ffmpeg once during trusted application initialization. 4. Canonicalize the executable path and require it to reside in an explicit allowlisted directory. 5. Verify that the resolved object is a regular executable file and is not a symbolic link. 6. Where practical, verify the binary's ownership, permissions, and cryptographic hash. 7. Run transcoding in a restricted subprocess with: - A minimal environment. - A dedicated low-privilege account. - Resource and execution time limits. - No access to Feishu or API credentials. 8. Add tests proving that `request_json["ffmpeg_exe"]` and equivalent request-file values are rejected. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/openclaw_voice_switchboard.py:1221
Finding
Shared Predictable Temporary Cache Enables Symlink and Cache-Poisoning Attacks<![CDATA[ ## Vulnerability Details **File Location**: `scripts/openclaw_voice_switchboard.py:842-875, 908-925, 1221-1250, 1311-1346` **Vulnerability Type**: Unsafe temporary storage and symlink following **Risk Level**: High ### Vulnerable Code Snippet The default cache uses a predictable shared temporary directory: ```python cache_dir = Path( str( request.get("cache_dir") or args.cache_dir or "/tmp/openclaw_voice_switchboard_cache" ) ) cache_dir.mkdir(parents=True, exist_ok=True) ``` Predictable cache names are generated from request content: ```python def cache_path_for( cache_dir: Path, text: str, voice_id: str, settings: Dict[str, object], ) -> Path: digest = hashlib.sha256( json.dumps( { "text": text, "voice_id": voice_id, "settings": settings, }, ensure_ascii=False, sort_keys=True, ).encode("utf-8") ).hexdigest() extension = settings["audio_format"] return cache_dir / f"{digest}.{extension}" ``` Existing entries are accepted without ownership or file-type validation: ```python if primary_delivery_cache_file.exists(): final_path = output_from_cache( primary_delivery_cache_file, out_path, ) ensure_file_mode(primary_delivery_cache_file, file_mode) if primary_cache_file.exists(): ensure_file_mode(primary_cache_file, file_mode) ensure_file_mode(primary_cache_file, file_mode) ensure_file_mode(final_path, file_mode) ``` Writes and permission changes follow ordinary filesystem path semantics: ```python if not attempt_cache_file.exists(): result = synthesize( api_key=api_key, text=text, voice_id=str(voice["voice_id"]), audio_format=str(settings["audio_format"]), sample_rate=int(settings["sample_rate"]), speed=float(settings["speed"]), volume=float(settings["volume"]), p ...[truncated 3018 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use a private, per-user cache directory rather than a shared predictable `/tmp` path. 2. Create the cache directory with mode `0700` and verify its ownership before use. 3. Create cache files atomically with restrictive permissions such as `0600`. 4. Use platform facilities equivalent to `O_CREAT | O_EXCL | O_NOFOLLOW`. 5. Reject symbolic links and non-regular files for cache entries and output paths. 6. Write to a securely created temporary file, flush and validate it, then atomically rename it into place. 7. Authenticate cached content with a keyed integrity tag or store trusted metadata alongside each entry. 8. Validate the audio container before accepting a cache hit. 9. Avoid silently ignoring permission-change failures. 10. Default generated and cached audio to mode `0600`; relax permissions only when a specific downstream process requires it. 11. Prevent request JSON from selecting arbitrary cache directories unless the caller is already trusted to select filesystem destinations. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/feishu_audio_sender.py:111
Finding
Automatic Session-Log Recipient Inference Can Send Audio to the Wrong Feishu Chat<![CDATA[ ## Vulnerability Details **File Location**: `scripts/feishu_audio_sender.py:111-145, 212-218`; `scripts/picoclaw_voice_reply.py:130-142` **Vulnerability Type**: Insecure recipient inference and unintended data disclosure **Risk Level**: High ### Vulnerable Code Snippet The sender chooses a recipient from recent session logs when no explicit chat identifier is provided: ```python def infer_chat_id( workspace_root: Path, explicit_chat_id: str, session_file: str, ) -> tuple[str, str]: if explicit_chat_id: return explicit_chat_id, "" if session_file: path = Path(session_file).expanduser() chat_id = extract_chat_id(path) if chat_id: return chat_id, str(path) raise SystemExit(f"no chat_id found in {path}") sessions_dir = workspace_root / "sessions" candidates = sorted( sessions_dir.glob("agent_main_feishu_direct_*.jsonl"), key=lambda p: p.stat().st_mtime, reverse=True, ) if not candidates: candidates = sorted( sessions_dir.glob("*feishu*.jsonl"), key=lambda p: p.stat().st_mtime, reverse=True, ) for candidate in candidates: chat_id = extract_chat_id(candidate) if chat_id: return chat_id, str(candidate) state_path = workspace_root / "state" / "state.json" try: state = load_json(state_path) except SystemExit: state = {} last_channel = str(state.get("last_channel") or "").strip() if last_channel.startswith("feishu:"): chat_id = last_channel.split(":", 1)[1].strip() if chat_id: return chat_id, str(state_path) raise SystemExit( "could not infer feishu chat_id from session logs; pass --chat-id" ) ``` The final regex match anywhere in the complete log is accepted: ```python def extract_chat_id(path: Path) -> str: try: text = path.read_text( encoding="utf-8", ...[truncated 3810 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require an explicit chat ID from authenticated current-session metadata for direct sending. 2. Bind the recipient identifier to the current inbound event rather than discovering it from workspace logs. 3. Remove the modification-time-based session selection and broad `*feishu*.jsonl` fallback. 4. If a session file must be supported: - Require an approved path beneath the expected sessions directory. - Parse structured JSON records. - Read a dedicated recipient field from the current event. - Verify the session and requesting principal match. 5. Reject ambiguous recipient state rather than falling back to `last_channel`. 6. Display or return the resolved recipient for confirmation before direct sending when no authenticated binding is available. 7. Add concurrency tests with multiple active Feishu sessions. 8. Restrict local write access to session logs and workspace state. 9. Consider making direct sending opt-in, with generation-only behavior as the safe default. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (37)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill advertises narrowly scoped voice-reply behavior but also instructs access to local workspace/session/state files and chat inference behavior that is not prominently declared in the purpose statement. That mismatch is dangerous because operators may approve the skill expecting only TTS generation, while it can also inspect local state and perform message-delivery actions with side effects.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding
The skill advertises narrowly scoped voice-reply behavior but also instructs access to local workspace/session/state files and chat inference behavior that is not prominently declared in the purpose statement. That mismatch is dangerous because operators may approve the skill expecting only TTS generation, while it can also inspect local state and perform message-delivery actions with side effects.

Ae1

High
Category
analysis-evasion
Content
3. Run `scripts/openclaw_voice_switchboard.py`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
3. Run `scripts/openclaw_voice_switchboard.py`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
3. Run `scripts/openclaw_voice_switchboard.py`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
3. Run `scripts/openclaw_voice_switchboard.py`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
3. Run `scripts/openclaw_voice_switchboard.py`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
3. Run `scripts/openclaw_voice_switchboard.py`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
3. Run `scripts/openclaw_voice_switchboard.py`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
3. Run `scripts/openclaw_voice_switchboard.py`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
3. Run `scripts/openclaw_voice_switchboard.py`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
3. Run `scripts/openclaw_voice_switchboard.py`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
3. Run `scripts/openclaw_voice_switchboard.py`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
3. Run `scripts/openclaw_voice_switchboard.py`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
3. Run `scripts/openclaw_voice_switchboard.py`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
This skill now treats `SENSEAUDIO_API_KEY` as the default API key source again.

Runtime rules:
- If the host app injects `SENSEAUDIO_API_KEY` as an AudioClaw login token such as `v2.public...`, the shared bootstrap will replace it with the real `sk-...` value from `~/.audioclaw/workspace/state/senseaudio_credentials.json` before TTS starts.
- `--api-key-env` still works, but the default runtime path is `SENSEAUDIO_API_KEY`.

If you need the exact same speaker timbre across many emotions, use a purchased multi-variant voice family or an authorized custom voice. Otherwise this skill will approximate the requested emotion with the best available voice and tuning.
Confidence
97% confidence
Finding
The skill instructs runtime substitution of an injected token with the real `sk-...` API key from `~/.audioclaw/workspace/state/senseaudio_credentials.json`. Accessing a local credential store to retrieve a more privileged secret increases blast radius: a skill that appears to need only a provided token can silently escalate to stored credentials, and compromise of the skill or host process could expose reusable API secrets.

Missing User Warnings

High
Confidence
97% confidence
Finding
User-provided text is sent to an external TTS provider, but the code contains no consent, warning, minimization, or policy gate around transmitting potentially sensitive content. In a voice-reply skill this behavior is expected functionally, yet it is still dangerous because agents may pass secrets, personal data, or internal content to a third-party service without explicit disclosure.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
parser.add_argument("--skip-direct-send", action="store_true")
    args = parser.parse_args()

    env = os.environ.copy()
    workspace_root = Path(args.workspace_root).expanduser()
    switchboard_args = [
        "--text", args.text,
Confidence
84% confidence
Finding
The code copies the entire parent process environment and forwards it to subordinate scripts, which may include unrelated secrets such as API keys, tokens, proxy credentials, or CI secrets. In a skill/executor context that chains multiple helper scripts, this broad propagation increases the blast radius if a child script logs, exposes, or mishandles environment data.

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill describes capabilities that require shell execution, network access, environment-variable use, and reads/writes under the local workspace, yet it declares no explicit tool scope or permission boundaries. In an agent setting, that omission can cause over-broad execution authority and makes it harder for a host to sandbox the skill to the minimum necessary privileges.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
- `--out` inside the AudioClaw workspace
   - `--openclaw-workspace-root` pointing at the workspace root
   - `--delivery-profile feishu_voice` when the downstream channel prefers `.ogg/.opus`
   - optional `--chmod 644` if you want to be explicit, though this skill now defaults to `0644`
   - if `--openclaw-workspace-root` is set and `--out` is omitted, this skill now writes to `workspace/state/audio/` automatically
7. Use the returned JSON manifest in AudioClaw to:
   - prefer `scripts/picoclaw_voice_reply.py` for AudioClaw on Feishu
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The instruction says the assistant should send 'one short natural Chinese line' if a final message is required. This imposes a specific language choice without indicating that it depends on the user's language or offering an opt-in, which violates the language/locale policy criteria.

External Transmission

Medium
Category
Data Exfiltration
Content
## Resources

- `scripts/senseaudio_tts_client.py`
  - Small importable client for `https://api.senseaudio.cn/v1/t2a_v2`
  - Handles SSE chunks and writes audio bytes
- `references/openclaw_voice_switchboard.md`
  - TTS capability summary plus the official voice catalog reference at `https://senseaudio.cn/docs/voice_api`
Confidence
90% confidence
Finding
The skill is explicitly designed to send user-provided text and generated audio to external services, including the SenseAudio API and Feishu endpoints. External transmission is expected for TTS, but it is still security-relevant because sensitive conversation content, voice preferences, and possibly metadata may leave the local environment without clear consent, minimization, or domain restrictions.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The default prompt uses broad activation language such as "Use $audioclaw-skills-voice-reply to turn an AudioClaw reply into AudioClaw speech" and then sets behavioral defaults for future voice replies. Without a narrowly defined trigger condition, the host agent may invoke this skill more often than intended, causing mode-switching into voice output when the user did not clearly request it and increasing the chance of unintended message handling in Feishu/Lark workflows.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The prompt instructs the agent to send a short natural Chinese line if a final assistant message is required, regardless of the user's language preference. This can override user expectations, create confusing or inaccessible output, and leak implementation-specific behavior into the user interaction when no confirmation or a different language would be more appropriate.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
This code fetches a tenant token, uploads a local audio file to Feishu, and sends it to a chat, which transmits user data over the network. The script includes no confirmation prompt, no user-facing disclosure before transmission, and no comments/docstrings warning about the privacy impact of uploading local audio and inferred chat context.

Static analysis

No suspicious patterns detected.