Back to skill

Security audit

LastFM to Spotify Playlists

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it says: it uses Last.fm data to generate music recommendations and can create Spotify playlists, but users should treat its saved Spotify token as a sensitive secret.

Install only if you are comfortable giving it Last.fm and Spotify credentials. Run Spotify auth only for an account you intend to use, remember that playlist creation modifies that Spotify account, and protect or delete ~/.openclaw/spotify-token.json if you no longer want the saved session.

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
spotify.py:73
Finding

Spotify OAuth Tokens Are Stored Without Restrictive File Permissions

Content
View full analysis
Path: out = Path(path) if path else DEFAULT_TOKEN_PATH out.parent.mkdir(parents=True, exist_ok=True) token["saved_at"] = int(time.time()) out.write_text(json.dumps(token, indent=2), encoding="utf-8") return out ``` ### Technical Analysis The token response saved by this function can contain a Spotify access token and a long-lived refresh token. The file is written using `Path.write_text()` without explicitly restricting its permissions. For a newly created file, the effective permissions depend on the process umask. With a common umask of `022`, the token file may be created with mode `0644`, making it readable by other local users. The parent directory is also created without an explicit owner-only mode. The default path is `~/.openclaw/spotify-token.json`, but callers can supply another path through `--spotify-token`. A user-provided location may be in a shared or otherwise insufficiently protected directory. ### Attack Path 1. The victim runs `python auth.py` and completes Spotify OAuth. 2. Spotify returns an access token and potentially a reusable refresh token. 3. `save_token()` writes these credentials to the token JSON file without enforcing owner-only permissions. 4. On a multi-user system with a permissive umask or shared token path, another local user or compromised process reads the file. 5. The attacker extracts the access or refresh token. 6. The attacker uses the granted Spotify OAuth scopes to make API requests until the token is revoked or otherwise invalidated. This path requires local filesystem access and permissions that allow the attacker to read the resulting file. ### Impact Assessment A stolen access token allows use of the permissions grante ...[truncated 538 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
spotify.py:136
Finding

Spotify OAuth Authorization Flow Does Not Validate a State Parameter

Content
View full analysis
None: return server = HTTPServer((host, port), CallbackHandler) params = { "client_id": creds["client_id"], "response_type": "code", "redirect_uri": creds["redirect_uri"], "scope": " ".join(scopes), } url = AUTH_URL + "?" + urllib.parse.urlencode(params) if open_browser: webbrowser.open(url) else: print(url) server.handle_request() server.server_close() if result.get("error"): raise SpotifyError(f"Spotify auth error: {result['error']}") if not result.get("code"): raise SpotifyError("No authorization code received.") token = _post_form( TOKEN_URL, { "grant_type": "authorization_code", "code": result["code"], "redirect_uri": creds["redirect_uri"], }, headers={"Authorization": _basic_auth_header(creds["client_id"], creds["client_secret"])}, ) ``` ### Technical Analysis The OAuth authorization request does not include a cryptographically random `state` parameter. The callback handler consequently accepts any authorization code delivered to the configured callback path ...[truncated 2678 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (24)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The declared description focuses on generating music recommendations and creating Spotify playlists based on Last.fm similarity and listening history. This code chunk instead implements an authentication utility: it requests Spotify scopes, performs OAuth authorization, stores tokens, retrieves an access token, and calls Spotify's user profile endpoint to fetch account identity data. OAuth support can be a supporting detail for playlist creation, but this chunk also accesses account profile data and its primary purpose is token setup rather than recommendations or playlist building. Therefore the behavior of this specific code chunk is materially different from the declared purpose.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description describes a music recommendation and Spotify playlist skill driven by Last.fm similarity and listening history. The supplied code chunk does not implement any of those behaviors or interact with Spotify, Last.fm, user listening data, or playlist APIs. Instead, it only provides a generic helper for dumping JSON to stdout. While this could be a supporting utility in a larger project, this code chunk by itself does not accurately represent the declared purpose, so it is a mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The code is clearly centered on Last.fm: it loads a Last.fm API key from environment variables or a local credentials file and performs read-only requests to Last.fm endpoints such as user.getrecenttracks, user.gettopartists, user.gettoptracks, artist.getsimilar, and track.getsimilar. These behaviors support gathering listening history and similarity data, which partially aligns with the recommendation aspect of the description. However, the declared purpose also says it will create Spotify playlists, and there is no Spotify API usage, authentication, playlist creation, or playlist management logic in this code chunk. Therefore the description overstates the implemented functionality in a material way.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The high-level description is broadly accurate: the code does build recommendations from Last.fm similarity/listening history and can create Spotify playlists. However, the declared permissions are empty, while the code clearly interacts with Last.fm and Spotify, loads credentials/tokens, searches Spotify tracks, creates playlists, and adds tracks to them. Those are material capabilities and resource accesses that should be declared. The primary purpose is aligned, so this is a permissions/capabilities mismatch rather than a purpose mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

The code is focused on Spotify authentication and basic playlist-related API operations. It loads credentials, stores tokens on disk, refreshes tokens, opens a browser for OAuth authorization, runs a local callback HTTP server, searches Spotify tracks, fetches the current user, creates playlists, and adds tracks to playlists. While playlist creation is consistent with part of the description, the central claimed functionality of building recommendations from Last.fm similarity and listening history is absent. The code also performs additional undeclared capabilities related to credential persistence and OAuth flow handling.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
91% confidence
Finding

The skill is designed to read Last.fm credentials from environment variables and local JSON files, which is a sensitive capability. Even if expected for functionality, credential access is dangerous because compromised or overly broad skill execution could expose API keys, usernames, or shared secrets and enable unauthorized API use.

Content

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

md
- `LASTFM_SHARED_SECRET`
  - `LASTFM_USERNAME`
- credentials file:
  - `~/.openclaw/lastfm-credentials.json`
- explicit file path via command flag:
  - `--creds <path>`

Credential Access

High
Category
Privilege Escalation
Confidence
94% confidence
Finding

The skill reads Spotify client credentials and later uses persisted tokens, which grants access to sensitive authentication material for a third-party account. Exposure or misuse of these secrets can lead to unauthorized API calls, account data access, or playlist/account manipulation beyond the user's intent.

Content

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

md
- `SPOTIFY_CLIENT_SECRET`
  - `SPOTIFY_REDIRECT_URI`
- credentials file:
  - `~/.openclaw/spotify-credentials.json`
- explicit file path via command flag:
  - `--spotify-creds <path>`

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · lastfm.py (reported line 11)May include surrounding context.

python
from typing import Any

API_ROOT = "https://ws.audioscrobbler.com/2.0/"
DEFAULT_CREDS_PATH = Path.home() / ".openclaw" / "lastfm-credentials.json"


class LastFMError(RuntimeError):

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · lastfm.py (reported line 31)May include surrounding context.

python
from typing import Any

API_ROOT = "https://ws.audioscrobbler.com/2.0/"
DEFAULT_CREDS_PATH = Path.home() / ".openclaw" / "lastfm-credentials.json"


class LastFMError(RuntimeError):

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · spotify.py (reported line 17)May include surrounding context.

python
from typing import Any

API_ROOT = "https://ws.audioscrobbler.com/2.0/"
DEFAULT_CREDS_PATH = Path.home() / ".openclaw" / "lastfm-credentials.json"


class LastFMError(RuntimeError):

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · spotify.py (reported line 48)May include surrounding context.

python
from typing import Any

API_ROOT = "https://ws.audioscrobbler.com/2.0/"
DEFAULT_CREDS_PATH = Path.home() / ".openclaw" / "lastfm-credentials.json"


class LastFMError(RuntimeError):

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The README instructs users to run a command that creates a Spotify playlist and to complete Spotify OAuth, but it does not clearly warn that this will perform write actions on the user's Spotify account. This can lead to users authorizing account modification without informed consent, increasing the risk of unintended data changes or overbroad permission acceptance.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill declares executable local scripts with access to environment variables, credential files, token storage, filesystem paths, and external network services, but it does not declare any explicit tool scope or permissions boundaries. This weakens reviewability and consent because a user cannot easily see that the skill may read secrets, write tokens, and call Last.fm/Spotify APIs before running it.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
83% confidence
Finding

The skill documents saved token storage at a persistent local path for Spotify authentication, which creates a session persistence surface. Persisted tokens increase convenience but also increase risk if the host is shared, compromised, or if users are not clearly told how long access persists and how to revoke it.

Content

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

md
---
name: lastfm-spotify-playlists
description: Build music recommendations and create Spotify playlists using Last.fm similarity and listening history.
---

# Execution Model

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The skill instructs automatic playlist creation when certain user intents are detected, but it does not include a clear warning that this will modify the user's Spotify account. Account-modifying actions without an explicit warning or confirmation can lead to unintended changes, especially when combined with stored OAuth tokens and direct script execution.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · lastfm.py (reported line 31)May include surrounding context.

python
self._api_key = api_key or self.load_credentials(explicit_path).get("api_key")
        if not self._api_key:
            raise LastFMError(
                "Missing Last.fm API key. Set LASTFM_API_KEY or create ~/.openclaw/lastfm-credentials.json"
            )

    @staticmethod

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · spotify.py (reported line 48)May include surrounding context.

python
self._api_key = api_key or self.load_credentials(explicit_path).get("api_key")
        if not self._api_key:
            raise LastFMError(
                "Missing Last.fm API key. Set LASTFM_API_KEY or create ~/.openclaw/lastfm-credentials.json"
            )

    @staticmethod

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The function obtains a Spotify access token, creates a playlist, and adds tracks to it, which are state-changing remote operations affecting the user's account. In this file there is no confirmation prompt, print/log disclosure, or comment/docstring warning around these actions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

The CLI accepts paths for Last.fm credentials, Spotify credentials, and a Spotify token, which are safety-relevant sensitive inputs. This file provides no confirmation prompt, logging, comment, or docstring warning users that credential material will be read and used, while the skill also appears capable of creating playlists via downstream processing.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · auth.py (reported line 34)May include surrounding context.

python
from pathlib import Path
from typing import Any

API_ROOT = "https://api.spotify.com/v1"
AUTH_URL = "https://accounts.spotify.com/authorize"
TOKEN_URL = "https://accounts.spotify.com/api/token"
DEFAULT_CREDS_PATH = Path.home() / ".openclaw" / "spotify-credentials.json"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · spotify.py (reported line 14)May include surrounding context.

python
from pathlib import Path
from typing import Any

API_ROOT = "https://api.spotify.com/v1"
AUTH_URL = "https://accounts.spotify.com/authorize"
TOKEN_URL = "https://accounts.spotify.com/api/token"
DEFAULT_CREDS_PATH = Path.home() / ".openclaw" / "spotify-credentials.json"

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The code persists Spotify OAuth tokens, including refresh tokens, to a predictable file under the user's home directory without setting restrictive permissions or clearly informing the user. If the host is multi-user, backed up, synced, or otherwise accessible to other processes, token theft could allow unauthorized access to the user's Spotify account and playlist actions until revoked.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
78% confidence
Finding

The code sends the provided Last.fm username to external APIs to retrieve recent tracks, and similar user-linked calls also appear elsewhere in the file. These outbound requests involve transmitting user-associated listening data, but the file provides no visible warning, logging, or explanatory comment about that data use.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The function reads sensitive credentials from environment variables and from a local credentials file, but the code provides no user-facing warning, prompt, or explanatory comment about accessing secret material. This matches the missing-warning criterion for sensitive environment variable or credential access in code files.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.exposed_secret_literal, suspicious.install_untrusted_source

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
auth.py:33

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
pipeline.py:450

Install source points to URL shortener or raw IP.

Warn
Code
suspicious.install_untrusted_source
Location
spotify_credentials.example.json:4