Back to skill

Security audit

Internet Radio Music Player

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent internet radio player, but it needs review because it opens external stream URLs in Foobar2000 and exports playback history in unsafe formats.

Install only if you are comfortable with the skill making outbound stream checks, controlling Foobar2000, force-stopping Foobar2000 processes, and saving local listening history. Avoid opening exported HTML or CSV history files from untrusted stream metadata unless they are sanitized first, and prefer explicit radio-specific commands to reduce accidental activation.

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/show_history.py:260
Finding

Stored HTML Injection in Playback History Export

Content
View full analysis
{i}{t}{g}{d}{nm}{u}{no}".format( i=i, t=h["Time"], g=h["Genre"], d=dur, nm=h["Name"], u=url, no=note ) ) ``` ### Technical Analysis The HTML export function inserts playback-history values directly into an HTML document without HTML escaping. Dynamic fields include the stream name, genre, URL, note, time, and duration. Some of these values originate from the companion stream database and are persisted in `state.json` when a stream is played. If an attacker can introduce malicious stream metadata, a value such as: ```html ``` would be written into the generated HTML as active markup rather than text. Opening the exported document in a browser could therefore execute attacker-controlled JavaScript. This is a stored injection vulnerability because the malicious metadata is first saved in playback history and subsequently rendered by the export function. ### Attack Path 1. An attacker introduces a stream record containing malicious HTML or JavaScript in a metadata field such as `name`, `genre`, or `url`. 2. The companion database marks the stream as available. 3. The user plays the stream, causing its metadata to be copied into the local playback history. 4. The user runs `show_history.py --export html`, optionally specifying an output file. 5. The export function interpolates the malicious value into the HTML document without escaping it. 6. The user opens the exported file in a browser. 7. The browser interprets the injected valu ...[truncated 656 chars]
Remediation
View remediation
{i}{t}{g}{d}" "{nm}{u}{no}".format( i=i, t=timestamp, g=genre, d=dur, nm=name, u=url, no=note, ) ) ``` Additional hardening measures: - Prefer an HTML template engine with automatic escaping enabled. - Treat all stream-database and history values as untrusted, even when loaded from local files. - If URLs are later rendered as links, separately validate their schemes and construct attributes using quoted, escaped values. - Add regression tests using payloads containing tags, quotes, ampersands, and event handlers. - Consider adding a restrictive Content Security Policy to exported documents as defense in depth, while retaining output escaping as the primary control. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/show_history.py:226
Finding

CSV Formula Injection in Playback History Export

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (11)

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The trigger phrase 'Play music' is extremely generic and likely to collide with normal assistant usage or other media skills. That can cause the skill to activate unintentionally, leading to unexpected network access, external stream playback, and state changes without clear user intent specific to this skill.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

These trigger phrases are broad conversational terms like 'Previous', 'Back', 'Status', and 'History' that can plausibly appear in normal dialogue unrelated to radio playback. In a voice- or chat-driven agent, that increases the chance of accidental activation and unintended control of media playback, especially because several aliases overlap with common navigation language.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
80% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

Scanner excerpt · SKILL.md (reported line 151)May include surrounding context.

md
- Auto-populated every 3 hours from internet-radio.com
- 29 genres collected in parallel
- Streams are speed-checked during population (60 workers, 4 sec)
- Streams with `failed_checks >= 3` are automatically removed from the database

## Files

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/play_music.py (reported line 217)May include surrounding context.

python
def play_foobar(url):
    stop_foobar()
    time.sleep(2)
    subprocess.Popen([FOOBAR, "/stop"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    time.sleep(1)
    subprocess.Popen([FOOBAR, "/play", url], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
89% confidence
Finding

This subprocess call launches Foobar2000 with a user-controlled URL argument from --url or untrusted database/history content. Although shell injection is avoided by using an argument list, the skill still causes an external application to open arbitrary remote URLs, enabling SSRF-style access to internal services, untrusted network interaction, and potentially abuse of protocol handlers if Foobar2000 accepts non-HTTP URI schemes.

Content

Scanner excerpt · scripts/play_music.py (reported line 219)May include surrounding context.

python
time.sleep(2)
    subprocess.Popen([FOOBAR, "/stop"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    time.sleep(1)
    subprocess.Popen([FOOBAR, "/play", url], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)


def stop_foobar():

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/play_music.py (reported line 223)May include surrounding context.

python
def stop_foobar():
    subprocess.run(["taskkill", "/f", "/im", "foobar2000.exe"], capture_output=True, timeout=10)


# --- Speed test ---

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The script writes potentially sensitive playback history fields such as stream URLs and notes to user-specified files in CSV export mode. Although the file purpose includes export functionality, there is no explicit warning, confirmation, or disclosure that exported files may contain personal listening history and metadata.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The HTML export persists detailed history records, including URLs and notes, to a file but only reports success after writing. This is a safety-relevant file write involving user activity data, and the script does not provide an upfront disclosure about the sensitivity of the exported content.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The JSON export serializes and saves user playback history, including URL and note fields, to an output file without any pre-write warning or disclosure. The success message appears only after the write and does not inform users in advance that potentially sensitive metadata will be stored.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The documentation explains that streams are fetched from a database and availability is checked via HTTP requests, but it does not clearly warn users up front that using the skill causes outbound network access and playback from third-party internet streams. This is primarily a transparency and consent issue rather than an exploit primitive, but it can still surprise users in privacy- or policy-sensitive environments.

Content

No source excerpt is available for this finding.

Scope Creep

Low
Category
Excessive Agency
Confidence
70% confidence
Finding

Skill's behavior or capabilities extend beyond its stated purpose. Scope creep allows an agent to perform actions unrelated to its documented functionality, increasing the attack surface.

Content

Scanner excerpt · scripts/play_music.py (reported line 410)May include surrounding context.

python
def cmd_resume(state):
    """Resume the last played stream from full history (not limited to today)."""
    history = state.get("History", [])
    if not history:
        print("No history — nothing to resume")

Static analysis

No suspicious patterns detected.