Lunar Client Modrinth Explore Chain PoC
Proof package for a high-severity Lunar Client chain observed in the June 2026 unpacked Electron application.
This repository documents a practical RCE-style chain and includes a benign cross-platform calc-pop proof for the final "drop local launcher and open it via the OS shell" primitive.
Status
High-confidence critical candidate, not yet a fully packaged public Modrinth-to-Lunar end-to-end exploit.
The chain is severe because it joins several individually dangerous behaviors:
- Modrinth project content is rendered in Lunar Explore as raw HTML.
- The Explore renderer has access to privileged Lunar preload APIs.
- The renderer can reach raw IPC/Redux state synchronization into main.
- Main accepts forged profile state and materializes provider profiles.
- Modrinth override extraction can write root-level override files into a profile-controlled game directory.
- The unverified modpack warning path only scans
mods,resourcepacks, andshaderpacks, not root overrides. openExternalLinkcan reachshell.openExternalfor non-HTTP URLs when the initiator is not one of the two restricted user-input initiators.- Opening shell-dispatched local launcher files can execute code. On Windows,
the reviewed chain uses
.lnk; the included proof also has macOS/Linux branches for environments where Windows is unavailable.
If the live Modrinth delivery leg is confirmed end-to-end through Lunar's production cache, the likely impact is arbitrary code execution as the victim's desktop user after the victim views or clicks a malicious Modrinth project in Lunar Explore. This path does not require launching Minecraft, having a JRE ready, or having a selected Minecraft account.
Impact
Estimated severity: critical.
Tentative CVSS v3.1: AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H = 9.6.
This score assumes the attacker can publish or otherwise get attacker-controlled Modrinth project/changelog content rendered by Lunar Explore, and that the raw HTML delivery path executes script-capable content in the packaged renderer.
Chain Summary
- Attacker-controlled Modrinth Markdown is fetched by Lunar Explore.
- Lunar renders that Markdown through
ReactMarkdownwithrehypeRaw, without an observed sanitizer or HTML allowlist. - A raw HTML payload can execute renderer JavaScript through an iframe-style delivery primitive.
- Renderer JavaScript can use exposed preload APIs and raw IPC/Redux sync.
- The renderer forges or creates a Modrinth provider profile whose
overrides.gameDirectorypoints at a writable attacker-chosen directory. - The renderer asks main to install the forged Modrinth profile.
- Main downloads the
.mrpack, saves the profile, and extracts root-leveloverrides/*entries togetEffectiveGameDirectory(profile). - A root override such as a benign launcher file is written to the chosen directory and is not covered by the unverified-file warning scanner.
- The renderer calls the external-link API on a
file:///.../<launcher>URL using a non-restricted initiator. - Main reaches
shell.openExternal(url). The operating system dispatches the local launcher.
Evidence From Local Review
The following paths refer to extracted source-map sources from the reviewed Lunar Client build.
- Raw HTML Markdown sink:
src/renderer/impl/app/pages/explore/project/components/markdown.tsximportsReactMarkdown, usesrehypeRaw, and does not apply a sanitizer. - Modrinth content flow:
src/renderer/impl/app/pages/explore/project/utils/fetch.tsmaps projectbodyand versionchangeloginto renderer state. - Main install handler:
src/electron/module/modrinth/index.tsexposesinstallModpack. - Profile source of truth:
src/electron/module/modrinth/install/modpack/index.tsreads the target profile fromlauncherRedux.store.getState().profiles.profiles. - Profile reducer:
src/shared/store/profiles/index.tsacceptsprofiles/addOrUpdateProfileand inserts or replaces the supplied profile object. - Profile persistence:
src/electron/module/profiles/index.tspreservesprofile.overrideswhen saving a virtual profile. - Effective game directory:
src/electron/module/profiles/paths.tsreturnsprofile.overrides.gameDirectorywhen present. - Override extraction:
src/electron/module/profiles/handlers/extract-overrides/utils.tsmaps non-content-diroverrides/*entries to the effective game directory. - Warning coverage:
src/electron/module/profiles/handlers/unverified-modpack-files/consts.tsscans onlyoverrides/mods/,overrides/resourcepacks/, andoverrides/shaderpacks/. - External open sink:
src/electron/window/preload/impl/misc.tsblocks non-HTTP protocols only for selected initiators, then callsshell.openExternal(url). - Redux bridge:
src/electron/redux/index.tsenablesstateSyncEnhancer()with no observed application-level action allowlist.
Calc-Pop PoC
Run this only on a local test machine. It does not interact with Lunar Client or any live Modrinth project. It validates the final execution primitive by:
- writing a marker file,
- creating a local platform-appropriate launcher file,
- asking the OS shell to open that launcher, and
- popping the Calculator app where available.
npm run poc
Expected output:
marker: calc-pop-attempted
opened: <launcher path>
Platform behavior:
- Windows: creates and opens a
.lnkpointing tocalc.exe. - macOS: creates and opens a
.commandlauncher that runs Calculator. - Linux: creates and opens a
.desktoplauncher for the first available calculator binary from a small allowlist.
In the original audit environment, the Windows shortcut primitive also wrote
lnk-executed to a marker file when the shortcut was opened.
What Is Intentionally Not Included
This repository does not include:
- A live malicious Modrinth project.
- A weaponized iframe or renderer payload.
- A
.mrpackcontaining an executable launcher. - A script that drives Lunar Client against real users.
See poc/renderer-chain-skeleton.md for a non-executable outline of the
renderer-side chain.
Fix Guidance
Recommended fixes should be layered:
- Disable raw HTML in Modrinth Markdown, or sanitize with a strict allowlist.
- Forbid script-capable embedded content in Explore project descriptions and changelogs.
- Remove generic renderer access to raw IPC
sendMessage, or enforce a strict channel allowlist in preload. - Disable or constrain Electron Redux state sync from untrusted renderers.
- Validate profile objects at every IPC/main boundary.
- Do not accept arbitrary renderer-supplied
gameDirectorypaths without a real user gesture and path policy. - Treat every
overrides/*archive entry as potentially dangerous, including root-level files. - Block
file:,ms-*, and other non-web protocols inopenExternalLinkunless there is a narrow, explicit allowlist. - Refuse to open executable file types such as
.lnk,.exe,.bat,.cmd,.ps1,.vbs, and similar from renderer-controlled URLs.
Disclosure Note
This is intended for authorized validation and coordinated disclosure. Keep the repository private until the vendor has acknowledged and remediated the issue.