Skip to content

Conversation

@renovate
Copy link

@renovate renovate bot commented Jan 8, 2026

This PR contains the following updates:

Package Change Age Confidence
spotipy (source) 2.19.02.22.1 age confidence
spotipy (source) ==2.19.0==2.25.2 age confidence

GitHub Vulnerability Alerts

CVE-2023-23608

Summary

If a malicious URI is passed to the library, the library can be tricked into performing an operation on a different API endpoint than intended.

Details

The code Spotipy uses to parse URIs and URLs accepts user data too liberally which allows a malicious user to insert arbitrary characters into the path that is used for API requests. Because it is possible to include .., an attacker can redirect for example a track lookup via spotifyApi.track() to an arbitrary API endpoint like playlists, but this is possible for other endpoints as well.

Before the security advisory feature was enabled on GitHub, I was already in contact with Stéphane Bruckert via e-mail, and he asked me to look into a potential fix.

My recommendation is to perform stricter parsing of URLs and URIs, which I implemented in the patch included at the end of the report. If you prefer, I can also invite you to a private fork of the repository.

Impact

The impact of this vulnerability depends heavily on what operations a client application performs when it handles a URI from a user and how it uses the responses it receives from the API.

CVE-2025-27154

Summary

The CacheHandler class creates a cache file to store the auth token here: https://github.com/spotipy-dev/spotipy/blob/master/spotipy/cache_handler.py#L93-L98

The file created has rw-r--r-- (644) permissions by default, when it could be locked down to rw------- (600) permissions. I think 600 is a sensible default.

image

Details

This leads to overly broad exposure of the spotify auth token. If this token can be read by an attacker (another user on the machine, or a process running as another user), it can be used to perform administrative actions on the Spotify account, depending on the scope granted to the token.

PoC

Run an application that uses spotipy with client creation like this:

from pathlib import Path
import spotipy
from os import getenv

def create_spotify_client(client_id: str, client_secret: str) -> spotipy.Spotify:
    """Create and return an authenticated Spotify client.

    Args:
        client_id: Spotify API client ID
        client_secret: Spotify API client secret

    Returns:
        An authenticated Spotify client instance
    """
    cache_path = Path.home() / ".cache" / "spotify-backup/.auth_cache"
    cache_path.parent.mkdir(parents=True, exist_ok=True)
    cache_handler = spotipy.cache_handler.CacheFileHandler(cache_path=str(cache_path))

    client = spotipy.Spotify(
        auth_manager=spotipy.oauth2.SpotifyOAuth(
            client_id=client_id,
            client_secret=client_secret,
            redirect_uri="http://localhost:8000/callback",
            cache_handler=cache_handler,
            scope=[
                "user-library-read",
                "playlist-read-private",
                "playlist-read-collaborative",
            ],
        )
    )

    return client

create_spotify_client()

And then check the file permissions on the cache file that was created with:

$ ls -la ~/.cache/spotify-backup/.auth_cache`
.rw-r--r--. alichtman alichtman 562 B Thu Feb 20 02:12:33 2025  /home/alichtman/.cache/spotify-backup/.auth_cache

If this issue is combined with another misconfiguration, like having o+r permissions set on your home directory, an attacker will be able to read this file and steal this auth token.

Good defense in depth would be to restrict read permissions on this cache file that contains an auth token

Impact

Potential exposure of Spotify auth token to other users with access to the machine. A worst case scenario is if the token is granted all permissions, and can be used to do any of:

  • exfiltrate spotify likes / saved playlists
  • delete your content
  • modify your content w/o your permission

If someone were to discover an RCE in Spotify that you could trigger on a machine by having a song played (or song metadata parsed or something), this auth token could maybe be used to add a song to a playlist, or control playback (allowing further exploitation).

CVE-2025-66040

Summary

XSS vulnerability in OAuth callback server allows JavaScript injection through unsanitized error parameter. Attackers can execute arbitrary JavaScript in the user's browser during OAuth authentication.

Details

Vulnerable Code: spotipy/oauth2.py lines 1238-1274 (RequestHandler.do_GET)

The Problem:
During OAuth flow, spotipy starts a local HTTP server to receive callbacks. The server reflects the error URL parameter directly into HTML without sanitization.

Vulnerable code at line 1255:

status = f"failed ({self.server.error})"

Then embedded in HTML at line 1265:

self._write(f"""<html>
<body>
<h1>Authentication status: {status}</h1>
</body>
</html>""")

The error parameter comes from URL parsing (lines 388-393) without HTML escaping, allowing script injection.

Attack Flow:

  1. User starts OAuth authentication → local server runs on http://127.0.0.1:8080
  2. Attacker crafts malicious URL: http://127.0.0.1:8080/?error=<script>alert(1)</script>&state=x
  3. User visits URL → JavaScript executes in localhost origin

PoC

Simple Python Test:

#!/usr/bin/env python3
# poc_xss.py - Demonstrates XSS in spotipy OAuth callback

import requests
from spotipy.oauth2 import start_local_http_server
import threading
import time

# Start vulnerable server in background
def start_server():
    server = start_local_http_server(8080)
    server.handle_request()

thread = threading.Thread(target=start_server, daemon=True)
thread.start()
time.sleep(2)

# Send XSS payload
payload = '<script>alert("XSS")</script>'
url = f'http://127.0.0.1:8080/?error={payload}&state=test'

response = requests.get(url)
print(f"Status: {response.status_code}")
print(f"\nHTML Response:\n{response.text}")

# Check if vulnerable
if payload in response.text:
    print(f"\n[!] VULNERABLE: Payload '{payload}' reflected without escaping!")
else:
    print("\n[+] Safe: Payload was sanitized")

Run it:

pip install spotipy requests
python3 poc_xss.py

Output shows:

Status: 200
HTML Response:
<html>
<body>
<h1>Authentication status: failed (<script>alert("XSS")</script>)</h1>
</body>
</html>

[!] VULNERABLE: Payload '<script>alert("XSS")</script>' reflected without escaping!

The Proof:

  • Expected (safe): &lt;script&gt;alert("XSS")&lt;/script&gt;
  • Actual (vulnerable): <script>alert("XSS")</script>
  • The script tags are NOT escaped → XSS confirmed

Impact

Vulnerability Type: Cross-Site Scripting (XSS) - CWE-79

Affected Users: Anyone using spotipy's OAuth flow with localhost redirect URIs

Attack Complexity: Medium-High

  • Requires timing (during brief OAuth window)
  • Localhost-only (127.0.0.1)
  • Requires user interaction (click malicious link)

Potential Impact:

  • Execute JavaScript in localhost origin
  • Access other localhost services (port scanning, API calls)
  • Steal data from local web applications
  • Extract OAuth tokens from browser storage
  • Bypass CSRF protections on localhost endpoints

CVSS 3.1 Score: 4.2 (Medium)

  • Attack Vector: Local
  • Attack Complexity: High
  • Privileges Required: None
  • User Interaction: Required
  • Scope: Unchanged
  • Confidentiality/Integrity: Low

Recommended Fix:

import html

# Line 1255 - apply HTML escaping
if self.server.error:
    status = f"failed ({html.escape(str(self.server.error))})"

Release Notes

plamere/spotipy (spotipy)

v2.22.1

Compare Source

Added
  • Add alternative module installation instruction to README
  • Added Comment to README - Getting Started for user to add URI to app in Spotify Developer Dashboard.
  • Added playlist_add_tracks.py to example folder
Changed
  • Modified docstring for playlist_add_items() to accept "only URIs or URLs",
    with intended deprecation for IDs in v3
Fixed
  • Path traversal vulnerability that may lead to type confusion in URI handling code
  • Update contributing.md

v2.22.0

Compare Source

Added
  • Integration tests via GHA (non-user endpoints)
  • Unit tests for new releases, passing limit parameter with minimum and maximum values of 1 and 50
  • Unit tests for categories, omitting country code to test global releases
  • Added CODE_OF_CONDUCT.md
Fixed
  • Incorrect category_id input for test_category
  • Assertion value for test_categories_limit_low and test_categories_limit_high
  • Pin GitHub Actions Runner to Ubuntu 20 for Py27
  • Fixed potential error where found variable in test_artist_related_artists is undefined if for loop never evaluates to true
  • Fixed false positive test test_new_releases which looks up the wrong property of the JSON response object and always evaluates to true

v2.21.0

Compare Source

Added
  • Added market parameter to album and albums to address (#​753
  • Added show_featured_artists.py to /examples.
  • Expanded contribution and license sections of the documentation.
  • Added FlaskSessionCacheHandler, a cache handler that stores the token info in a flask session.
  • Added Python 3.10 in GitHub Actions
Fixed
  • Updated the documentation to specify ISO-639-1 language codes.
  • Fix AttributeError for text attribute of the Response object
  • Require redis v3 if python2.7 (fixes readthedocs)

v2.20.0

Compare Source

Added
  • Added RedisCacheHandler, a cache handler that stores the token info in Redis.
  • Changed URI handling in client.Spotify._get_id() to remove queries if provided by error.
  • Added a new parameter to RedisCacheHandler to allow custom keys (instead of the default token_info key)
  • Simplify check for existing token in RedisCacheHandler
Changed
  • Removed Python 3.5 and added Python 3.9 in GitHub Action

Configuration

📅 Schedule: Branch creation - "" (UTC), Automerge - At any time (no schedule defined).

🚦 Automerge: Enabled.

Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

👻 Immortal: This PR will be recreated if closed unmerged. Get config help if that's undesired.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate bot requested a review from billsioros as a code owner January 8, 2026 05:10
@renovate renovate bot added the 🎲 dependencies Working on dependencies label Jan 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🎲 dependencies Working on dependencies

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants