fix: identify as Lunar Client and stop direct third-party image loads - #24
Merged
Merged
Conversation
- Send "Lunar Client (skycrypt-embed.lunarclient.com)" as the User-Agent on every outbound request: SkyCrypt API, CMS, image proxy, Sentry tunnel, card assets, takumi image fetches, and all server-side event.fetch calls via handleFetch. - Load player renders from skins.mcstats.com instead of nmsr.nickac.dev. - Serve SkyCrypt API textures through a same-origin /textures proxy so the browser never requests them from sky.shiiyu.moe with its own User-Agent; theme images on first-party hosts now go through /api/image-proxy. - Drop sky.shiiyu.moe and cupcake.shiiyu.moe from the CSP img/connect allowlists and cupcake.shiiyu.moe from the CSRF trusted origins.
inglettronald
approved these changes
Oct 2, 2026
colinmcdonald22
approved these changes
Oct 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Requests to third parties went out with the visitor's browser User-Agent (or a generic one), so upstreams couldn't identify traffic as coming from the embed. Player renders came from nmsr.nickac.dev, and item/head textures were loaded by the browser directly from sky.shiiyu.moe.
Solution
USER_AGENT(src/lib/shared/constants/user-agent.ts) isLunar Client (skycrypt-embed.lunarclient.com). It's sent by the SkyCrypt API and CMS clients, the image proxy, the Sentry tunnel, the card's asset and takumi image fetches, and every server-sideevent.fetchvia a newhandleFetchhook.fullbody?no=shadowbody/front?scale=2bust?y=-20bust?scale=2faceface?size=512customFetchrewrites absolute API URLs in responses to/textures/....src/routes/textures/[...path]fetches them from the API with our User-Agent. It passes through only images and the JSON from item/resolvelookups (rewriting itstextureURL too). It never sends the API token, and paths can't leave the API origin. The stats card points proxied paths back at the API before rendering, since takumi has no page origin./api/image-proxyinstead of loading directly.sky.shiiyu.moeandcupcake.shiiyu.moefromimg-src/connect-src, andcupcake.shiiyu.moefrom the CSRF trusted origins. Any direct load we missed will now be blocked and show up as a CSP violation instead of silently leaking the browser User-Agent.Notes for testing
<img>to/textures) still carry the browser User-Agent; browsers don't allow overriding it. What changes is that the third parties no longer receive it.img-src/connect-srcviolations while clicking through stats pages; those are loads the rewrite missed.Verification
pnpm check: 0 errorspnpm exec eslint .: cleanpnpm test: 447 passed, 14 skipped. Includes newtexture-proxy.spec.tsand the updated theme engine test./textures/api/item/FLAMEBREAKER_LEGGINGS/textures/api/head/<hash>.../resolve/textures/cache/rendered/...webp, then 200 WebP/textures/api/stats/<uuid>/textures//evil.example.com/x.pngoxfmt --check .flagsCLAUDE.md, which is already ondevand not touched here.