Back to skill

Security audit

Denon AVR Control

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its Denon receiver and local-media purpose, but its DLNA modes can expose local music files on the LAN more broadly than the documentation clearly warns.

Review this skill before installing if your music folders contain private or unrelated files. Use only dedicated media folders, bind servers to a trusted LAN interface, stop the DLNA/HTTP server when finished, and be careful with raw receiver commands because they can change power, input, mute, and volume on the target device.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/dlna_push.py:125
Finding
Temporary DLNA Push Server Exposes the Selected File's Entire Parent Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/dlna_push.py:125-127` and `scripts/dlna_push.py:178-181` **Vulnerability Type**: Unauthenticated file and directory exposure **Risk Level**: Medium ### Vulnerable Code ```python def start_http_server(root_dir: Path, host: str, port: int): cmd = [sys.executable, '-m', 'http.server', str(port), '--bind', host, '--directory', str(root_dir)] proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) time.sleep(0.8) if proc.poll() is not None: raise RuntimeError('HTTP server exited immediately') return proc ``` ```python http_root = track.parent server = start_http_server(http_root, serve_ip, serve_port) file_url = f'http://{serve_ip}:{serve_port}/{urllib.request.pathname2url(track.name)}' metadata = didl_for(file_url, track) ``` ### Technical Analysis The single-track DLNA push workflow starts Python's generic `http.server` with the selected track's parent directory as its document root. The standard handler permits unauthenticated retrieval of every file below that directory and generates directory listings when a directory URL is requested. Consequently, the implementation exposes more data than the single selected audio file. It can also expose non-audio files stored beside the track. Binding to the automatically detected LAN interface makes the service reachable by other devices on the same network, subject to host firewall rules. The server stays active after the push operation so the receiver can fetch the media. It remains available until the user runs the `stop` command, the process terminates, or an external mechanism kills it. The documentation mentions temporary HTTP serving but does not clearly disclose that the entire parent directory is browsable. ### Attack Path 1. A user pushes an audio file using `dlna_push.py push`. 2. The script resolves the selected track and uses `track.parent` as the HTTP document root. 3. It launches an ...[truncated 835 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace `python -m http.server` with a dedicated HTTP handler that serves exactly one selected file. 2. Expose the file through a cryptographically random, unguessable URL path rather than its original filename. 3. Return `404` for directory paths and all resources other than the selected file. 4. Do not enable directory listing or recursive access to the parent directory. 5. Where practical, restrict requests to the receiver's source IP address. 6. Add an automatic expiration timer and terminate the server after playback or a short configurable timeout. 7. Ensure error and shutdown paths remove saved state and terminate the server. 8. Explicitly warn users that the selected media becomes reachable over the LAN for the duration of playback. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/dlna_push.py:221
Finding
Unverified Persistent PID State Can Terminate Unrelated Processes<![CDATA[ ## Vulnerability Details **File Location**: `scripts/dlna_push.py:221-228`, `scripts/simple_dlna_server.py:442-451`, and `scripts/local_audio_jukebox.py:45-61` **Vulnerability Type**: Unsafe process lifecycle management using stale PID files **Risk Level**: Low ### Vulnerable Code `scripts/dlna_push.py:221-228`: ```python pid = st.get('httpPid') killed = False if pid and is_running(pid): try: os.kill(pid, signal.SIGTERM) killed = True except Exception: pass ``` `scripts/simple_dlna_server.py:442-451`: ```python pid = st.get('pid') if not pid: print(json.dumps({'stopped': False, 'reason': 'no recorded pid'}, ensure_ascii=False, indent=2)) return 0 try: os.kill(pid, signal.SIGTERM) print(json.dumps({'stopped': True, 'pid': pid}, ensure_ascii=False, indent=2)) except ProcessLookupError: print(json.dumps({'stopped': False, 'reason': 'process not found', 'pid': pid}, ensure_ascii=False, indent=2)) return 0 ``` `scripts/local_audio_jukebox.py:45-61`: ```python def stop_current(): state = load_state() pid = state.get('pid') if not pid: print('No active player recorded.') return 0 if not is_running(pid): print('Recorded player is not running.') return 0 try: os.kill(pid, signal.SIGTERM) print(f'Stopped player pid {pid}.') return 0 except Exception as e: print(f'error: {e}', file=sys.stderr) return 1 ``` ### Technical Analysis The scripts persist numeric process identifiers under `~/.openclaw/state` and later use those identifiers to send `SIGTERM`. The `is_running` check only establishes that some process currently uses the PID; it does not prove that the process is the HTTP server, DLNA server, or audio player originally recorded. Operating systems reuse process identifiers. If a managed process exits without clearing its state file, its PID can later be assigned to an unrelated process. A subsequent ` ...[truncated 1403 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove or invalidate PID state atomically during normal shutdown and all handled error paths. 2. Store process identity data in addition to the PID, such as executable path, expected command line, and process start time. 3. Before sending a signal, verify that the current process matches every stored identity attribute. 4. On Linux, prefer pidfds where available to avoid PID-reuse races. 5. For long-running servers, use an authenticated local control socket or another direct shutdown channel instead of a reusable numeric PID. 6. Create state files with restrictive permissions and reject state files not owned by the current user. 7. After a successful stop, delete the state file rather than leaving a terminated PID recorded. 8. If identity verification fails, refuse to signal the process and instruct the user to inspect it manually. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (16)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The implemented behavior substantially matches the receiver-control portion of the description: it connects to a specified host over TCP or HTTP, formats Denon commands, and supports power, mute, volume, input selection, status queries, sound mode changes, and raw commands. However, the description also claims the skill can expose local audio libraries through DLNA/UPnP for receiver playback. There is no code for filesystem access, media indexing, DLNA/UPnP advertisement, HTTP media serving beyond the Denon goform request, or any HEOS library-serving functionality. So the description overstates a significant capability that is absent from the code, making this a description-behavior mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The DLNA-serving portion of the description is substantially supported: the script scans audio files, selects one, serves it over a local HTTP server, and instructs a Denon-compatible renderer to play it via UPnP SOAP AVTransport actions. However, the broader declared purpose emphasizes controlling a Denon AVR/AVC over its classic IP control interface or goform endpoint, including power, volume, mute, input changes, status, and raw Denon commands. None of those receiver-control capabilities appear in this code chunk. Instead, the script performs a narrower and materially different function: DLNA/UPnP media push/playback orchestration. Because major declared control capabilities are absent and the actual control method is different from the declared interface, this is a description-behavior mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The declared description is centered on network control of a Denon receiver and exposing music libraries over DLNA/UPnP. The supplied code does none of that: it does not open network connections, send Denon control commands, query receiver status over IP, or implement any media server functionality. Instead, it is a local command-line jukebox that scans directories, selects tracks, plays them on the local machine through external media players, and tracks/stops the local playback process. That is a materially different primary purpose and includes unrelated local playback/process-management behavior not represented by the description.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
This is a material description/behavior mismatch. While the declared description includes two major functions—(1) controlling a Denon receiver over its classic IP interfaces and (2) exposing local audio libraries over DLNA/UPnP—the provided code only covers the second function. The code scans local directories for audio files, hosts them via an HTTP server, publishes UPnP device and service descriptions, handles SOAP Browse and related actions, and answers SSDP discovery requests. There is no code for opening outbound TCP connections to a Denon receiver, no telnet-style command formatting, no goform HTTP client behavior, and no receiver control/status logic. So the implementation represents only the DLNA-serving subset of the declaration, omitting the primary Denon control capabilities explicitly advertised.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill advertises and instructs use of capabilities that imply shell execution, network access, and local file interaction, but it declares no explicit tool scope or permissions boundary. In an agent environment, that omission can cause overbroad execution authority and make it harder to enforce least privilege, especially because the skill includes network control of devices and serving local media directories.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The documentation instructs users to start a temporary HTTP server that exposes a local audio file on the LAN, but it does not clearly warn that any device with network access to that port may be able to fetch the file while the server is running. In a home or shared network environment, this can unintentionally disclose private media files or metadata, especially if users broaden the bind address or open firewall access without understanding the exposure.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The instructions tell the operator to start a DLNA/HTTP media server and mention firewall access, but they do not explicitly warn that doing so makes the selected music library browsable over the local network and may expose filenames, metadata, and media contents to other devices on the LAN. In the context of a home receiver control skill, this is relevant because users may assume playback setup is local-only, while the feature actually publishes a network-visible service discoverable via SSDP.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def start_http_server(root_dir: Path, host: str, port: int):
    cmd = [sys.executable, '-m', 'http.server', str(port), '--bind', host, '--directory', str(root_dir)]
    proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    time.sleep(0.8)
    if proc.poll() is not None:
        raise RuntimeError('HTTP server exited immediately')
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The push flow starts an HTTP server bound to a user-supplied or auto-detected LAN address and serves the selected track's parent directory so the receiver can fetch the file. This exposes local media on the network without authentication, encryption, or a prominent warning, so other hosts on the same network may browse or retrieve files from that directory while the server is running.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
The script persists a PID in a user-writable state file and later trusts that PID in stop_current() to send SIGTERM, without verifying that the process is actually the jukebox's child or expected media player. An attacker or other local process that can modify ~/.openclaw/state/local-audio-jukebox.json could cause the script to terminate an arbitrary process owned by the same user, creating an unintended local denial-of-service capability.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
cmd = ['afplay', str(track)]
        else:
            cmd = ['ffplay', '-nodisp', '-autoexit', '-loglevel', 'error', str(track)]
        proc = subprocess.Popen(cmd)
        rc = proc.wait()
        if rc != 0:
            print(f'warning: player exited with {rc} for {track}', file=sys.stderr)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Tainted flow: 'addr' from socket.socket.recvfrom (line 362, network input) → socket.socket.sendto (network output)

Medium
Category
Data Flow
Content
f'ST: {st}\r\n'
                        f'USN: {self.udn}::{st}\r\n\r\n'
                    )
                    sock.sendto(resp.encode('utf-8'), addr)
        sock.close()

    def stop(self):
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The serve command scans user-selected roots and exposes matching audio files over HTTP/DLNA on the network, while advertising the service via SSDP to the local subnet. In this skill context that behavior is intentional, but it still creates a real confidentiality risk because local media becomes discoverable and retrievable by other devices on the LAN without authentication or an explicit runtime warning.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The stop command trusts a persisted PID from a state file under the user's home directory and sends SIGTERM to whatever process currently owns that PID. If the state file is modified, corrupted, or stale after PID reuse, the script can terminate an unrelated local process, expanding its authority beyond managing its own DLNA server.

Missing User Warnings

Low
Confidence
82% confidence
Finding
This markdown file includes a practical workflow for sending power, volume, input, and sound-mode commands that change the state of a receiver. While it advises querying first and sending one mutating command at a time, it does not warn users that these actions can alter playback state, switch inputs, or change volume on a live device.

Missing User Warnings

Low
Confidence
83% confidence
Finding
The script persistently stores local file paths, file URLs, receiver host information, and process metadata in a state file under the user's home directory without disclosure or permission tightening. While this is not remote code execution, it can leak sensitive information about the user's media library structure and network environment to other local users or software that can read the file.

Static analysis

No suspicious patterns detected.