Skip to content
Merged

26.2 #1391

Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0005aea
dependency: Update actions/upload-artifact action to v7
renovate[bot] Feb 26, 2026
4aae41f
Update gradle.yml
Jakubk15 Mar 1, 2026
c015e65
dependency: Update actions/checkout action to v7
renovate[bot] Jun 18, 2026
a67c7fc
dependency: Update gradle/actions action to v6
renovate[bot] Jun 18, 2026
95462b9
dependency: Update hmarr/auto-approve-action action to v4
renovate[bot] Jun 18, 2026
501256c
dependency: Update actions/cache action to v6
renovate[bot] Jun 27, 2026
ebbd343
harden: verify dependency integrity and add download timeouts
Jakubk15 Jul 2, 2026
60b141d
security: require distinct permission to enchant another player's item
Jakubk15 Jul 2, 2026
140f283
dependency: Update dependency fr.skytasul:glowingentities to v2
renovate[bot] Jul 8, 2026
1c39b68
dependency: Update dependency com.gradleup.shadow:shadow-gradle-plugi…
renovate[bot] Jul 8, 2026
ca0b6e3
Merge remote-tracking branch 'origin/renovate/com.gradleup.shadow-sha…
Jakubk15 Jul 8, 2026
b02e76b
Merge remote-tracking branch 'origin/renovate/actions-checkout-7.x' i…
Jakubk15 Jul 8, 2026
50b004e
Merge remote-tracking branch 'origin/renovate/actions-cache-6.x' into…
Jakubk15 Jul 8, 2026
d5f13fc
Merge remote-tracking branch 'origin/renovate/hmarr-auto-approve-acti…
Jakubk15 Jul 8, 2026
3a523b6
Merge remote-tracking branch 'origin/renovate/major-7-github-artifact…
Jakubk15 Jul 8, 2026
863dfe5
Merge remote-tracking branch 'origin/renovate/gradle-actions-6.x' int…
Jakubk15 Jul 8, 2026
faf6b91
Merge branch 'security/enchant-other-permission' into ver/26.2
Jakubk15 Jul 8, 2026
d412143
Merge branch 'refs/heads/harden/dependency-loader-integrity' into ver…
Jakubk15 Jul 8, 2026
b89d9b0
26.2 in runServer
Jakubk15 Jul 8, 2026
b094124
Merge branch 'master' into ver/26.2
Jakubk15 Jul 9, 2026
c6fab54
Revert "harden: verify dependency integrity and add download timeouts"
Jakubk15 Jul 9, 2026
9f7f1f0
Add 26.2 in paperVersions property
Jakubk15 Jul 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/autoapprove.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: ""
7 changes: 4 additions & 3 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.5.0
Expand All @@ -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: >-
Expand All @@ -85,11 +85,12 @@ 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
path: eternalcore-plugin/build/libs/EternalCore*.jar
archive: false
retention-days: 14
overwrite: true

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/hangar-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/modrinth-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import org.bukkit.inventory.PlayerInventory;

@Command(name = "enchant")
@Permission("eternalcore.enchant")
class EnchantCommand {

private final EnchantSettings enchantSettings;
Expand All @@ -28,6 +27,7 @@ class EnchantCommand {
}

@Execute
@Permission("eternalcore.enchant")
@DescriptionDocs(description = "Enchants item in hand", arguments = "<enchantment> <level>")
void execute(@Sender Player player, @Arg Enchantment enchantment, @Arg(EnchantLevelArgument.KEY) int level) {
PlayerInventory playerInventory = player.getInventory();
Expand All @@ -51,7 +51,8 @@ void execute(@Sender Player player, @Arg Enchantment enchantment, @Arg(EnchantLe
}

@Execute
@DescriptionDocs(description = "Enchants item in hand", arguments = "<enchantment> <level> <player>")
@Permission("eternalcore.enchant.other")
@DescriptionDocs(description = "Enchants the item held by the specified player", arguments = "<enchantment> <level> <player>")
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());
Expand Down
6 changes: 2 additions & 4 deletions eternalcore-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import org.gradle.jvm.toolchain.JavaLanguageVersion
import org.gradle.jvm.toolchain.JavaToolchainService

plugins {
`eternalcode-java`
`eternalcode-java-test`
`eternalcore-repositories`
`eternalcore-shadow-compiler`
`eternalcore-publish-plugin`
Expand Down Expand Up @@ -51,7 +49,7 @@ tasks {
languageVersion.set(JavaLanguageVersion.of(25))
})

minecraftVersion("26.1.2")
minecraftVersion("26.2")
downloadPlugins.modrinth("luckperms", "v${Versions.LUCKPERMS}-bukkit")
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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}).
*
* <p>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.
*
* <p>Published checksum files sometimes contain trailing content such as {@code "<hash> <filename>"};
* 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();
}
Comment thread
Jakubk15 marked this conversation as resolved.
Outdated

}
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Repository> repositories;
Expand Down Expand Up @@ -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);
Expand All @@ -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) {
Expand All @@ -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;
}
}
Comment thread
Jakubk15 marked this conversation as resolved.
Outdated

private static InputStream openStream(URLConnection connection) throws IOException {
connection.setConnectTimeout(CONNECT_TIMEOUT_MILLIS);
connection.setReadTimeout(READ_TIMEOUT_MILLIS);
return connection.getInputStream();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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);
}
Expand Down
Loading
Loading