Skip to content

Security: KarolexDev/CivPortal

Security

SECURITY.md

CivPortal — security review

Scope: the whole mod as of v1.2.0 (2026-08-07), every line under src/, plus build.gradle. Question asked: can this mod install malware, or be made to?


The short answer

No, and not through any path that exists in the code.

The concern is a reasonable one to raise: the mod talks to a website and downloads files. But a download is only dangerous if something later runs it, and this mod has no way to run anything. The following were searched for across the entire source tree and are completely absent — not "used carefully", not "guarded", but zero occurrences:

Absent Why it would matter
Runtime.exec, ProcessBuilder Launching a program
System.load, System.loadLibrary Loading a native .dll/.so
URLClassLoader, defineClass, setAccessible Loading Java code at runtime
ScriptEngine / Nashorn Running a script
ZipInputStream, ZipFile, JarFile, newFileSystem Unpacking an archive (so "zip-slip" cannot apply)
Desktop.browse, Util.getPlatform().openUri/openFile Handing a file or link to the operating system
ClickEvent, HoverEvent Clickable chat that runs a command or opens a URL
ObjectInputStream, readObject Java deserialization, a classic remote-code-execution sink
sendChatMessage, networkHandler.send Saying or doing anything on the Minecraft server on your behalf

The mod never opens anything with the operating system, never launches a browser, never asks Windows to do anything with a file, and never sends a chat message or command to the game server.

There is also no update mechanism: the mod downloads no code, no jars, no plugins, and no configuration at startup. What is in the jar you installed is all there will ever be.


What the mod actually does

It is a client-only mod ("environment": "client" in fabric.mod.json). It makes HTTPS requests to one website — the portal address you type in — and it:

  • reads JSON and draws it on screen (tasks, ranks, news, map markers),
  • writes three small JSON files of its own in config/civportal/,
  • appends waypoints to Xaero's waypoints.txt, if you have Xaero installed,
  • downloads one kind of file: a schematic, into Litematica's schematics folder, when you press a button on a build task.

That last one is the only place a file arrives from the internet, so it gets its own section.

The trust boundary, stated plainly

You choose which portal to point the mod at. That portal is trusted with the data it sends you, in the same way a website is trusted with the page it serves your browser. This review is about what a portal could do if it turned hostile — not about whether it can be one.


The schematic download, in detail

This is the part worth actually reading.

Where it can be saved. The portal supplies a filename. Before it touches the disk it goes through this (SchematicRenderer.java):

String cleaned = raw.replaceAll("[^A-Za-z0-9._-]", "_");
while (cleaned.startsWith(".")) cleaned = cleaned.substring(1);
return cleaned.isBlank() ? "schematic.litematic" : cleaned;

Everything outside letters, digits, dot, underscore and hyphen becomes an underscore. That defeats the whole standard list at once: ../../ (slashes replaced, leading dots stripped), C:\Windows\ (colon and backslashes replaced), file.txt:hidden (alternate data streams), embedded null bytes, and Unicode look-alikes. The name cannot escape the folder it is written to, which is fixed at Litematica's own schematics directory. A portal cannot choose where a file lands.

What happens to it. Nothing, until you press "Create Schematic Placement". Then the path is handed to Litematica, which parses it. This mod contains no NBT parser, no schematic parser and no decompression code of its own.

What it cannot become. A .jar written into the schematics folder is an inert file. Minecraft loads mods from mods/ at startup and never rescans; nothing in this mod writes there, and nothing in this mod or in Minecraft executes a file found in a schematics folder.

Your token is deliberately not sent with it. The download request is built from scratch rather than through the normal request builder, specifically so the Authorization header does not travel to a storage host (HttpPortalClient.java:486-496). That is the correct design and it is worth knowing it was done on purpose.


One thing that was checked carefully and turned out to be fine

The obvious attack, and the first thing worth suspecting: the portal replies to an authenticated request with a redirect to evil.example, and your login token gets forwarded there.

The mod does follow redirects (followRedirects(NORMAL)) and does attach the token as a header, so on the face of it this looks live. It is not. Java's HTTP client strips sensitive headers when a redirect changes host:

// HttpRequestImpl.newInstanceForRedirection
if (uri.getScheme().equalsIgnoreCase(other.uri.getScheme()) &&
        uri.getRawAuthority().equals(other.uri.getRawAuthority())) {
    return new HttpRequestImpl(uri, method, other, mayHaveBody, Optional.empty());
}
return new HttpRequestImpl(uri, method, other, mayHaveBody, Optional.of(Utils.ALLOWED_REDIRECT_HEADERS));

…where the filtered set is {Authorization, Cookie, Origin, Referer, Host}.

Verified two ways rather than taken on trust: read from the JDK 25 sources, and disassembled out of a JDK 21.0.9 runtime, because Minecraft 1.21.11 runs on Java 21 and the two could have differed. Both behave identically. No change needed.


Findings worth fixing

None of these is a way to run code on your machine. They are robustness problems: what a portal that turned hostile could do to annoy you. Listed most to least worth doing.

# What Effect if a portal turned hostile Fix
1 JSON responses are read into memory with no size limit (HttpPortalClient.java:673) A huge reply crashes the game with an out-of-memory error Cap the response body
2 The 32 MB download limit is checked after the file is fully written (HttpPortalClient.java:507-521) Fills the disk, bounded only by a 2-minute timeout Check Content-Length, and stop writing at the cap
3 The "must be HTTPS" rule runs when you type an address, but not when the config file is read (PortalConfig.java:229-285) A shared or hand-edited config.json set to http:// sends your token in the clear. Plausible if a nation hands out a preconfigured config Validate on load too
4 The download URL may point at any host, and is not required to be HTTPS (HttpPortalClient.java:485-499) The portal can make your client fetch from arbitrary addresses, including ones on your home network Require HTTPS; restrict to the portal's own host
5 The filename's extension is not restricted, and Litematica picks its parser by extension The portal chooses which of Litematica's four format parsers reads its bytes Allow only .litematic, .schem, .nbt, .schematic
6 Downloads overwrite by default (REPLACE_EXISTING) A portal can replace files inside its own schematics subfolder Refuse to overwrite, or verify a hash
7 Xaero folder names strip Windows-illegal characters but not . (XaeroWaypoints.java:128-138) A world or server named .. writes one directory higher. You would have to do this to yourself Reject . and .. outright
8 If setting file permissions fails, the token is written with inherited ones (TokenStore.java:126-136) On a shared computer the token could be readable by another account Fail loudly instead
9 The log records the token's first 4 characters and its exact length (CivPortalSession.java:385) A fingerprint, not the secret — but logs get pasted into bug reports Log neither
10 Portal text is not stripped of § formatting codes before display Cosmetic vandalism in chat and toasts Strip them on the way in
11 fabric-loom 1.17-SNAPSHOT, and no Gradle dependency verification (build.gradle:2) A build-time supply-chain concern, not a runtime one Pin the version; add a verification file

No code was changed as part of this review. These are recorded for you to decide on.


What a malicious portal can and cannot do

Can:

  • show you false information — fake tasks, fake ranks, fake news;
  • crash your game with an oversized reply (#1) or fill your disk (#2);
  • write a file with a name of its choosing into Litematica's schematics folder — where it sits inert unless you ask Litematica to load it;
  • make your client issue web requests to addresses it picks (#4);
  • see the account you linked, and anything the mod sends it — which is your inventory ledger only if you switched that on, per container.

Cannot:

  • run a program, script, or any code on your computer;
  • write a file anywhere except its own subfolder of Litematica's schematics directory and the mod's own config folder;
  • install a mod, replace your jar, or change anything in mods/;
  • read your files, your other mods' data, or your Minecraft password;
  • see where you are — the mod never transmits your position;
  • read or modify your own waypoints;
  • type in chat, run a command, or act as you on the Minecraft server;
  • get your token forwarded to a third-party host by redirect (checked above).

How this was checked

Static review of all ~13,000 lines under src/, plus targeted greps for every dangerous primitive listed in the first table, plus reading the relevant JDK sources and disassembling the JDK 21 runtime for the redirect question. The network and parsing layer contains no Minecraft types by design, so it can also be exercised headlessly against tools/MockPortalServer.java — which is how the download and parsing paths were run.

Anyone can repeat the first table's checks in one command:

grep -rE "Runtime\.getRuntime|ProcessBuilder|System\.load|ScriptEngine|URLClassLoader|defineClass|Desktop\.|openUri|openFile|ClickEvent|ZipInputStream|ObjectInputStream" src --include=*.java

It returns nothing.

There aren't any published security advisories