Back to skill

Security audit

SpotiClaw

Security checks for vulnerabilities and agentic risk

Overview

This Spotify agent skill is coherent, but it gives an agent broad account-changing access while relying on reusable plaintext Spotify tokens that need closer review before installation.

Install only if you are comfortable giving the agent control over Spotify playback, playlists, private playlist reads, library changes, listening history, and top-artist/track data. Keep .spotify_cache and .env out of shared folders and source control, restrict their filesystem permissions, prefer a dedicated Spotify app with minimal scopes, and require explicit confirmation before playlist, library, playback, or public-content changes.

Vulnerability Patterns
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (3)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/auth.py:81
Finding

OAuth Authorization Flow Does Not Validate a CSRF State Parameter

Content
View full analysis
Authentication successful!

You can close this window.

") global auth_code auth_code = params["code"] ``` ```python auth_url = "https://accounts.spotify.com/authorize?" + urlencode({ "client_id": CLIENT_ID, "response_type": "code", "redirect_uri": REDIRECT_URI, "scope": SCOPE, }) ``` ### Technical Analysis The OAuth authorization request does not contain a cryptographically random `state` parameter. The callback handler consequently accepts any `code` parameter without verifying that the callback belongs to the authorization flow initiated by this process. The OAuth `state` parameter binds an authorization response to the browser session and local process that initiated the request. Without this binding, the local callback endpoint cannot distinguish a legitimate response from an attacker-induced response. The callback parsing is also implemented manually rather than with `urllib.parse.urlparse()` and `parse_qs()`. Although this is not the primary vulnerability, standards-compliant parsing would avoid malformed-query and encoding edge cases. ### Attack Path 1. An attacker initiates a Spotify authorization flow using the same public client ID and registered loopback redirect URI. 2. The attacker authorizes the application against a Spotify account control ...[truncated 1374 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/auth.py:117
Finding

OAuth Access and Refresh Tokens Are Stored Without Explicit Restrictive File Permissions

Content
View full analysis
None: """Save token data to cache.""" cache_file = _get_cache_path() with open(cache_file, "w") as f: json.dump(data, f) ``` ### Technical Analysis The `.spotify_cache` file can contain both a short-lived access token and a long-lived refresh token. Both write paths use ordinary `open(..., "w")` calls and rely entirely on the operating system's current process umask and any permissions already assigned to the file. On systems with a permissive umask, shared working directory, inherited file mode, or unsuitable ownership configuration, another local user or process may be able to read the cache. The refresh token is particularly sensitive because it can be exchanged repeatedly for new access tokens. The refresh path also writes directly to the destination file. A crash during serialization can leave a truncated token cache. In an attacker-writable directory, direct path handling may also expose the write operation to symlink manipulation. ### Attack Path 1. A user runs `scripts/auth.py`, which completes OAuth authorization. 2. Spotify returns access-token data and, where applicable, a refresh token. 3. The helper creates `.spotify_cache` using permissions derived from the current umask rather than explicitly requiring owner-only access. 4. Another local account or process with filesystem access reads the cache. 5. The attacker extracts the bearer access token or refresh ...[truncated 1110 chars]
Remediation
View remediation
None: cache_file.parent.mkdir(mode=0o700, parents=True, exist_ok=True) fd, temporary_path = tempfile.mkstemp( dir=cache_file.parent, prefix=".spotify_cache.", ) try: os.fchmod(fd, 0o600) with os.fdopen(fd, "w") as handle: json.dump(data, handle) handle.flush() os.fsync(handle.fileno()) os.replace(temporary_path, cache_file) os.chmod(cache_file, 0o600) except Exception: try: os.unlink(temporary_path) except FileNotFoundError: pass raise ``` 3. Check that the cache path is not a symbolic link before opening or replacing it. 4. Verify that the cache file and parent directory are owned by the expected user. 5. Reject existing cache files that are group-readable or world-readable, or automatically correct their permissions with a clear warning. 6. On Windows, apply an access control list that restricts the cache to the current user. 7. Document that `.spotify_cache` must never be committed to source control, included in logs, or placed in a shared directory. 8. Consider using an operating-system credential store instead of a plaintext JSON file where deployment constraints permit it. ]]>

T08 · Insecure Dependencies

Note
Location
scripts/requirements.txt:1
Finding

Runtime Dependencies Are Installed Without Version or Integrity Pinning

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (29)

Tainted flow: 'REDIRECT_URI' from os.getenv (line 34, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/auth.py (reported line 110)May include surrounding context.

python
# Exchange for token
    print("Exchanging code for token...")
    resp = requests.post(
        "https://accounts.spotify.com/api/token",
        data={
            "grant_type": "authorization_code",

Tainted flow: 'auth_header' from os.getenv (line 78, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/spoticlaw.py (reported line 81)May include surrounding context.

python
auth_header = base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()
    
    # Request new token
    resp = requests.post(
        "https://accounts.spotify.com/api/token",
        data={
            "grant_type": "refresh_token",

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

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

Content

Scanner excerpt · README.md (reported line 52)May include surrounding context.

  1. Configure Spotify credentials:
bash
cp .env.example .env
# Edit .env with your Spotify app credentials

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 · SKILL.md (reported line 97)May include surrounding context.

md
The library automatically handles token refresh **only if the agent has the same app credentials in `.env`**:

- Access token expires after ~1 hour
- On first API call after expiry, it uses `refresh_token` + client credentials to request a new access token
- Requires `SPOTIFY_CLIENT_ID` and `SPOTIFY_CLIENT_SECRET` in the agent environment
- If `.spotify_cache` exists but `.env` is missing/mismatched, refresh fails (`invalid_client`)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- `scripts/spoticlaw.py` - Main API client

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

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

Content

Scanner excerpt · README.md (reported line 53)May include surrounding context.

md
import requests
from dotenv import load_dotenv

# Load .env
_env_path = Path(__file__).parent.parent / ".env"
load_dotenv(_env_path)

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

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

Content

Scanner excerpt · scripts/spoticlaw.py (reported line 18)May include surrounding context.

python
import requests
from dotenv import load_dotenv

# Load .env
_env_path = Path(__file__).parent.parent / ".env"
load_dotenv(_env_path)

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

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

Content

Scanner excerpt · scripts/auth.py (reported line 140)May include surrounding context.

python
from dotenv import load_dotenv

# Load .env
_env_path = Path(__file__).parent.parent / ".env"
load_dotenv(_env_path)

# Configuration

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

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

Content

Scanner excerpt · scripts/spoticlaw.py (reported line 19)May include surrounding context.

python
from dotenv import load_dotenv

# Load .env
_env_path = Path(__file__).parent.parent / ".env"
load_dotenv(_env_path)

# Configuration

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 · README.md (reported line 103)May include surrounding context.

md
def _refresh_token() -> str:
    """Refresh access token using refresh_token."""
    token_data = _load_token_data()
    refresh_token = token_data.get("refresh_token")

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 · SKILL.md (reported line 98)May include surrounding context.

md
def _refresh_token() -> str:
    """Refresh access token using refresh_token."""
    token_data = _load_token_data()
    refresh_token = token_data.get("refresh_token")

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 · SKILL.md (reported line 553)May include surrounding context.

md
def _refresh_token() -> str:
    """Refresh access token using refresh_token."""
    token_data = _load_token_data()
    refresh_token = token_data.get("refresh_token")

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 · scripts/spoticlaw.py (reported line 69)May include surrounding context.

python
def _refresh_token() -> str:
    """Refresh access token using refresh_token."""
    token_data = _load_token_data()
    refresh_token = token_data.get("refresh_token")

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 · scripts/spoticlaw.py (reported line 113)May include surrounding context.

python
def _refresh_token() -> str:
    """Refresh access token using refresh_token."""
    token_data = _load_token_data()
    refresh_token = token_data.get("refresh_token")

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding

The skill documents and encourages use of environment variables, a local token cache file, and outbound network access, but it does not declare any explicit tool scope or permissions boundary. That omission increases the chance an agent runtime grants broader-than-expected access, making credential exposure, unintended file operations, or unauthorized Spotify actions harder to constrain and audit.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The quick-start examples immediately demonstrate playback control, playlist modification, and library changes without an explicit warning that these actions affect the user's account and active devices. In an agent setting, this can lead to surprising or privacy-impacting side effects such as altering playback, editing playlists, or saving content without clear user consent.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script claims tokens never pass through the AI model, but it explicitly instructs the user to place the .spotify_cache token file inside the agent skill directory where the agent can access it. In the context of an agent skill, giving the skill runtime direct access to refresh/access tokens materially increases the chance of token misuse, exfiltration, or actions on the user's Spotify account beyond intended use.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The script writes the OAuth token response, including sensitive access and potentially refresh tokens, to a local JSON cache file without warning the user or restricting file permissions. If the file is copied, backed up, or read by other local users/processes, those credentials can be reused to access the victim's Spotify account within the granted scopes.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The code reads Spotify client credentials from environment variables and later uses them to construct a Basic Authorization header for token refresh requests. While these operations are part of API authentication, this file provides no user-facing warning, confirmation, or explanatory comment that sensitive secrets are being accessed and sent over the network.

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 · scripts/spoticlaw.py (reported line 28)May include surrounding context.

python
REDIRECT_URI = os.getenv("SPOTIFY_REDIRECT_URI", "http://127.0.0.1:8888/callback")
CACHE_PATH = os.getenv("SPOTIFY_CACHE_PATH", ".spotify_cache")

BASE_URL = "https://api.spotify.com/v1"


class SpotifyException(Exception):

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The code persists OAuth token data, including refresh tokens, to a local cache file without setting restrictive file permissions or protecting the storage path. If another local user or process can read that file, they may gain long-lived access to the Spotify account and act as the user.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
70% 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 · scripts/auth.py (reported line 110)May include surrounding context.

python
auth_header = base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()
    
    # Request new token
    resp = requests.post(
        "https://accounts.spotify.com/api/token",
        data={
            "grant_type": "refresh_token",

External Transmission

Medium
Category
Data Exfiltration
Confidence
70% 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 · scripts/spoticlaw.py (reported line 81)May include surrounding context.

python
auth_header = base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()
    
    # Request new token
    resp = requests.post(
        "https://accounts.spotify.com/api/token",
        data={
            "grant_type": "refresh_token",

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

Methods such as removing library items, removing playlist items, and replacing playlist contents perform user-data-affecting changes through the Spotify API. The code includes only terse method descriptions and no explicit warning, confirmation, or user-facing notice that these actions modify or remove saved content.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest describes the skill as for Spotify search, playback, playlists, library, tracks, artists, albums, shows, and podcasts. This file additionally exposes Audiobooks and Chapters endpoints, which are not mentioned in the declared description and broaden the skill beyond the stated capability set.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.