From 0005aead871fd59a01354d163f7a0f6d3c6c8142 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 20:38:07 +0000 Subject: [PATCH 01/13] dependency: Update actions/upload-artifact action to v7 --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 2ddce2cc5..03804d3ab 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -85,7 +85,7 @@ jobs: run: ./gradlew clean eternalcore-plugin:shadowJar - name: Upload built JAR - uses: actions/upload-artifact@v6.0.0 + uses: actions/upload-artifact@v7.0.0 id: upload-artifact with: name: EternalCore Dev Build From 4aae41f01c20e83e0f7608aa50df6aa99443b76b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20K=C4=99dziora?= <77227023+Jakubk15@users.noreply.github.com> Date: Sun, 1 Mar 2026 19:10:04 +0100 Subject: [PATCH 02/13] Update gradle.yml --- .github/workflows/gradle.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 03804d3ab..f1ce4d4ad 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -90,6 +90,7 @@ jobs: with: name: EternalCore Dev Build path: eternalcore-plugin/build/libs/EternalCore*.jar + archive: false retention-days: 14 overwrite: true From c015e651509e660af73e3f2a5ebcb158cb295bdc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 18:34:13 +0000 Subject: [PATCH 03/13] dependency: Update actions/checkout action to v7 --- .github/workflows/gradle.yml | 2 +- .github/workflows/hangar-publish.yml | 2 +- .github/workflows/modrinth-publish.yml | 2 +- .github/workflows/publish.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 966d72593..e6f8c1f01 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -62,7 +62,7 @@ jobs: echo "comment-id=${{ steps.find-comment.outputs.comment-id != '' && steps.find-comment.outputs.comment-id || steps.create-comment.outputs.comment-id }}" >> $GITHUB_OUTPUT - name: Checkout - uses: actions/checkout@v6.0.3 + uses: actions/checkout@v7.0.0 - name: Set up JDK ${{ matrix.java }} uses: actions/setup-java@v5.3.0 diff --git a/.github/workflows/hangar-publish.yml b/.github/workflows/hangar-publish.yml index a6a604e24..135e2007c 100644 --- a/.github/workflows/hangar-publish.yml +++ b/.github/workflows/hangar-publish.yml @@ -14,7 +14,7 @@ jobs: contents: read if: (github.event_name == 'push' && !contains(github.event.head_commit.message, '[ci skip]')) || github.event_name == 'release' steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 - name: Set up JDK 21 diff --git a/.github/workflows/modrinth-publish.yml b/.github/workflows/modrinth-publish.yml index 1473ec0ed..87291633e 100644 --- a/.github/workflows/modrinth-publish.yml +++ b/.github/workflows/modrinth-publish.yml @@ -14,7 +14,7 @@ jobs: contents: read if: (github.event_name == 'push' && !contains(github.event.head_commit.message, '[ci skip]')) || github.event_name == 'release' steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 - name: Set up JDK 21 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 15226140b..b27ee114f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,7 +9,7 @@ jobs: environment: deployment steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Set up JDK uses: actions/setup-java@v5 From a67c7fce94a46d8b1e38d45c74a32695168e278c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 18:34:19 +0000 Subject: [PATCH 04/13] dependency: Update gradle/actions action to v6 --- .github/workflows/hangar-publish.yml | 2 +- .github/workflows/modrinth-publish.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/hangar-publish.yml b/.github/workflows/hangar-publish.yml index a6a604e24..eb7029f6e 100644 --- a/.github/workflows/hangar-publish.yml +++ b/.github/workflows/hangar-publish.yml @@ -24,7 +24,7 @@ jobs: distribution: 'temurin' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@v6 - name: Grant execute permission for gradlew run: chmod +x gradlew diff --git a/.github/workflows/modrinth-publish.yml b/.github/workflows/modrinth-publish.yml index 1473ec0ed..9d56a8c4e 100644 --- a/.github/workflows/modrinth-publish.yml +++ b/.github/workflows/modrinth-publish.yml @@ -24,7 +24,7 @@ jobs: distribution: 'temurin' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@v6 - name: Grant execute permission for gradlew run: chmod +x gradlew From 95462b9824826df9d2771bc8af113dc5419ede9a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 18:34:24 +0000 Subject: [PATCH 05/13] dependency: Update hmarr/auto-approve-action action to v4 --- .github/workflows/autoapprove.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/autoapprove.yml b/.github/workflows/autoapprove.yml index 5ab0756fd..db246d3b9 100644 --- a/.github/workflows/autoapprove.yml +++ b/.github/workflows/autoapprove.yml @@ -9,6 +9,6 @@ jobs: pull-requests: write if: github.actor == 'renovate[bot]' steps: - - uses: hmarr/auto-approve-action@v3 + - uses: hmarr/auto-approve-action@v4.0.0 with: review-message: "" From 501256c9f11d44e1d396605b6efcc6e9cf0566d4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 27 Jun 2026 01:42:40 +0000 Subject: [PATCH 06/13] dependency: Update actions/cache action to v6 --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index db45e5bc5..a6ea98b8b 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -71,7 +71,7 @@ jobs: java-version: ${{ matrix.java }} - name: Cache Gradle - uses: actions/cache@v5.1.0 + uses: actions/cache@v6.1.0 with: path: ~/.gradle/caches key: >- From ebbd343b5832e685908c862fbb4855aa5301c682 Mon Sep 17 00:00:00 2001 From: Jakubk15 <77227023+Jakubk15@users.noreply.github.com> Date: Thu, 2 Jul 2026 14:54:30 +0200 Subject: [PATCH 07/13] harden: verify dependency integrity and add download timeouts Fixes two findings from the loader audit: 1. Runtime-downloaded dependencies were loaded with no integrity check. DependencyDownloader now verifies every artifact against the strongest checksum the repository publishes (sha512 > sha256 > sha1) before it is written to the local cache and added to the classpath. Fails closed: a mismatch or the absence of any published checksum rejects that repository so resolution falls back to another source (and errors if none can vouch for the artifact). Adds a pure, unit-tested Checksum utility. 3. Jar, checksum, and POM fetches had no connect/read timeouts, so a slow or half-open repository could stall plugin startup. All three now set a 15s connect / 30s read timeout. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01JrkLFxzmmn7BpB9y6vMTeg --- eternalcore-plugin/build.gradle.kts | 1 + .../core/loader/dependency/Checksum.java | 92 +++++++++++++++++++ .../core/loader/dependency/Dependency.java | 5 + .../dependency/DependencyDownloader.java | 59 +++++++++++- .../core/loader/pom/PomXmlScanner.java | 10 +- .../core/loader/dependency/ChecksumTest.java | 58 ++++++++++++ 6 files changed, 221 insertions(+), 4 deletions(-) create mode 100644 eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/Checksum.java create mode 100644 eternalcore-plugin/src/test/java/com/eternalcode/core/loader/dependency/ChecksumTest.java diff --git a/eternalcore-plugin/build.gradle.kts b/eternalcore-plugin/build.gradle.kts index e1991e995..74887e8b6 100644 --- a/eternalcore-plugin/build.gradle.kts +++ b/eternalcore-plugin/build.gradle.kts @@ -3,6 +3,7 @@ import org.gradle.jvm.toolchain.JavaToolchainService plugins { `eternalcode-java` + `eternalcode-java-test` `eternalcore-repositories` `eternalcore-shadow-compiler` `eternalcore-publish-plugin` diff --git a/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/Checksum.java b/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/Checksum.java new file mode 100644 index 000000000..f28e6aebe --- /dev/null +++ b/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/Checksum.java @@ -0,0 +1,92 @@ +package com.eternalcode.core.loader.dependency; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Verifies the integrity of downloaded artifacts against the checksum files published alongside them in a Maven + * repository (e.g. {@code artifact-1.0.jar.sha256}). + * + *

Ordered strongest-first so callers can prefer the most secure digest a repository publishes. SHA-1 is retained + * only as a last-resort fallback because it is the single digest Maven repositories are guaranteed to serve; it is + * cryptographically weak and should not be relied upon on its own. + */ +public enum Checksum { + + SHA512("sha512", "SHA-512"), + SHA256("sha256", "SHA-256"), + SHA1("sha1", "SHA-1"); + + private final String extension; + private final String algorithm; + + Checksum(String extension, String algorithm) { + this.extension = extension; + this.algorithm = algorithm; + } + + /** + * The file extension appended to the artifact name to locate this checksum (without a leading dot). + */ + public String extension() { + return this.extension; + } + + /** + * Computes the lower-case hex digest of the given data using this algorithm. + */ + public String hash(byte[] data) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance(this.algorithm); + } + catch (NoSuchAlgorithmException exception) { + throw new DependencyException("Missing digest algorithm: " + this.algorithm, exception); + } + + return toHex(digest.digest(data)); + } + + /** + * Returns {@code true} if the digest of {@code data} equals the published checksum. + * + *

Published checksum files sometimes contain trailing content such as {@code " "}; + * only the leading token is compared, and comparison is case-insensitive. + */ + public boolean matches(byte[] data, String publishedChecksum) { + if (publishedChecksum == null) { + return false; + } + + String expected = normalize(publishedChecksum); + if (expected.isEmpty()) { + return false; + } + + return expected.equalsIgnoreCase(this.hash(data)); + } + + static String normalize(String rawChecksum) { + String trimmed = rawChecksum.trim(); + + for (int index = 0; index < trimmed.length(); index++) { + if (Character.isWhitespace(trimmed.charAt(index))) { + return trimmed.substring(0, index); + } + } + + return trimmed; + } + + private static String toHex(byte[] bytes) { + StringBuilder builder = new StringBuilder(bytes.length * 2); + + for (byte value : bytes) { + builder.append(Character.forDigit((value >> 4) & 0xF, 16)); + builder.append(Character.forDigit(value & 0xF, 16)); + } + + return builder.toString(); + } + +} diff --git a/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/Dependency.java b/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/Dependency.java index 6e24c40fb..5fd1117b0 100644 --- a/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/Dependency.java +++ b/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/Dependency.java @@ -44,6 +44,11 @@ public ResourceLocator toMavenJar(Repository repository, String classifier) { return toResource(repository, JAR_MAVEN_FORMAT_WITH_CLASSIFIER.formatted(this.artifactId, this.version, classifier)); } + public ResourceLocator toMavenJarChecksum(Repository repository, String checksumExtension) { + String jarName = JAR_MAVEN_FORMAT.formatted(this.artifactId, this.version); + return toResource(repository, jarName + "." + checksumExtension); + } + public ResourceLocator toPomXml(Repository repository) { return toResource(repository, POM_XML_FORMAT.formatted(this.artifactId, this.version)); } diff --git a/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/DependencyDownloader.java b/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/DependencyDownloader.java index b279270d0..bd0f68044 100644 --- a/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/DependencyDownloader.java +++ b/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/DependencyDownloader.java @@ -8,6 +8,7 @@ import java.io.InputStream; import java.net.URISyntaxException; import java.net.URLConnection; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; @@ -18,6 +19,9 @@ public class DependencyDownloader { + private static final int CONNECT_TIMEOUT_MILLIS = 15_000; + private static final int READ_TIMEOUT_MILLIS = 30_000; + private final Logger logger; private final Repository localRepository; private final List repositories; @@ -70,6 +74,8 @@ private Path tryDownloadDependency(Dependency dependency) throws URISyntaxExcept private Path downloadJarAndSave(Repository repository, Dependency dependency, Path file) { try { byte[] bytes = this.downloadJar(repository, dependency); + this.verifyChecksum(repository, dependency, bytes); + Path parent = file.getParent(); Files.createDirectories(parent); @@ -86,9 +92,7 @@ private Path downloadJarAndSave(Repository repository, Dependency dependency, Pa } private byte[] downloadJar(Repository repository, Dependency dependency) throws IOException { - URLConnection connection = dependency.toMavenJar(repository).toURL().openConnection(); - - try (InputStream in = connection.getInputStream()) { + try (InputStream in = openStream(dependency.toMavenJar(repository).toURL().openConnection())) { byte[] bytes = ByteStreams.toByteArray(in); if (bytes.length == 0) { @@ -99,4 +103,53 @@ private byte[] downloadJar(Repository repository, Dependency dependency) throws } } + /** + * Verifies the downloaded artifact against the strongest checksum the repository publishes for it. Fails closed: + * a mismatch, or the total absence of any published checksum, rejects this repository so the caller can fall back + * to another one (and ultimately fail if none can vouch for the artifact). This prevents a tampered or corrupted + * jar from being written to the local cache and loaded into the JVM. + */ + private void verifyChecksum(Repository repository, Dependency dependency, byte[] jarBytes) { + for (Checksum checksum : Checksum.values()) { + String publishedChecksum = this.downloadChecksum(repository, dependency, checksum); + + if (publishedChecksum == null) { + continue; + } + + if (!checksum.matches(jarBytes, publishedChecksum)) { + throw new DependencyException( + "Checksum mismatch (" + checksum.extension() + ") for " + dependency + " from " + repository + + " - refusing to load a potentially tampered dependency"); + } + + return; + } + + throw new DependencyException( + "No published checksum (sha512/sha256/sha1) available to verify " + dependency + " from " + repository); + } + + private String downloadChecksum(Repository repository, Dependency dependency, Checksum checksum) { + try (InputStream in = openStream(dependency.toMavenJarChecksum(repository, checksum.extension()).toURL().openConnection())) { + byte[] bytes = ByteStreams.toByteArray(in); + + if (bytes.length == 0) { + return null; + } + + return new String(bytes, StandardCharsets.UTF_8); + } + catch (IOException exception) { + // Checksum not served by this repository; the caller treats this as "cannot verify here". + return null; + } + } + + private static InputStream openStream(URLConnection connection) throws IOException { + connection.setConnectTimeout(CONNECT_TIMEOUT_MILLIS); + connection.setReadTimeout(READ_TIMEOUT_MILLIS); + return connection.getInputStream(); + } + } diff --git a/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/pom/PomXmlScanner.java b/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/pom/PomXmlScanner.java index 16a752ee7..89adb3b9f 100644 --- a/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/pom/PomXmlScanner.java +++ b/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/pom/PomXmlScanner.java @@ -8,6 +8,7 @@ import java.io.InputStream; import java.net.URISyntaxException; import java.net.URL; +import java.net.URLConnection; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.ArrayList; @@ -25,6 +26,9 @@ public class PomXmlScanner implements DependencyScanner { + private static final int CONNECT_TIMEOUT_MILLIS = 15_000; + private static final int READ_TIMEOUT_MILLIS = 30_000; + private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); static { @@ -91,7 +95,11 @@ private File savePomXmlToLocalRepository(Dependency dependency, Repository repos URL url = dependency.toPomXml(repository).toURL(); - try (InputStream inputStream = url.openStream()) { + URLConnection connection = url.openConnection(); + connection.setConnectTimeout(CONNECT_TIMEOUT_MILLIS); + connection.setReadTimeout(READ_TIMEOUT_MILLIS); + + try (InputStream inputStream = connection.getInputStream()) { Files.createDirectories(localFile.toPath()); Files.copy(inputStream, localFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } diff --git a/eternalcore-plugin/src/test/java/com/eternalcode/core/loader/dependency/ChecksumTest.java b/eternalcore-plugin/src/test/java/com/eternalcode/core/loader/dependency/ChecksumTest.java new file mode 100644 index 000000000..a72dae99e --- /dev/null +++ b/eternalcore-plugin/src/test/java/com/eternalcode/core/loader/dependency/ChecksumTest.java @@ -0,0 +1,58 @@ +package com.eternalcode.core.loader.dependency; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; + +class ChecksumTest { + + // Known digests of the ASCII string "abc". + private static final byte[] ABC = "abc".getBytes(StandardCharsets.UTF_8); + private static final String ABC_SHA1 = "a9993e364706816aba3e25717850c26c9cd0d89d"; + private static final String ABC_SHA256 = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"; + private static final String ABC_SHA512 = + "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a" + + "2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"; + + @Test + void computesKnownDigests() { + assertEquals(ABC_SHA1, Checksum.SHA1.hash(ABC)); + assertEquals(ABC_SHA256, Checksum.SHA256.hash(ABC)); + assertEquals(ABC_SHA512, Checksum.SHA512.hash(ABC)); + } + + @Test + void matchesIgnoringCase() { + assertTrue(Checksum.SHA256.matches(ABC, ABC_SHA256)); + assertTrue(Checksum.SHA256.matches(ABC, ABC_SHA256.toUpperCase())); + } + + @Test + void rejectsMismatchedChecksum() { + assertFalse(Checksum.SHA256.matches(ABC, ABC_SHA1)); + assertFalse(Checksum.SHA256.matches(ABC, "not-a-hash")); + } + + @Test + void rejectsNullOrBlankChecksum() { + assertFalse(Checksum.SHA256.matches(ABC, null)); + assertFalse(Checksum.SHA256.matches(ABC, " ")); + } + + @Test + void ignoresTrailingFilenameInPublishedChecksum() { + // Maven repositories occasionally publish " ". + assertTrue(Checksum.SHA256.matches(ABC, ABC_SHA256 + " artifact-1.0.jar")); + assertEquals(ABC_SHA256, Checksum.normalize(ABC_SHA256 + " artifact-1.0.jar")); + } + + @Test + void exposesExpectedExtensions() { + assertEquals("sha512", Checksum.SHA512.extension()); + assertEquals("sha256", Checksum.SHA256.extension()); + assertEquals("sha1", Checksum.SHA1.extension()); + } +} From 60b141d77951e2adf3aa28d324c759323cc9c309 Mon Sep 17 00:00:00 2001 From: Jakubk15 <77227023+Jakubk15@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:11:44 +0200 Subject: [PATCH 08/13] security: require distinct permission to enchant another player's item MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both /enchant execute overloads were gated only by the class-level @Permission("eternalcore.enchant"), so anyone allowed to enchant their own item could also modify other players' held items. Follow the FlyCommand convention: drop the class-level permission and gate each overload individually — eternalcore.enchant for the self variant and eternalcore.enchant.other for the "" variant. Note: servers that previously granted eternalcore.enchant to allow enchanting others must now also grant eternalcore.enchant.other. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01JrkLFxzmmn7BpB9y6vMTeg --- .../com/eternalcode/core/feature/enchant/EnchantCommand.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/enchant/EnchantCommand.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/enchant/EnchantCommand.java index 02a72028e..6789c2c2b 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/enchant/EnchantCommand.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/enchant/EnchantCommand.java @@ -15,7 +15,6 @@ import org.bukkit.inventory.PlayerInventory; @Command(name = "enchant") -@Permission("eternalcore.enchant") class EnchantCommand { private final EnchantSettings enchantSettings; @@ -28,6 +27,7 @@ class EnchantCommand { } @Execute + @Permission("eternalcore.enchant") @DescriptionDocs(description = "Enchants item in hand", arguments = " ") void execute(@Sender Player player, @Arg Enchantment enchantment, @Arg(EnchantLevelArgument.KEY) int level) { PlayerInventory playerInventory = player.getInventory(); @@ -51,7 +51,8 @@ void execute(@Sender Player player, @Arg Enchantment enchantment, @Arg(EnchantLe } @Execute - @DescriptionDocs(description = "Enchants item in hand", arguments = " ") + @Permission("eternalcore.enchant.other") + @DescriptionDocs(description = "Enchants the item held by the specified player", arguments = " ") void execute(@Sender Player sender, @Arg Enchantment enchantment, @Arg(EnchantLevelArgument.KEY) int level, @Arg Player target) { PlayerInventory targetInventory = target.getInventory(); ItemStack handItem = targetInventory.getItem(targetInventory.getHeldItemSlot()); From 140f2831c4b6e3221c3713a03ccc55822d97e7c2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2026 11:07:20 +0000 Subject: [PATCH 09/13] dependency: Update dependency fr.skytasul:glowingentities to v2 --- buildSrc/src/main/kotlin/Versions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index f22bc07d7..81e57fc55 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -40,7 +40,7 @@ object Versions { const val CAFFEINE = "3.2.4" - const val GLOWING_ENTITIES = "1.4.11" + const val GLOWING_ENTITIES = "2.0.0" const val SPOTIFY_COMPLETABLE_FUTURES = "0.3.6" From 1c39b6818a01a02817407063190c451956af90e7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2026 13:04:13 +0000 Subject: [PATCH 10/13] dependency: Update dependency com.gradleup.shadow:shadow-gradle-plugin to v9.5.1 --- buildSrc/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index e9b6a4902..9edfa9522 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -7,7 +7,7 @@ repositories { } dependencies { - implementation("com.gradleup.shadow:shadow-gradle-plugin:9.4.3") + implementation("com.gradleup.shadow:shadow-gradle-plugin:9.5.1") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.4.0") implementation("net.minecrell:plugin-yml:0.6.0") implementation("com.modrinth.minotaur:Minotaur:2.9.0") From b89d9b0130aedab0ab60a420ee291dbe24399ba7 Mon Sep 17 00:00:00 2001 From: Jakubk15 <77227023+Jakubk15@users.noreply.github.com> Date: Wed, 8 Jul 2026 16:17:16 +0200 Subject: [PATCH 11/13] 26.2 in runServer --- eternalcore-plugin/build.gradle.kts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/eternalcore-plugin/build.gradle.kts b/eternalcore-plugin/build.gradle.kts index 74887e8b6..7d80d2812 100644 --- a/eternalcore-plugin/build.gradle.kts +++ b/eternalcore-plugin/build.gradle.kts @@ -1,6 +1,3 @@ -import org.gradle.jvm.toolchain.JavaLanguageVersion -import org.gradle.jvm.toolchain.JavaToolchainService - plugins { `eternalcode-java` `eternalcode-java-test` @@ -52,7 +49,7 @@ tasks { languageVersion.set(JavaLanguageVersion.of(25)) }) - minecraftVersion("26.1.2") + minecraftVersion("26.2") downloadPlugins.modrinth("luckperms", "v${Versions.LUCKPERMS}-bukkit") } } From c6fab54bae67d2a7d232d1921be0bf15a16a3c92 Mon Sep 17 00:00:00 2001 From: Jakubk15 <77227023+Jakubk15@users.noreply.github.com> Date: Thu, 9 Jul 2026 13:29:38 +0200 Subject: [PATCH 12/13] Revert "harden: verify dependency integrity and add download timeouts" This reverts commit ebbd343b5832e685908c862fbb4855aa5301c682. --- eternalcore-plugin/build.gradle.kts | 1 - .../core/loader/dependency/Checksum.java | 92 ------------------- .../core/loader/dependency/Dependency.java | 5 - .../dependency/DependencyDownloader.java | 59 +----------- .../core/loader/pom/PomXmlScanner.java | 10 +- .../core/loader/dependency/ChecksumTest.java | 58 ------------ 6 files changed, 4 insertions(+), 221 deletions(-) delete mode 100644 eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/Checksum.java delete mode 100644 eternalcore-plugin/src/test/java/com/eternalcode/core/loader/dependency/ChecksumTest.java diff --git a/eternalcore-plugin/build.gradle.kts b/eternalcore-plugin/build.gradle.kts index 7d80d2812..1e8557bf2 100644 --- a/eternalcore-plugin/build.gradle.kts +++ b/eternalcore-plugin/build.gradle.kts @@ -1,6 +1,5 @@ plugins { `eternalcode-java` - `eternalcode-java-test` `eternalcore-repositories` `eternalcore-shadow-compiler` `eternalcore-publish-plugin` diff --git a/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/Checksum.java b/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/Checksum.java deleted file mode 100644 index f28e6aebe..000000000 --- a/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/Checksum.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.eternalcode.core.loader.dependency; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -/** - * Verifies the integrity of downloaded artifacts against the checksum files published alongside them in a Maven - * repository (e.g. {@code artifact-1.0.jar.sha256}). - * - *

Ordered strongest-first so callers can prefer the most secure digest a repository publishes. SHA-1 is retained - * only as a last-resort fallback because it is the single digest Maven repositories are guaranteed to serve; it is - * cryptographically weak and should not be relied upon on its own. - */ -public enum Checksum { - - SHA512("sha512", "SHA-512"), - SHA256("sha256", "SHA-256"), - SHA1("sha1", "SHA-1"); - - private final String extension; - private final String algorithm; - - Checksum(String extension, String algorithm) { - this.extension = extension; - this.algorithm = algorithm; - } - - /** - * The file extension appended to the artifact name to locate this checksum (without a leading dot). - */ - public String extension() { - return this.extension; - } - - /** - * Computes the lower-case hex digest of the given data using this algorithm. - */ - public String hash(byte[] data) { - MessageDigest digest; - try { - digest = MessageDigest.getInstance(this.algorithm); - } - catch (NoSuchAlgorithmException exception) { - throw new DependencyException("Missing digest algorithm: " + this.algorithm, exception); - } - - return toHex(digest.digest(data)); - } - - /** - * Returns {@code true} if the digest of {@code data} equals the published checksum. - * - *

Published checksum files sometimes contain trailing content such as {@code " "}; - * only the leading token is compared, and comparison is case-insensitive. - */ - public boolean matches(byte[] data, String publishedChecksum) { - if (publishedChecksum == null) { - return false; - } - - String expected = normalize(publishedChecksum); - if (expected.isEmpty()) { - return false; - } - - return expected.equalsIgnoreCase(this.hash(data)); - } - - static String normalize(String rawChecksum) { - String trimmed = rawChecksum.trim(); - - for (int index = 0; index < trimmed.length(); index++) { - if (Character.isWhitespace(trimmed.charAt(index))) { - return trimmed.substring(0, index); - } - } - - return trimmed; - } - - private static String toHex(byte[] bytes) { - StringBuilder builder = new StringBuilder(bytes.length * 2); - - for (byte value : bytes) { - builder.append(Character.forDigit((value >> 4) & 0xF, 16)); - builder.append(Character.forDigit(value & 0xF, 16)); - } - - return builder.toString(); - } - -} diff --git a/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/Dependency.java b/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/Dependency.java index 5fd1117b0..6e24c40fb 100644 --- a/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/Dependency.java +++ b/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/Dependency.java @@ -44,11 +44,6 @@ public ResourceLocator toMavenJar(Repository repository, String classifier) { return toResource(repository, JAR_MAVEN_FORMAT_WITH_CLASSIFIER.formatted(this.artifactId, this.version, classifier)); } - public ResourceLocator toMavenJarChecksum(Repository repository, String checksumExtension) { - String jarName = JAR_MAVEN_FORMAT.formatted(this.artifactId, this.version); - return toResource(repository, jarName + "." + checksumExtension); - } - public ResourceLocator toPomXml(Repository repository) { return toResource(repository, POM_XML_FORMAT.formatted(this.artifactId, this.version)); } diff --git a/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/DependencyDownloader.java b/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/DependencyDownloader.java index bd0f68044..b279270d0 100644 --- a/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/DependencyDownloader.java +++ b/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/dependency/DependencyDownloader.java @@ -8,7 +8,6 @@ import java.io.InputStream; import java.net.URISyntaxException; import java.net.URLConnection; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; @@ -19,9 +18,6 @@ public class DependencyDownloader { - private static final int CONNECT_TIMEOUT_MILLIS = 15_000; - private static final int READ_TIMEOUT_MILLIS = 30_000; - private final Logger logger; private final Repository localRepository; private final List repositories; @@ -74,8 +70,6 @@ private Path tryDownloadDependency(Dependency dependency) throws URISyntaxExcept private Path downloadJarAndSave(Repository repository, Dependency dependency, Path file) { try { byte[] bytes = this.downloadJar(repository, dependency); - this.verifyChecksum(repository, dependency, bytes); - Path parent = file.getParent(); Files.createDirectories(parent); @@ -92,7 +86,9 @@ private Path downloadJarAndSave(Repository repository, Dependency dependency, Pa } private byte[] downloadJar(Repository repository, Dependency dependency) throws IOException { - try (InputStream in = openStream(dependency.toMavenJar(repository).toURL().openConnection())) { + URLConnection connection = dependency.toMavenJar(repository).toURL().openConnection(); + + try (InputStream in = connection.getInputStream()) { byte[] bytes = ByteStreams.toByteArray(in); if (bytes.length == 0) { @@ -103,53 +99,4 @@ private byte[] downloadJar(Repository repository, Dependency dependency) throws } } - /** - * Verifies the downloaded artifact against the strongest checksum the repository publishes for it. Fails closed: - * a mismatch, or the total absence of any published checksum, rejects this repository so the caller can fall back - * to another one (and ultimately fail if none can vouch for the artifact). This prevents a tampered or corrupted - * jar from being written to the local cache and loaded into the JVM. - */ - private void verifyChecksum(Repository repository, Dependency dependency, byte[] jarBytes) { - for (Checksum checksum : Checksum.values()) { - String publishedChecksum = this.downloadChecksum(repository, dependency, checksum); - - if (publishedChecksum == null) { - continue; - } - - if (!checksum.matches(jarBytes, publishedChecksum)) { - throw new DependencyException( - "Checksum mismatch (" + checksum.extension() + ") for " + dependency + " from " + repository - + " - refusing to load a potentially tampered dependency"); - } - - return; - } - - throw new DependencyException( - "No published checksum (sha512/sha256/sha1) available to verify " + dependency + " from " + repository); - } - - private String downloadChecksum(Repository repository, Dependency dependency, Checksum checksum) { - try (InputStream in = openStream(dependency.toMavenJarChecksum(repository, checksum.extension()).toURL().openConnection())) { - byte[] bytes = ByteStreams.toByteArray(in); - - if (bytes.length == 0) { - return null; - } - - return new String(bytes, StandardCharsets.UTF_8); - } - catch (IOException exception) { - // Checksum not served by this repository; the caller treats this as "cannot verify here". - return null; - } - } - - private static InputStream openStream(URLConnection connection) throws IOException { - connection.setConnectTimeout(CONNECT_TIMEOUT_MILLIS); - connection.setReadTimeout(READ_TIMEOUT_MILLIS); - return connection.getInputStream(); - } - } diff --git a/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/pom/PomXmlScanner.java b/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/pom/PomXmlScanner.java index 89adb3b9f..16a752ee7 100644 --- a/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/pom/PomXmlScanner.java +++ b/eternalcore-plugin/src/main/java/com/eternalcode/core/loader/pom/PomXmlScanner.java @@ -8,7 +8,6 @@ import java.io.InputStream; import java.net.URISyntaxException; import java.net.URL; -import java.net.URLConnection; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.ArrayList; @@ -26,9 +25,6 @@ public class PomXmlScanner implements DependencyScanner { - private static final int CONNECT_TIMEOUT_MILLIS = 15_000; - private static final int READ_TIMEOUT_MILLIS = 30_000; - private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); static { @@ -95,11 +91,7 @@ private File savePomXmlToLocalRepository(Dependency dependency, Repository repos URL url = dependency.toPomXml(repository).toURL(); - URLConnection connection = url.openConnection(); - connection.setConnectTimeout(CONNECT_TIMEOUT_MILLIS); - connection.setReadTimeout(READ_TIMEOUT_MILLIS); - - try (InputStream inputStream = connection.getInputStream()) { + try (InputStream inputStream = url.openStream()) { Files.createDirectories(localFile.toPath()); Files.copy(inputStream, localFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } diff --git a/eternalcore-plugin/src/test/java/com/eternalcode/core/loader/dependency/ChecksumTest.java b/eternalcore-plugin/src/test/java/com/eternalcode/core/loader/dependency/ChecksumTest.java deleted file mode 100644 index a72dae99e..000000000 --- a/eternalcore-plugin/src/test/java/com/eternalcode/core/loader/dependency/ChecksumTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.eternalcode.core.loader.dependency; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.nio.charset.StandardCharsets; -import org.junit.jupiter.api.Test; - -class ChecksumTest { - - // Known digests of the ASCII string "abc". - private static final byte[] ABC = "abc".getBytes(StandardCharsets.UTF_8); - private static final String ABC_SHA1 = "a9993e364706816aba3e25717850c26c9cd0d89d"; - private static final String ABC_SHA256 = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"; - private static final String ABC_SHA512 = - "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a" - + "2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"; - - @Test - void computesKnownDigests() { - assertEquals(ABC_SHA1, Checksum.SHA1.hash(ABC)); - assertEquals(ABC_SHA256, Checksum.SHA256.hash(ABC)); - assertEquals(ABC_SHA512, Checksum.SHA512.hash(ABC)); - } - - @Test - void matchesIgnoringCase() { - assertTrue(Checksum.SHA256.matches(ABC, ABC_SHA256)); - assertTrue(Checksum.SHA256.matches(ABC, ABC_SHA256.toUpperCase())); - } - - @Test - void rejectsMismatchedChecksum() { - assertFalse(Checksum.SHA256.matches(ABC, ABC_SHA1)); - assertFalse(Checksum.SHA256.matches(ABC, "not-a-hash")); - } - - @Test - void rejectsNullOrBlankChecksum() { - assertFalse(Checksum.SHA256.matches(ABC, null)); - assertFalse(Checksum.SHA256.matches(ABC, " ")); - } - - @Test - void ignoresTrailingFilenameInPublishedChecksum() { - // Maven repositories occasionally publish " ". - assertTrue(Checksum.SHA256.matches(ABC, ABC_SHA256 + " artifact-1.0.jar")); - assertEquals(ABC_SHA256, Checksum.normalize(ABC_SHA256 + " artifact-1.0.jar")); - } - - @Test - void exposesExpectedExtensions() { - assertEquals("sha512", Checksum.SHA512.extension()); - assertEquals("sha256", Checksum.SHA256.extension()); - assertEquals("sha1", Checksum.SHA1.extension()); - } -} From 9f7f1f08b74fe4f013fab3da895a517f358bd396 Mon Sep 17 00:00:00 2001 From: Jakubk15 <77227023+Jakubk15@users.noreply.github.com> Date: Thu, 9 Jul 2026 13:31:26 +0200 Subject: [PATCH 13/13] Add 26.2 in paperVersions property --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 654a99224..895049b7a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,4 +6,4 @@ org.gradle.configuration-cache=true org.gradle.configuration-cache.parallel=true # Platforms -paperVersion=1.19.3,1.19.4,1.20,1.20.1,1.20.2,1.20.3,1.20.4,1.20.5,1.20.6,1.21,1.21.1,1.21.2,1.21.3,1.21.4,1.21.5,1.21.6,1.21.7,1.21.8,1.21.9,1.21.10,1.21.11,26.1,26.1.1,26.1.2 +paperVersion=1.19.3,1.19.4,1.20,1.20.1,1.20.2,1.20.3,1.20.4,1.20.5,1.20.6,1.21,1.21.1,1.21.2,1.21.3,1.21.4,1.21.5,1.21.6,1.21.7,1.21.8,1.21.9,1.21.10,1.21.11,26.1,26.1.1,26.1.2,26.2