Summary
The wallpaper renderer trusts locally stored StoredWallpaper data and wallpaper_change event payloads without verifying that a server-backed wallpaper was successfully activated.
A locally supplied synthetic wallpaper can therefore be rendered without a successful response from:
This report concerns the client-side trust boundary only. I have not confirmed a server-side ownership or coin bypass, and I did not download or redistribute paid assets.
Affected files
src/layouts/setting/tabs/wallpapers/hooks/use-wallpaper.tsx
src/layouts/setting/tabs/wallpapers/hooks/use-wallpaper-apply.tsx
src/context/wallpaper.context.tsx
background/wallpaper-cache.ts
Observed behavior
useWallpaperApply() reads the local wallpaper value and passes it directly to applyWallpaper().
The same renderer accepts wallpaper_change event payloads containing:
{
id: string
type: "IMAGE" | "VIDEO" | "GRADIENT"
src: string
}
The rendering path does not distinguish between:
- A custom local wallpaper
- A free server wallpaper
- An owned paid wallpaper
- An unconfirmed server wallpaper
- Arbitrary locally supplied wallpaper state
It does not check authentication, ownership, entitlement, or whether PUT /wallpapers/@me succeeded.
Additionally, when wallpaper.isOwned === true, handleSelectBackground() updates the local wallpaper before the activation request completes. If the request fails, the previous wallpaper is not restored.
Safe reproduction
I reproduced the behavior using a generated synthetic image/video with a fake ID:
- Back up the existing local
wallpaper value.
- Store a synthetic
StoredWallpaper, or dispatch it using wallpaper_change.
- Observe that the renderer immediately applies it.
- Confirm that no
PUT /wallpapers/@me request occurred.
- Reload offline and observe that the locally stored wallpaper is applied.
- Restore the original wallpaper.
No paid wallpaper URL or protected asset is required for this reproduction.
I can provide the screen-capture PoC privately to a maintainer.
Expected behavior
Server-backed paid wallpapers should only become persistent active state after successful server confirmation.
If an activation request fails, optimistic local state should be reverted to the previously authorized wallpaper.
Local custom wallpapers should also be explicitly distinguished from server-backed wallpapers.
Actual behavior
Locally supplied StoredWallpaper data is applied directly. The renderer does not know whether the wallpaper was:
- Authorized by the server
- Purchased
- Owned
- A temporary preview
- Locally created
Security classification
This is currently a confirmed client-side/local-state trust issue—not a confirmed server-side payment bypass.
Because browser-extension storage is controlled by the local user, the direct security severity may be low unless full-quality paid asset URLs are exposed through another weakness.
A server-side authorization vulnerability would require separate evidence that an unowned account can make PUT /wallpapers/@me persist a paid wallpaper without a legitimate purchase. I have not demonstrated or claimed that behavior.
Suggested remediation
- Apply server-backed wallpapers only after
PUT /wallpapers/@me succeeds.
- Restore the previous wallpaper when activation fails.
- Separate local custom wallpaper state from server-entitled wallpaper state.
- Never treat client-side
coin or isOwned values as authorization controls.
- Consider storing only the wallpaper ID for server-backed selections.
- Resolve entitled wallpaper data through account synchronization.
- Mark preview state explicitly and never persist it as an owned wallpaper.
- Validate or restrict remote URLs accepted by the offline wallpaper cache.
Environment
- Repository:
widgetify-app/widgetify-extension
- Audited commit:
8958250eec27bebf07c02e13448211879f69b444
- Testing performed only with my own account and synthetic local assets.
Summary
The wallpaper renderer trusts locally stored
StoredWallpaperdata andwallpaper_changeevent payloads without verifying that a server-backed wallpaper was successfully activated.A locally supplied synthetic wallpaper can therefore be rendered without a successful response from:
This report concerns the client-side trust boundary only. I have not confirmed a server-side ownership or coin bypass, and I did not download or redistribute paid assets.
Affected files
src/layouts/setting/tabs/wallpapers/hooks/use-wallpaper.tsxsrc/layouts/setting/tabs/wallpapers/hooks/use-wallpaper-apply.tsxsrc/context/wallpaper.context.tsxbackground/wallpaper-cache.tsObserved behavior
useWallpaperApply()reads the localwallpapervalue and passes it directly toapplyWallpaper().The same renderer accepts
wallpaper_changeevent payloads containing:The rendering path does not distinguish between:
It does not check authentication, ownership, entitlement, or whether
PUT /wallpapers/@mesucceeded.Additionally, when
wallpaper.isOwned === true,handleSelectBackground()updates the local wallpaper before the activation request completes. If the request fails, the previous wallpaper is not restored.Safe reproduction
I reproduced the behavior using a generated synthetic image/video with a fake ID:
wallpapervalue.StoredWallpaper, or dispatch it usingwallpaper_change.PUT /wallpapers/@merequest occurred.No paid wallpaper URL or protected asset is required for this reproduction.
I can provide the screen-capture PoC privately to a maintainer.
Expected behavior
Server-backed paid wallpapers should only become persistent active state after successful server confirmation.
If an activation request fails, optimistic local state should be reverted to the previously authorized wallpaper.
Local custom wallpapers should also be explicitly distinguished from server-backed wallpapers.
Actual behavior
Locally supplied
StoredWallpaperdata is applied directly. The renderer does not know whether the wallpaper was:Security classification
This is currently a confirmed client-side/local-state trust issue—not a confirmed server-side payment bypass.
Because browser-extension storage is controlled by the local user, the direct security severity may be low unless full-quality paid asset URLs are exposed through another weakness.
A server-side authorization vulnerability would require separate evidence that an unowned account can make
PUT /wallpapers/@mepersist a paid wallpaper without a legitimate purchase. I have not demonstrated or claimed that behavior.Suggested remediation
PUT /wallpapers/@mesucceeds.coinorisOwnedvalues as authorization controls.Environment
widgetify-app/widgetify-extension8958250eec27bebf07c02e13448211879f69b444