Skip to content

fix: identify as Lunar Client and stop direct third-party image loads - #24

Merged
imconnorngl merged 2 commits into
devfrom
fix/lunar-user-agent
Oct 2, 2026
Merged

imconnorngl merged 2 commits into
devfrom
fix/lunar-user-agent

Conversation

@imconnorngl

Copy link
Copy Markdown
Member

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

  • One User-Agent everywhere. USER_AGENT (src/lib/shared/constants/user-agent.ts) is Lunar 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-side event.fetch via a new handleFetch hook.
  • Player renders from skins.mcstats.com.
    nmsr mcstats
    fullbody?no=shadow body/front?scale=2
    bust?y=-20 bust?scale=2
    face face?size=512
  • Same-origin texture proxy. customFetch rewrites 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 /resolve lookups (rewriting its texture URL 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.
  • Theme images on sky/cupcake now go through /api/image-proxy instead of loading directly.
  • CSP/CSRF. Removed sky.shiiyu.moe and cupcake.shiiyu.moe from img-src/connect-src, and cupcake.shiiyu.moe from 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

  • Browser-originated requests to our own origin (page loads, <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.
  • Each texture is now one Worker request; upstream cache headers are passed through.
  • Watch the console for CSP img-src / connect-src violations while clicking through stats pages; those are loads the rewrite missed.

Verification

  • pnpm check: 0 errors
  • pnpm exec eslint .: clean
  • pnpm test: 447 passed, 14 skipped. Includes new texture-proxy.spec.ts and the updated theme engine test.
  • Dev server against the live API:
    Request Result
    /textures/api/item/FLAMEBREAKER_LEGGINGS 200 PNG
    /textures/api/head/<hash> 200 PNG
    .../resolve /textures/cache/rendered/...webp, then 200 WebP
    /textures/api/stats/<uuid> 502
    /textures//evil.example.com/x.png 404
  • Not yet tested: a full stats page end to end (the local API token is rejected by production).
  • oxfmt --check . flags CLAUDE.md, which is already on dev and not touched here.

- 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.
@imconnorngl
imconnorngl merged commit 305e738 into dev Oct 2, 2026
13 checks passed
@imconnorngl
imconnorngl deleted the fix/lunar-user-agent branch October 2, 2026 16:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants