How Spotify Silently Updates Itself (And How to Stop It)

10 min read Original article ↗

Spotify updated itself without my permission. I blocked it. It updated again. I blocked it again. It found a third way. This is the story of reverse-engineering Spotify's three-stage auto-update mechanism on Windows and how to permanently shut it down.


I've become so numb

Let me briefly give you a bit of background: after Spotify updated its pricing last year, I've reached a point, where I really questioned value vs price. Sure, Spotify has a huge range of music directly available to you. But looking at the outcome for a musician (I've worked in this area many years ago), it's devastating how little a band gets in their pocket, even their songs are streamed a couple hundred thousands times. Imho, it's a joke how little artists get paid.

Back to the main topic: I wanted to freeze Spotify at a specific version. The reasons may vary: maybe you want stability, maybe you're running Spicetify and don't want your patches wiped on every update, maybe you just don't like software changing on your machine without explicit consent.

I discovered that Spotify has quietly built a surprisingly sophisticated multi-layer update system that doesn't rely on a Windows Service, doesn't show up in Task Scheduler, and survives most naive blocking attempts. Let's tear it apart.


Handgranate pins in every line

After some back and forth, it seems Spotify runs a the three-stage update mechanism.

Stage 1: The Executable Update (via SpotifyLauncher)

The obvious layer. SpotifyLauncher.exe (~10 MB, in %AppData%\Roaming\Spotify\) is what actually runs when you click the Spotify icon. Not Spotify.exe directly. On every launch it checks for a new version and downloads it before handing off to the main process.

The update servers:

upgrade.scdn.co
upgrade.spotify.com

This is the layer most people try to block and it's the easiest. Add both domains to your hosts file pointing at 0.0.0.0 and SpotifyLauncher can no longer pull a new executable.

There's also an in-app setting that supposedly controls this:

# %AppData%\Roaming\Spotify\prefs
app.update.enabled=false

The catch: Spotify rewrites this file on every startup, so you have to mark prefs as read-only after adding that line. Simple enough.

Stage 2: The prefs.tmp Bypass

Here's where it gets interesting.

When Spotify starts and finds that prefs is read-only, it doesn't crash or complain. It silently falls back to writing settings to prefs.tmp instead. Same directory, same format, used as a working copy.

The problem: prefs.tmp doesn't inherit your app.update.enabled=false setting. Spotify writes a clean copy without it. Depending on which file takes precedence on the next launch, your update block may be silently ignored.

Fix: apply the same treatment to prefs.tmp, write the setting, mark the file read-only.

# %AppData%\Roaming\Spotify\prefs.tmp
app.update.enabled=false

Both files, both read-only. Now the preference layer is locked.

(Add on: I've added my own pref file below as a template for those, who want to tweak around)

Stage 3: The xpui Hot-Update (the sneaky one)

This is the one that catches everyone off guard, because it doesn't touch Spotify.exe at all.

Spotify is built on Chromium Embedded Framework (CEF). The entire UI, everything you see, is a web application living in:

%AppData%\Roaming\Spotify\Apps\xpui\

That folder contains hundreds of JS and CSS bundle files. Spotify can update the entire UI layer independently from the C++ executable, by simply replacing the contents of this folder. The main binary stays at the same version. The app.last-launched-version pref doesn't change. But you're running different code.

The evidence: after locking the executable update and the prefs bypass, I observed the Apps\xpui\ directory getting a fresh timestamp, all files simultaneously updated, while Spotify.exe modification date stayed unchanged. Spotify had silently replaced its entire frontend.

The mechanism: the SpotifyLauncher binary contacts spclient.wg.spotify.com (Spotify's main API gateway, also used for playlists, remote config, and event collection) and receives instructions to pull a new app bundle. The update is applied by deleting the entire xpui\ directory and recreating it. Not by patching individual files.

This matters for the fix.


One step closer

Now comes blocking stage 3: NTFS ACLs, Not chmod

My first attempt was chmod -R -w on the xpui\ directory via Git Bash. This sets the read-only attribute bit on individual files, but on Windows, the attribute bit on a file only prevents modifying the file's contents. It does not prevent a process from deleting the file if the parent directory grants write access.

Spotify sidestepped this entirely: it deleted the whole Apps\xpui\ directory (write permission on the parent Apps\ folder was never restricted) and created a fresh one with new files. The read-only bits were gone because they lived on files that no longer existed.

The correct tool is Windows NTFS ACLs via icacls.

What I tried first (wrong):

icacls "...\Apps\xpui" /deny "CINDERELLA\cinderella:(OI)(CI)(M)" /T

Using (M) (Modify) denies write and read. Spotify's Chromium renderer couldn't read its own UI files and fell back to trying to load xpui.app.spotify.com over the network, resulting in a DNS_PROBE_FINISHED_NXDOMAIN error and a broken app.

What I tried second (also wrong):

icacls "...\Apps\xpui" /deny "CINDERELLA\cinderella:(OI)(CI)(W,D,DC)" /T

Only denying write/delete, not read. Theoretically correct. But Spotify writes runtime state (lock files, etc.) into the xpui\ directory during normal operation. Blocking write on xpui\ itself breaks the startup sequence, again causing the xpui URL handler to fail to register.. same DNS error, different cause.

What actually works:

icacls "...\Apps" /deny "CINDERELLA\cinderella:(AD,DC)"

Target the parent directory (Apps\), not xpui\ itself:

  • (AD) = Add Directory: prevents creating new subdirectories inside Apps\
  • (DC) = Delete Child: prevents deleting xpui\ or login\ from Apps\

No inheritance flags, no recursion. Just two rights on one directory.

Spotify cannot delete xpui\ (DC blocked). It cannot create xpui_new\ to stage a replacement (AD blocked). The contents of xpui\ remain fully accessible for normal runtime use. Spotify starts and works normally.


So, I'm breaking the habit

Ok, the complete fix looks like this basically:

%AppData%\Roaming\Spotify\
├── prefs          → add app.update.enabled=false, mark read-only
├── prefs.tmp      → add app.update.enabled=false, mark read-only
└── Apps\          → NTFS deny (AD,DC) for current user
C:\Windows\System32\drivers\etc\hosts
→ 0.0.0.0  upgrade.scdn.co
→ 0.0.0.0  upgrade.spotify.com

Verified ACL state:

C:\...\Spotify\Apps   CINDERELLA\cinderella:(DENY)(AD,DC)

From the inside

A few architectural observations worth noting for anyone digging deeper:

SpotifyLauncher is the update orchestrator. It's a 10 MB binary that runs first, checks for updates, applies them, and then launches the actual Spotify process. Running Spotify.exe directly bypasses the update check entirely. Though, the xpui hot-update happens post-launch via the remote config resolver.

The remote config endpoint. Strings extracted from SpotifyLauncher.exe reveal:

https://spclient.wg.spotify.com/remote-config-resolver/v3/unauth/configuration

This is how Spotify receives instructions server-side, like feature flags, rollout configs, and apparently xpui update triggers. It's the same host that serves playlist data, so blocking it wholesale breaks the app.

Chromium profile state. Spotify stores a full Chromium browser profile at %LocalAppData%\Spotify\Default\. This includes the Code Cache, which was updated at the exact timestamp of the xpui hot-update, confirming the bundle replacement had taken effect and the JS had been recompiled and cached.

The public.ldb database. %LocalAppData%\Spotify\ contains a LevelDB store that tracks update state, including a key DesktopUpdateDownloadComplete. A reliable forensic indicator that a hot-update has been staged and applied.


What I've done ...

... to make it work with Spicetify is I've toggled between locking Spotify down or not, if required. If you're patching Spotify with Spicetify, you need full write access to Apps\xpui\. Here's the toggle workflow:

Before patching:

:: spotify_reset_permissions.bat
icacls "...\Apps" /remove:d "CINDERELLA\cinderella"
attrib -R "...\Spotify\prefs"
attrib -R "...\Spotify\prefs.tmp"

After patching:

:: spotify_lock_permissions.bat
icacls "...\Apps" /deny "CINDERELLA\cinderella:(AD,DC)"
:: re-add app.update.enabled=false to prefs + prefs.tmp, mark both read-only
attrib +R "...\Spotify\prefs"
attrib +R "...\Spotify\prefs.tmp"

Both scripts are included below.


Falling for the promise of the emptiness machine

As mentioned above, I've did this all on a windows machine (shame on me still for not switched fully to unix). Therefore, what this doesn't cover:

  • macOS/Linux: Spotify's update mechanism on other platforms is different. On macOS it uses a Sparkle-based updater; the xpui hot-update concept likely applies but the file paths and permission model differ.
  • The Microsoft Store version: The Store version of Spotify is a UWP app with a completely different update mechanism controlled by the Store runtime. None of this applies.
  • Future-proofing: Spotify could add a fourth layer at any point, watching the ACL and reverting it, using an alternate staging directory, or pulling xpui updates from a different endpoint. The (AD,DC) approach blocks the current mechanism; treat it as version-specific.

So, maybe get yourself the stand-alone installer of Spotify, if you like to play around with it. Like: https://download.scdn.co/SpotifySetup.exe


In the end...

Spotify's auto-update system has three independent layers:

Layer Mechanism Block
Executable SpotifyLauncher.exe downloads new binary hosts file + app.update.enabled=false in prefs
prefs bypass Falls back to prefs.tmp when prefs is read-only Same setting in prefs.tmp, also read-only
xpui hot-update Deletes and recreates Apps\xpui\ directory NTFS deny (AD,DC) on Apps\ parent directory

The key insight: chmod / file attribute bits are insufficient on Windows because they protect file contents, not the files' existence. For that, you need ACLs on the parent directory. And when applying ACLs, target the parent rather than the contents. Spotify's runtime legitimately needs write access inside xpui\.

So far, so good. Let's see, if this works out... or if I missed another sneaky twist and Spotify updates itself in a couple of days nonetheless. I'm really looking forward to your thoughts and comments!


Additonal discovery 2026-03-17:

In Windows, a directory can be deleted if either FILE_DELETE_CHILD is permitted on the parent directory OR DELETE is permitted on the object itself. xpui/ inherits Full Control — meaning DELETE is permitted on itself — which overrides our (DC) block on Apps/. Fix: Also prohibit DELETE on xpui/ and login/ themselves!


Tested on Spotify 1.2.8x, Windows 11, 2026.

My pref file as baseline ("%AppData%\Roaming\Spotify\Users\your.mum-user\prefs"):

ui.system_media_controls_enabled=false
audio.equalizer.low_peak_gain_v2=1002159035
audio.equalizer.high_peak_gain_v2=626349397
audio.play_bitrate_non_metered_migrated=true
ui.minimize_to_tray=true
audio.play_bitrate_non_metered_enumeration=4
audio.equalizer.high_shelf_gain_v2=805306368
audio.allow_downgrade=false
audio.crossfade_v2=true
audio.equalizer.high_mid_peak_gain_v2=-178956971
audio.equalizer.low_shelf_gain_v2=680036488
connect.dial_devices="[]"
audio.equalizer_v2=true
connect.mdns_devices="[]"
audio.play_bitrate_enumeration=4
audio.equalizer.low_mid_peak_gain_v2=-89478485
audio.crossfade.time_v2=2300
app.update.enabled=false

The supporting script to lock the files as needed (replace %AppData% with the full path to the respective files):

@echo off
echo  Spotify Active Update Protection

:: --- add & lock prefs: app.update.enabled=false ---
attrib -R "%AppData%\Roaming\Spotify\prefs" >nul 2>&1
findstr /C:"app.update.enabled" "%AppData%\Roaming\Spotify\prefs" >nul 2>&1
if %ERRORLEVEL% NEQ 0 (
    echo app.update.enabled=false >> "%AppData%\Roaming\Spotify\prefs"
)
attrib +R "%AppData%\Roaming\Spotify\prefs"
echo locked

:: --- prefs.tmp locking ---
attrib -R "%AppData%\Roaming\Spotify\prefs.tmp" >nul 2>&1
if exist "%AppData%\Roaming\Spotify\prefs.tmp" (
    findstr /C:"app.update.enabled" "%AppData%\Roaming\Spotify\prefs.tmp" >nul 2>&1
    if %ERRORLEVEL% NEQ 0 (
        echo app.update.enabled=false >> "%AppData%\Roaming\Spotify\prefs.tmp"
    )
    attrib +R "%AppData%\Roaming\Spotify\prefs.tmp"
    echo prefs.tmp locked
)

:: --- Apps/: xpui/login not to be deleted / rewritten ---
:: Note: Spotify needs write access for xpui/
icacls "%AppData%\Roaming\Spotify\Apps" /deny "CINDERELLA\cinderella:(AD,DC)"
echo Apps/ dir locked. xpui and login can not be deleted / replaced

echo Now dance, Cinderella.
echo.
pause

And the counterpart:

@echo off
echo  Spotify Remove Update Protection

:: Kill all Deny-ACLs
icacls "%AppData$\Roaming\Spotify\Apps\xpui" /remove:d "CINDERELLA\cinderella" /T >nul 2>&1
icacls "%AppData%\Roaming\Spotify\Apps\login" /remove:d "CINDERELLA\cinderella" /T >nul 2>&1
icacls "%AppData%\Roaming\Spotify\Apps" /remove:d "CINDERELLA\cinderella" >nul 2>&1

:: enable r/w for prefs and prefs.tmp
attrib -R "%AppData%\Roaming\Spotify\prefs" >nul 2>&1
attrib -R "%AppData%\Roaming\Spotify\prefs.tmp" >nul 2>&1

echo Spotify is ready to be patched by Spicetify.
echo.
pause