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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

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

Medium
Category
Data Flow
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.

Content

Scanner excerpt · music-server.py (reported line 35)May include surrounding context.

python
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:

Intent-Code Divergence

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.