Back to skill

Security audit

Play Local Music

Security checks for vulnerabilities and agentic risk

Overview

The skill is a local music player, but its background server and lock-file handling create avoidable local security and reliability risks.

Review this skill before installing. It does not show exfiltration or deceptive behavior, but it runs a local background server, opens a localhost control port, writes a world-writable lock file under `/tmp` by default, and accepts local playback commands without authentication. Use it only in a trusted local-user environment, avoid running it with elevated privileges, and prefer a hardened version with a private per-user runtime directory, restrictive lock-file permissions, path validation for songs, authenticated control requests, and safer server-stop instructions.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (4)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
music-server.py:17
Finding
Predictable lock file allows symlink-based file truncation and permission modification## Vulnerability Details **File Location**: `music-server.py`, lines 17 and 28-36 **Vulnerability Type**: Insecure temporary file handling and symbolic-link following **Risk Level**: High ### Vulnerable Code ```python LOCK_FILE = Path(os.environ.get("MUSIC_LOCK_FILE", "/tmp/music_player.lock")) def save_lock_file(): """Save server port to lock file""" try: with open(LOCK_FILE, 'w') as f: f.write(str(CONTROL_PORT)) os.chmod(LOCK_FILE, 0o666) # Make it readable by all except Exception as e: print(f"Error saving lock file: {e}", file=sys.stderr) ``` ### Technical Analysis The default lock file uses the predictable path `/tmp/music_player.lock`, located in a shared temporary directory. The file is opened in write mode without preventing symbolic-link traversal, checking ownership, or atomically ensuring that a new regular file is being created. If the path is a symbolic link, `open(..., 'w')` follows the link and truncates the target before writing the port number. The subsequent `os.chmod()` also follows the link under normal platform behavior and attempts to change the target permissions to `0666`, making it writable by every local user. Exploitability depends on operating-system protections for shared temporary directories, such as Linux protected-symlink settings, and on the permissions and identity under which the server runs. These mitigations should not be relied upon as the application's security boundary. ### Attack Path 1. A local attacker identifies that the server will use `/tmp/music_player.lock`. 2. Before the server starts, the attacker places a symbolic link at that path pointing to a file writable by the account that will run the server. 3. A victim or privileged service account starts `music-server.py`. 4. `open(LOCK_FILE, 'w')` follows the symbolic link, truncates the target, and writes `12346`. 5. `os.chmod(LOCK_FILE, 0o666)` attempts to ma ...[truncated 757 chars]
Remediation
## Remediation Suggestions - Store runtime state in a private per-user runtime directory, such as `$XDG_RUNTIME_DIR`, with directory permissions of `0700`. - Create the file atomically using `os.open()` with `O_CREAT`, `O_EXCL`, and, where supported, `O_NOFOLLOW`. - Create the lock file with mode `0600`; do not make it world-writable. - Before using an existing path, use `lstat()` and reject symbolic links and non-regular files. - Verify that the file is owned by the expected user. - Prefer an advisory lock on an already securely opened descriptor rather than treating a predictable pathname as authoritative. - Avoid running the music server with elevated privileges.

T09 · Insecure Skill Coding Practices

Warning
Location
music-server.py:53
Finding
Unvalidated song paths allow access outside the configured music directory## Vulnerability Details **File Location**: `music-server.py`, lines 53-76 **Vulnerability Type**: Path traversal and absolute-path injection **Risk Level**: Medium ### Vulnerable Code ```python def play_music(song_name): """Play a music file by name""" global current_song, is_playing, is_paused, position # Build full path song_path = MUSIC_DIR / song_name if not song_path.exists(): # Try with .mp3 extension if not provided if not song_name.endswith('.mp3'): song_path = MUSIC_DIR / f"{song_name}.mp3" if not song_path.exists(): print(f"File not found: {song_name} (looked in {MUSIC_DIR})", file=sys.stderr) return False print(f"Attempting to play: {song_path}", file=sys.stderr) try: pygame = initialize_pygame() pygame.mixer.music.load(str(song_path)) ``` ### Technical Analysis `song_name` originates in an unauthenticated JSON request and is joined directly to `MUSIC_DIR`. The code does not reject absolute paths, `..` components, symbolic links, unsupported extensions, or non-regular files. In Python's `pathlib`, joining a base path with an absolute second path discards the base path. Relative values containing parent-directory components can similarly resolve outside the intended directory. The existence check does not establish that the final path remains beneath `MUSIC_DIR`. The resulting attacker-selected path is passed to Pygame's media loader. This permits filesystem probing and causes arbitrary accessible files to be opened and parsed as media. It does not directly return file contents, but success or failure can act as an information oracle, while malformed media can expose vulnerabilities in native decoding dependencies. ### Attack Path 1. A local process connects to `127.0.0.1:12346`. 2. It submits a play request containing traversal components, for example: ...[truncated 1082 chars]
Remediation
## Remediation Suggestions - Reject absolute paths and any input containing `..` path components. - Resolve the configured root and candidate path with `Path.resolve()`, then verify containment using `candidate.relative_to(root)`. - Permit only explicitly supported filename extensions. - Require the resolved target to be a regular file. - Decide whether symbolic links are allowed; if not, reject them explicitly and protect against time-of-check/time-of-use races. - Prefer exposing a server-generated list of opaque track identifiers rather than accepting caller-provided filesystem paths. - Run the service with access only to the intended music directory.

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
music-server.py:141
Finding
Unauthenticated synchronous control socket permits unauthorized control and denial of service## Vulnerability Details **File Location**: `music-server.py`, lines 141-191 and 213-237 **Vulnerability Type**: Missing authentication and socket resource exhaustion **Risk Level**: Medium ### Vulnerable Code ```python def handle_client(client_socket): """Handle a client connection""" global server_running try: # Receive command data = client_socket.recv(1024).decode() if not data: return command = json.loads(data) cmd = command.get("command") response = {"status": "error", "error": "Unknown command"} if cmd == "play": song_name = command.get("song") if song_name: if play_music(song_name): response = {"status": "ok", "message": f"Playing {song_name}"} else: response = {"status": "error", "error": f"Failed to play {song_name}"} # Other playback commands omitted here do not perform authentication. elif cmd == "shutdown": response = {"status": "ok", "message": "Server shutting down"} server_running = False def main(): """Main server function""" global server_running # Create socket server server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: server_socket.bind(('127.0.0.1', CONTROL_PORT)) server_socket.listen(5) # Main server loop while server_running: try: # Set timeout to allow checking server_running flag server_socket.settimeout(1.0) client_socket, addr = server_socket.accept() print(f"Client connected: {addr}", file=sys.stderr) handle_client(client_socket) ``` ### Technical ...[truncated 1842 chars]
Remediation
## Remediation Suggestions - Authenticate every request with a cryptographically random, per-user secret stored in a file accessible only to that user. - On Unix-like systems, prefer a Unix-domain socket in a private runtime directory and enforce restrictive filesystem permissions. - Set a short receive timeout on each accepted client socket. - Define explicit request framing and maximum message sizes rather than relying on one `recv(1024)` call. - Handle clients using a bounded worker pool or another design in which one stalled client cannot block the accept loop. - Apply rate limits and close idle connections. - Restrict or remove the unauthenticated `shutdown` operation. - Keep the loopback binding; authentication should supplement rather than replace network exposure restrictions.

T08 · Insecure Dependencies

Note
Location
SETUP.md:11
Finding
Installation instructions use an unpinned third-party dependency## Vulnerability Details **File Location**: `SETUP.md`, lines 11-15; also `README.md`, lines 7-10 and 81-87; `SKILL.md`, lines 51-56 **Vulnerability Type**: Unpinned dependency and non-reproducible installation **Risk Level**: Low ### Vulnerable Code ```markdown 2. **Install dependencies** ```bash pip install pygame ``` ``` Equivalent unpinned `pip install pygame` instructions also appear in `README.md` and `SKILL.md`. ### Technical Analysis The setup instructions install Pygame without a version constraint, integrity hash, lock file, or explicit trusted package index. Consequently, separate installations can retrieve different artifacts over time. The dependency name is consistent and there is no evidence of typosquatting, dependency confusion, or a currently malicious package in the audited project. The security concern is that the installation is mutable and does not verify the exact reviewed artifact. A future compromised release, package-index compromise, or incompatible update could therefore affect users who follow the instructions. ### Attack Path 1. A user follows the documented setup command. 2. Pip queries its configured package index and resolves whichever Pygame release currently satisfies the unconstrained request. 3. The selected distribution is downloaded and installed without comparison against a project-supplied hash or lock file. 4. If the selected release or configured package source is compromised, malicious installation or runtime code may execute with the user's privileges. This is a supply-chain exposure rather than evidence that the current dependency is malicious. ### Impact Assessment A compromised dependency artifact could execute with the privileges of the user performing installation or running the server. Potential scope includes that user's files, environment, and accessible credentials. The likelihood is reduced by using the established `pygame` package name rathe ...[truncated 139 chars]
Remediation
## Remediation Suggestions - Pin Pygame to a reviewed, compatible version. - Use a requirements or lock file containing cryptographic hashes, such as pip's `--require-hashes` workflow. - Document installation inside an isolated virtual environment. - Explicitly use a trusted package index and secure transport. - Establish a dependency-update process that reviews release notes, vulnerabilities, and artifact hashes before changing the pin. - Correct the metadata inconsistency: `_meta.json` describes Pygame as optional, while `music-server.py` imports it unconditionally.
Vulnerability Patterns
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (5)

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The troubleshooting guidance recommends `pkill -f "music-server.py"` without cautioning that pattern-based process termination can kill unintended processes whose command lines match the string. This is dangerous because users may terminate unrelated workloads or services, causing denial of service or data loss if those processes are doing active work.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
Creating the lock file and then setting it to mode `0o666` makes it world-writable, allowing other local users or processes to alter or replace its contents. Because the file is used to advertise server state and port information, tampering can mislead clients, interfere with coordination, or combine with symlink/path manipulation to affect other files.

Tainted flow: 'LOCK_FILE' from os.environ.get (line 18, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
def save_lock_file():
    """Save server port to lock file"""
    try:
        with open(LOCK_FILE, 'w') as f:
            f.write(str(CONTROL_PORT))
        os.chmod(LOCK_FILE, 0o666)  # Make it readable by all
    except Exception as e:
Confidence
91% confidence
Finding
The lock file path is taken from an environment variable and written without validation, which can cause the process to overwrite an unintended file if the environment is attacker-controlled. This is made more dangerous by subsequently changing permissions on that path, potentially broadening access to a sensitive file or a symlink target.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The `stop` command performs two actions: it stops playback and also sets `server_running = False`, shutting down the service. Any local client able to connect to the control port can terminate the server unexpectedly, creating a denial-of-service condition and violating the principle of least surprise for command behavior.

Missing User Warnings

Low
Confidence
92% confidence
Finding
The README documents auto-starting and stopping a background music server, but it does not clearly warn users that the skill creates a persistent background process and listens on a local port. This can lead to unexpected exposure, confusion during troubleshooting, and accidental conflicts with other local services, especially for users who assume the tool is a simple foreground music player.

Static analysis

No suspicious patterns detected.