From 2a043cbdf43b27cab3d5a0990bcaf3b55c457114 Mon Sep 17 00:00:00 2001 From: Kharkunov Eugene Date: Mon, 16 Feb 2026 10:59:19 +0200 Subject: [PATCH 01/10] Fix possible resource leakage --- .../extender/client/ExtenderClientCache.java | 14 +++++------ .../extender/client/ExtenderClientTest.java | 23 ++++++++++--------- .../AndroidManifestMerger.java | 8 +++++++ .../manifestmergetool/InfoPlistMerger.java | 7 ++++-- .../PrivacyManifestMerger.java | 7 ++++-- .../services/cocoapods/CocoaPodsService.java | 11 ++++----- 6 files changed, 40 insertions(+), 30 deletions(-) diff --git a/client/src/main/java/com/defold/extender/client/ExtenderClientCache.java b/client/src/main/java/com/defold/extender/client/ExtenderClientCache.java index 7ce0c67f..82b876cd 100644 --- a/client/src/main/java/com/defold/extender/client/ExtenderClientCache.java +++ b/client/src/main/java/com/defold/extender/client/ExtenderClientCache.java @@ -128,8 +128,8 @@ public void put(String platform, String key, File source) throws ExtenderClientE throw new ExtenderClientException(String.format("Failed to create cache dir %s", parentDir.getAbsolutePath())); } - try { - Files.copy(new FileInputStream(source), cachedFile.toPath(), REPLACE_EXISTING); + try(InputStream is = new FileInputStream(source)) { + Files.copy(is, cachedFile.toPath(), REPLACE_EXISTING); } catch (IOException e) { throw new ExtenderClientException(String.format("Failed to copy %s to %s", source.getAbsolutePath(), cachedFile.getAbsolutePath()), e); } @@ -152,15 +152,13 @@ public void get(String platform, String key, File destination) throws ExtenderCl throw new ExtenderClientException(String.format("The file %s wasn't cached with key %s", cachedFile.getAbsolutePath(), key)); } - try { - Files.copy(new FileInputStream(cachedFile), destination.toPath(), REPLACE_EXISTING); + try(InputStream is = new FileInputStream(cachedFile)) { + Files.copy(is, destination.toPath(), REPLACE_EXISTING); } catch (IOException e) { throw new ExtenderClientException(String.format("Failed to copy %s to %s", cachedFile.getAbsolutePath(), destination.getAbsolutePath()), e); } } - // - private static MessageDigest getHasher() throws ExtenderClientException { MessageDigest md = null; try { @@ -198,8 +196,8 @@ private void saveCache() { private void loadCache() { Properties properties = new Properties(); - try { - properties.load(new FileInputStream(getCacheFile())); + try(InputStream is = new FileInputStream(getCacheFile())) { + properties.load(is); } catch (IOException e) { return; } diff --git a/client/src/test/java/com/defold/extender/client/ExtenderClientTest.java b/client/src/test/java/com/defold/extender/client/ExtenderClientTest.java index c9f66fe1..2eea6600 100644 --- a/client/src/test/java/com/defold/extender/client/ExtenderClientTest.java +++ b/client/src/test/java/com/defold/extender/client/ExtenderClientTest.java @@ -205,18 +205,19 @@ public void testUploadStructure(String cacheResponse, List expectedFilen assertNotNull(sourceCodeArchiveEntity); assertTrue(sourceCodeArchiveEntity.getBody() instanceof BinaryBody); BinaryBody archiveBody = (BinaryBody)sourceCodeArchiveEntity.getBody(); - ZipInputStream zis = new ZipInputStream(archiveBody.getInputStream()); - ZipEntry zipEntry = zis.getNextEntry(); - List zipEntriesFilenames = new ArrayList<>(); - while (zipEntry != null) { - zipEntriesFilenames.add(zipEntry.getName()); - zipEntry = zis.getNextEntry(); + try (ZipInputStream zis = new ZipInputStream(archiveBody.getInputStream())) { + ZipEntry zipEntry = zis.getNextEntry(); + List zipEntriesFilenames = new ArrayList<>(); + while (zipEntry != null) { + zipEntriesFilenames.add(zipEntry.getName()); + zipEntry = zis.getNextEntry(); + } + assertTrue( + expectedFilenames.size() == zipEntriesFilenames.size() && + expectedFilenames.containsAll(zipEntriesFilenames) && + zipEntriesFilenames.containsAll(expectedFilenames) + ); } - assertTrue( - expectedFilenames.size() == zipEntriesFilenames.size() && - expectedFilenames.containsAll(zipEntriesFilenames) && - zipEntriesFilenames.containsAll(expectedFilenames) - ); } catch (Exception e) { System.out.println("ERROR LOG:"); diff --git a/server/manifestmergetool/src/main/java/com/defold/manifestmergetool/AndroidManifestMerger.java b/server/manifestmergetool/src/main/java/com/defold/manifestmergetool/AndroidManifestMerger.java index a2674099..77a70464 100644 --- a/server/manifestmergetool/src/main/java/com/defold/manifestmergetool/AndroidManifestMerger.java +++ b/server/manifestmergetool/src/main/java/com/defold/manifestmergetool/AndroidManifestMerger.java @@ -24,15 +24,23 @@ private static class ILoggerWrapper implements ILogger { public ILoggerWrapper(Logger logger) { this.logger = logger; } + + @Override public void error(Throwable t, String msgFormat, Object... args) { AndroidManifestMerger.logger.log(Level.SEVERE, msgFormat, args); } + + @Override public void warning(String msgFormat, Object... args) { AndroidManifestMerger.logger.log(Level.WARNING, msgFormat, args); } + + @Override public void info(String msgFormat, Object... args) { AndroidManifestMerger.logger.log(Level.INFO, msgFormat, args); } + + @Override public void verbose(String msgFormat, Object... args) { AndroidManifestMerger.logger.log(Level.FINE, msgFormat, args); } diff --git a/server/manifestmergetool/src/main/java/com/defold/manifestmergetool/InfoPlistMerger.java b/server/manifestmergetool/src/main/java/com/defold/manifestmergetool/InfoPlistMerger.java index ad044be2..6c282055 100644 --- a/server/manifestmergetool/src/main/java/com/defold/manifestmergetool/InfoPlistMerger.java +++ b/server/manifestmergetool/src/main/java/com/defold/manifestmergetool/InfoPlistMerger.java @@ -5,6 +5,7 @@ import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.io.Reader; import java.io.FileNotFoundException; import java.util.Iterator; import java.util.ArrayList; @@ -151,14 +152,16 @@ else if (baseMergePolicy == MergePolicy.MERGE) { private XMLPropertyListConfiguration loadPlist(File file) { - try { + try(Reader fr = new FileReader(file)) { XMLPropertyListConfiguration plist = new XMLPropertyListConfiguration(); - plist.read(new FileReader(file)); + plist.read(fr); return plist; } catch (ConfigurationException e) { throw new RuntimeException(String.format("Failed to parse plist '%s': %s", file.getAbsolutePath(), e.toString())); } catch (FileNotFoundException e) { throw new RuntimeException(String.format("File not found: %s", file.getAbsolutePath())); + } catch (IOException e) { + throw new RuntimeException(String.format("Exception when closing file %s", e.getMessage())); } } diff --git a/server/manifestmergetool/src/main/java/com/defold/manifestmergetool/PrivacyManifestMerger.java b/server/manifestmergetool/src/main/java/com/defold/manifestmergetool/PrivacyManifestMerger.java index 2a1638b5..ec193a1b 100644 --- a/server/manifestmergetool/src/main/java/com/defold/manifestmergetool/PrivacyManifestMerger.java +++ b/server/manifestmergetool/src/main/java/com/defold/manifestmergetool/PrivacyManifestMerger.java @@ -1,6 +1,7 @@ package com.defold.manifestmergetool; import java.io.File; +import java.io.Reader; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; @@ -132,14 +133,16 @@ private void mergeConfigs(XMLPropertyListConfiguration base, XMLPropertyListConf } private XMLPropertyListConfiguration loadConfig(File file) { - try { + try(Reader fr = new FileReader(file)) { XMLPropertyListConfiguration plist = new XMLPropertyListConfiguration(); - plist.read(new FileReader(file)); + plist.read(fr); return plist; } catch (ConfigurationException e) { throw new RuntimeException(String.format("Failed to parse plist '%s': %s", file.getAbsolutePath(), e.toString())); } catch (FileNotFoundException e) { throw new RuntimeException(String.format("File not found: %s", file.getAbsolutePath())); + } catch (IOException e) { + throw new RuntimeException(String.format("Exception when closing file %s", e.getMessage())); } } diff --git a/server/src/main/java/com/defold/extender/services/cocoapods/CocoaPodsService.java b/server/src/main/java/com/defold/extender/services/cocoapods/CocoaPodsService.java index 74378cba..084b5bb9 100644 --- a/server/src/main/java/com/defold/extender/services/cocoapods/CocoaPodsService.java +++ b/server/src/main/java/com/defold/extender/services/cocoapods/CocoaPodsService.java @@ -121,10 +121,9 @@ private static void dumpDir(File file, int indent) throws IOException { /** * Create the main Podfile with a list of all dependencies for all uploaded extensions - * @param podFiles List of podfiles to merge into the main Pofile - * @param jobDirectory The job directory from where to search for Podfiles - * @param workingDir The working directory where pods should be resolved - * @param platform For which platform to resolve pods + * @param buildState Extender's build state + * @param cocoapodsBuildState Cocoapods build state + * @param jobEnvContext Map of environemnt variables defined for the current build * @return Main pod file structure * @throws PodfileParsingException * @throws IOException @@ -354,9 +353,7 @@ private Map createJobEnvContext(Map env) { /** * Entry point for Cocoapod dependency resolution. * @param config Platform config - * @param jobDir Root directory of the job to resolve - * @param platform Which platform to resolve pods for - * @param configuration Build configuration ("debug", "release", "headless") + * @param buildState Extender's build state * @return ResolvedPods instance with list of pods, install directory etc */ public ResolvedPods resolveDependencies(PlatformConfig config, ExtenderBuildState buildState) throws IOException, ExtenderException { From 2a62eb8cc0c23c366b325bb60e8d971173ba29c3 Mon Sep 17 00:00:00 2001 From: Kharkunov Eugene Date: Mon, 13 Apr 2026 22:37:32 +0300 Subject: [PATCH 02/10] Added more validation or manifest options --- .../extender/ExtensionManifestValidator.java | 19 +++ .../ExtensionManifestValidatorTest.java | 112 ++++++++++++++++++ 2 files changed, 131 insertions(+) diff --git a/server/src/main/java/com/defold/extender/ExtensionManifestValidator.java b/server/src/main/java/com/defold/extender/ExtensionManifestValidator.java index fef9e486..92976fab 100644 --- a/server/src/main/java/com/defold/extender/ExtensionManifestValidator.java +++ b/server/src/main/java/com/defold/extender/ExtensionManifestValidator.java @@ -19,6 +19,9 @@ class ExtensionManifestValidator { private final List allowedDefines = new ArrayList<>(); private final List allowedSymbols = new ArrayList<>(); + private static final Pattern VALID_INCLUDE_PATH = Pattern.compile("^[A-Za-z0-9._+\\-/]+$"); + private static final Pattern VALID_SYMBOL_IDENTIFIER = Pattern.compile("^[A-Za-z_][A-Za-z0-9_]*$"); + ExtensionManifestValidator(WhitelistConfig whitelistConfig, List allowedFlags, List allowedSymbols) { this.allowedDefines.add(WhitelistConfig.compile(whitelistConfig.defineRe)); this.allowedLibs.add(WhitelistConfig.compile(whitelistConfig.libraryRe)); @@ -34,6 +37,11 @@ private static boolean isListOfStrings(List list) { private void validateIncludePaths(String extensionName, File extensionFolder, List includes) throws ExtenderException { for (String include : includes) { + if (include == null || include.isEmpty() || !VALID_INCLUDE_PATH.matcher(include).matches()) { + throw new ExtenderException(String.format( + "Error in '%s': Invalid include path '%s'. Include paths may only contain letters, digits and '._+-/'.", + extensionName, include)); + } String[] tokens = include.split("/"); for (int i = 0; i < tokens.length; ++i) { @@ -98,6 +106,17 @@ void validate(String extensionName, File extensionFolder, Map co continue; case "symbols": + if (v instanceof List) { + for (String sym : (List) v) { + if (sym == null || !VALID_SYMBOL_IDENTIFIER.matcher(sym).matches()) { + throw new ExtenderException(String.format( + "Error in '%s': Invalid symbol '%s'. Symbols must be valid C identifiers.", + extensionName, sym)); + } + } + } + continue; + case "excludeLibs": case "excludeJars": case "excludeJsLibs": diff --git a/server/src/test/java/com/defold/extender/ExtensionManifestValidatorTest.java b/server/src/test/java/com/defold/extender/ExtensionManifestValidatorTest.java index cca77e9e..bcc5f813 100644 --- a/server/src/test/java/com/defold/extender/ExtensionManifestValidatorTest.java +++ b/server/src/test/java/com/defold/extender/ExtensionManifestValidatorTest.java @@ -132,6 +132,118 @@ public void testWhitelistCheckContext() throws ExtenderException, IOException { assertTrue(exc.toString().contains("rm -rf")); } + @Test + public void testValidateIncludePaths() throws ExtenderException { + List empty = new ArrayList<>(); + ExtensionManifestValidator validator = new ExtensionManifestValidator(new WhitelistConfig(), empty, empty); + + File extensionFolder = new File("ext-folder"); + + // Happy paths + assertDoesNotThrow(() -> { + Map ctx = new HashMap<>(); + ctx.put("includes", new ArrayList()); + validator.validate("ext", extensionFolder, ctx); + }); + + assertDoesNotThrow(() -> { + Map ctx = new HashMap<>(); + ctx.put("includes", Arrays.asList("include")); + validator.validate("ext", extensionFolder, ctx); + }); + + assertDoesNotThrow(() -> { + Map ctx = new HashMap<>(); + ctx.put("includes", Arrays.asList("./include")); + validator.validate("ext", extensionFolder, ctx); + }); + + assertDoesNotThrow(() -> { + Map ctx = new HashMap<>(); + ctx.put("includes", Arrays.asList("src/foo", "src/bar")); + validator.validate("ext", extensionFolder, ctx); + }); + + assertDoesNotThrow(() -> { + Map ctx = new HashMap<>(); + ctx.put("includes", Arrays.asList("include-1.2_beta/path")); + validator.validate("ext", extensionFolder, ctx); + }); + + // Space injection — PoC from the bug report + String[] injections = new String[] { + "./ -Xclang -load /etc/libs/evil.so", + "include -I/etc", + "include\t-I/etc", + "include\n-I/etc", + "include;rm -rf /", + "include$(whoami)", + "include`id`", + "include|nc attacker 1337", + "include\"quoted\"", + "include\\backslash", + "", + }; + for (String bad : injections) { + Map ctx = new HashMap<>(); + ctx.put("includes", Arrays.asList(bad)); + ExtenderException exc = assertThrows(ExtenderException.class, () -> { + validator.validate("ext", extensionFolder, ctx); + }, "expected rejection of include: " + bad); + assertTrue(exc.getMessage().contains("Invalid include path"), + "message should mention Invalid include path, got: " + exc.getMessage()); + } + + // Path traversal is still rejected (by existing isChild check) + { + Map ctx = new HashMap<>(); + ctx.put("includes", Arrays.asList("../../etc/passwd")); + assertThrows(ExtenderException.class, () -> { + validator.validate("ext", extensionFolder, ctx); + }); + } + + // Non-list includes is rejected + { + Map ctx = new HashMap<>(); + ctx.put("includes", "not-a-list"); + assertThrows(ExtenderException.class, () -> { + validator.validate("ext", extensionFolder, ctx); + }); + } + } + + @Test + public void testValidateSymbols() throws ExtenderException { + List empty = new ArrayList<>(); + ExtensionManifestValidator validator = new ExtensionManifestValidator(new WhitelistConfig(), empty, empty); + + File extensionFolder = new File("ext-folder"); + + assertDoesNotThrow(() -> { + Map ctx = new HashMap<>(); + ctx.put("symbols", Arrays.asList("MyExtension", "_underscore", "mix3d_42")); + validator.validate("ext", extensionFolder, ctx); + }); + + String[] bad = new String[] { + "x();system(\"pwn\");void y", + "has space", + "1startsWithDigit", + "has-dash", + "", + }; + for (String s : bad) { + Map ctx = new HashMap<>(); + ctx.put("symbols", Arrays.asList(s)); + ExtenderException exc = assertThrows(ExtenderException.class, () -> { + validator.validate("ext", extensionFolder, ctx); + }, "expected rejection of symbol: " + s); + assertTrue(exc.getMessage().contains("Invalid symbol"), + "message should mention Invalid symbol, got: " + exc.getMessage()); + } + } + @Test public void testAllowedLibs() throws ExtenderException { Map context = new HashMap<>(); From 98ec3fd86b7b3970624f80c03fca472d5ac5c542 Mon Sep 17 00:00:00 2001 From: Kharkunov Eugene Date: Tue, 14 Apr 2026 14:05:44 +0300 Subject: [PATCH 03/10] Added http configuration to handle redirects in case of switching protocols (https->http) --- server/configs/application-local-dev-app.yml | 7 ++ server/scripts/debug_defoldsdk.py | 2 +- .../extender/services/DefoldSdkService.java | 74 ++++++++++++------- .../DefoldSdkServiceConfiguration.java | 1 + server/src/main/resources/application.yml | 8 +- .../services/DefoldSDKServiceTest.java | 7 +- 6 files changed, 63 insertions(+), 36 deletions(-) diff --git a/server/configs/application-local-dev-app.yml b/server/configs/application-local-dev-app.yml index fb5c2e5e..06d8f508 100644 --- a/server/configs/application-local-dev-app.yml +++ b/server/configs/application-local-dev-app.yml @@ -1,4 +1,11 @@ extender: + sdk: + sdk-urls: > + https://d.defold.com/archive/stable/%s/engine/defoldsdk.zip, + https://d.defold.com/archive/%s/engine/defoldsdk.zip + mappings-urls: > + https://d.defold.com/archive/stable/%s/engine/platform.sdks.json, + https://d.defold.com/archive/%s/engine/platform.sdks.json instance-type: FRONTEND_ONLY remote-builder: enabled: true diff --git a/server/scripts/debug_defoldsdk.py b/server/scripts/debug_defoldsdk.py index 17cab33f..7cc223ff 100755 --- a/server/scripts/debug_defoldsdk.py +++ b/server/scripts/debug_defoldsdk.py @@ -10,7 +10,7 @@ domain=os.environ.get("DM_ARCHIVE_DOMAIN", "d.defold.com") def get_latest_version(): - url = "http://d.defold.com/stable/info.json" + url = "https://d.defold.com/stable/info.json" response = urllib.request.urlopen(url) if response.getcode() == 200: return json.loads(response.read()) diff --git a/server/src/main/java/com/defold/extender/services/DefoldSdkService.java b/server/src/main/java/com/defold/extender/services/DefoldSdkService.java index 783cd28e..26f2e789 100644 --- a/server/src/main/java/com/defold/extender/services/DefoldSdkService.java +++ b/server/src/main/java/com/defold/extender/services/DefoldSdkService.java @@ -14,8 +14,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.micrometer.core.instrument.MeterRegistry; + +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpResponse; @@ -38,6 +41,7 @@ import java.security.NoSuchAlgorithmException; import java.util.Comparator; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @@ -58,6 +62,41 @@ public class DefoldSdkService { private final ConcurrentHashMap> mappingsDownloadOperationCache = new ConcurrentHashMap<>(); protected final LinkedHashMap mappingsCache; + private static ClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory() { + @Override + protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException { + super.prepareConnection(connection, httpMethod); + connection.setInstanceFollowRedirects("GET".equals(httpMethod) || "HEAD".equals(httpMethod)); + } + }; + + // SimpleClientHttpRequestFactory doesn't handle reiderect in case of switching protocols (https->http->https) by default + // so need manual handling of such kind of redirects + private static ClientHttpResponse doRequestWithRedirects(URI url, HttpMethod method, int maxRedirects) throws IOException, NullPointerException { + ClientHttpResponse response = null; + int counter = 0; + do { + ++counter; + ClientHttpRequest request = clientHttpRequestFactory.createRequest(url, method); + + // Connect and copy to file + response = request.execute(); + HttpStatusCode responseCode = response.getStatusCode(); + if (responseCode.is3xxRedirection()) { + List location = response.getHeaders().get(HttpHeaders.LOCATION); + if (location == null || location.isEmpty()) { + break; + } + URI next = URI.create(location.get(0)); + url = url.resolve(next); + response.close(); + continue; + } + return response; + } while(counter < maxRedirects); + throw new NullPointerException(String.format("Mac redirect count reached for request {}", url.toString())); + } + DefoldSdkService(DefoldSdkServiceConfiguration configuration, MeterRegistry meterRegistry) throws IOException { this.configuration = configuration; @@ -130,20 +169,11 @@ public CompletableFuture getRemoteSdk(String hash) { } else { boolean sdkFound = false; String url = null; - ClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory() { - @Override - protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException { - super.prepareConnection(connection, httpMethod); - connection.setInstanceFollowRedirects("GET".equals(httpMethod) || "HEAD".equals(httpMethod)); - } - }; - for (String urlPattern : configuration.getSdkUrls()) { try { url = String.format(urlPattern, hash); URI sdkURI = URI.create(url); - ClientHttpRequest existenceRequest = clientHttpRequestFactory.createRequest(sdkURI, HttpMethod.HEAD); - try (ClientHttpResponse response = existenceRequest.execute()) { + try (ClientHttpResponse response = doRequestWithRedirects(sdkURI, HttpMethod.HEAD, configuration.getMaxRedirectCount())) { if (response.getStatusCode() != HttpStatus.OK) { LOGGER.info("The given sdk does not exist: {} {}", url, response.getStatusCode().toString()); continue; @@ -155,7 +185,7 @@ protected void prepareConnection(HttpURLConnection connection, String httpMethod } catch (IOException exc) { LOGGER.warn(String.format("HEAD for %s failed", url), exc); } - } catch (IOException exc) { + } catch (Exception exc) { LOGGER.warn("Can't create HEAD request", exc); } } @@ -163,17 +193,9 @@ protected void prepareConnection(HttpURLConnection connection, String httpMethod int attempt = 0; while (attempt < configuration.getMaxVerificationRetryCount()) { LOGGER.info("Downloading Defold SDK from {} attempt {} ...", url, attempt + 1); - ClientHttpRequest request; - try { - request = clientHttpRequestFactory.createRequest(URI.create(url), HttpMethod.GET); - } catch (IOException exc) { - LOGGER.error("Connect can't be established", exc); - break; - } - File tmpResponseBody = null; // Connect and copy to file - try (ClientHttpResponse response = request.execute()) { + try (ClientHttpResponse response = doRequestWithRedirects(URI.create(url), HttpMethod.GET, configuration.getMaxRedirectCount())) { InputStream body = response.getBody(); tmpResponseBody = File.createTempFile(hash, ".zip.tmp"); Files.copy(body, tmpResponseBody.toPath(), StandardCopyOption.REPLACE_EXISTING); @@ -317,14 +339,10 @@ private CompletableFuture downloadSdkMappings(String hash) { for (String url_pattern : configuration.getMappingsUrls()) { try { URI url = URI.create(String.format(url_pattern, hash)); - - ClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory(); - ClientHttpRequest request = clientHttpRequestFactory.createRequest(url, HttpMethod.GET); - - // Connect and copy to file - try (ClientHttpResponse response = request.execute()) { - if (response.getStatusCode() != HttpStatus.OK) { - LOGGER.info("The given sdk does not exist: {} {}", url, response.getStatusCode().toString()); + try (ClientHttpResponse response = doRequestWithRedirects(url, HttpMethod.GET, configuration.getMaxRedirectCount())) { + HttpStatusCode responseCode = response.getStatusCode(); + if (responseCode != HttpStatus.OK) { + LOGGER.info("The given sdk does not exist: {} {}", url, responseCode.toString()); continue; } diff --git a/server/src/main/java/com/defold/extender/services/DefoldSdkServiceConfiguration.java b/server/src/main/java/com/defold/extender/services/DefoldSdkServiceConfiguration.java index 4a9db34f..85d1a286 100644 --- a/server/src/main/java/com/defold/extender/services/DefoldSdkServiceConfiguration.java +++ b/server/src/main/java/com/defold/extender/services/DefoldSdkServiceConfiguration.java @@ -25,6 +25,7 @@ public class DefoldSdkServiceConfiguration { @Builder.Default private int mappingsCacheSize = 20; // retry count in case of checksum validation fail @Builder.Default private int maxVerificationRetryCount = 3; + @Builder.Default private int maxRedirectCount = 5; private boolean cacheClearOnExit; private boolean enableSdkVerification; } diff --git a/server/src/main/resources/application.yml b/server/src/main/resources/application.yml index ae0570ad..08b9fe57 100644 --- a/server/src/main/resources/application.yml +++ b/server/src/main/resources/application.yml @@ -11,11 +11,11 @@ extender: mappings-cache-size: 20 cache-clear-on-exit: true sdk-urls: > - http://d.defold.com/archive/stable/%s/engine/defoldsdk.zip, - http://d.defold.com/archive/%s/engine/defoldsdk.zip + https://d.defold.com/archive/stable/%s/engine/defoldsdk.zip, + https://d.defold.com/archive/%s/engine/defoldsdk.zip mappings-urls: > - http://d.defold.com/archive/stable/%s/engine/platform.sdks.json, - http://d.defold.com/archive/%s/engine/platform.sdks.json + https://d.defold.com/archive/stable/%s/engine/platform.sdks.json, + https://d.defold.com/archive/%s/engine/platform.sdks.json max-verification-retry-count: 3 enable-sdk-verification: true server: diff --git a/server/src/test/java/com/defold/extender/services/DefoldSDKServiceTest.java b/server/src/test/java/com/defold/extender/services/DefoldSDKServiceTest.java index e9d1ff2c..227936ad 100644 --- a/server/src/test/java/com/defold/extender/services/DefoldSDKServiceTest.java +++ b/server/src/test/java/com/defold/extender/services/DefoldSDKServiceTest.java @@ -56,17 +56,18 @@ public static void beforeAll() throws IOException { DefoldSDKServiceTest.configuration = DefoldSdkServiceConfiguration.builder() .location(sdkLocation) - .sdkUrls(new String[]{"http://d.defold.com/archive/stable/%s/engine/defoldsdk.zip", "http://d.defold.com/archive/%s/engine/defoldsdk.zip"}) - .mappingsUrls(new String[] {"http://d.defold.com/archive/stable/%s/engine/platform.sdks.json", "http://d.defold.com/archive/%s/engine/platform.sdks.json"}) + .sdkUrls(new String[]{"https://d.defold.com/archive/stable/%s/engine/defoldsdk.zip", "https://d.defold.com/archive/%s/engine/defoldsdk.zip"}) + .mappingsUrls(new String[] {"https://d.defold.com/archive/stable/%s/engine/platform.sdks.json", "https://d.defold.com/archive/%s/engine/platform.sdks.json"}) .cacheSize(3) .mappingsCacheSize(3) .cacheClearOnExit(true) .enableSdkVerification(false) .maxVerificationRetryCount(3) + .maxRedirectCount(5) .build(); DefoldSDKServiceTest.zeroCacheConfiguration = new DefoldSdkServiceConfiguration(DefoldSDKServiceTest.configuration.toBuilder()); - zeroCacheConfiguration.setCacheSize(0); + zeroCacheConfiguration.setCacheSize(0); Files.createDirectories(DefoldSDKServiceTest.configuration.getLocation()); From 3e9fda3ec540d9453c4115bf66c0342025dbd36b Mon Sep 17 00:00:00 2001 From: Kharkunov Eugene Date: Tue, 14 Apr 2026 14:06:39 +0300 Subject: [PATCH 04/10] Minor script fixes --- server/build-docker.sh | 4 ++-- server/envs/generate_user_env.sh | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/server/build-docker.sh b/server/build-docker.sh index e57d4ace..5a82cdee 100755 --- a/server/build-docker.sh +++ b/server/build-docker.sh @@ -16,8 +16,8 @@ DOCKER_XBOX_PRIVATE_REGISTRY=$REGISTRY_PREFIX/extender-xbox-private-registry MULTI_ARCH="linux/amd64" [[ -z "$NO_ARM64" ]] && MULTI_ARCH+=",linux/arm64" -# base stage image (shared across Dockerfiles) -echo "Base stage image with archs: $MULTI_ARCH" +# build env image (shared across Dockerfiles) +echo "Build env image with archs: $MULTI_ARCH" docker buildx build --load --platform $MULTI_ARCH -t $DOCKER_REGISTRY/extender-build-env:1.0.0 -t $DOCKER_REGISTRY/extender-build-env:latest -f $SCRIPT_DIR/docker/Dockerfile.build-env $SCRIPT_DIR/docker # base images diff --git a/server/envs/generate_user_env.sh b/server/envs/generate_user_env.sh index 5686d0e0..9a54d554 100755 --- a/server/envs/generate_user_env.sh +++ b/server/envs/generate_user_env.sh @@ -67,8 +67,8 @@ echo "XCTOOLCHAIN_PATH=${PLATFORMSDK_DIR}/XcodeDefault${XCODE_VERSION}.xctoolcha echo "PATH=\"${APPENDED_PATH}\"" >> $OUTPUT_FILE -echo ";DM_DEBUG_COMMANDS=1" >> $OUTPUT_FILE -echo ";DYNAMO_HOME=" >> $OUTPUT_FILE -echo ";DM_DEBUG_KEEP_JOB_FOLDER=1" >> $OUTPUT_FILE +echo "#DM_DEBUG_COMMANDS=1" >> $OUTPUT_FILE +echo "#DYNAMO_HOME=" >> $OUTPUT_FILE +echo "#DM_DEBUG_KEEP_JOB_FOLDER=1" >> $OUTPUT_FILE echo "Generation completed." From a7ba90f42c547dd35f5022c5d45dee097c7ef6b5 Mon Sep 17 00:00:00 2001 From: Kharkunov Eugene Date: Tue, 14 Apr 2026 14:31:23 +0300 Subject: [PATCH 05/10] Added validation for debugSourcePath --- .../java/com/defold/extender/Extender.java | 28 +++++---- .../extender/ExtensionManifestValidator.java | 24 +++++++ .../ExtensionManifestValidatorTest.java | 62 +++++++++++++++++++ 3 files changed, 102 insertions(+), 12 deletions(-) diff --git a/server/src/main/java/com/defold/extender/Extender.java b/server/src/main/java/com/defold/extender/Extender.java index d94fa251..41ebfd98 100644 --- a/server/src/main/java/com/defold/extender/Extender.java +++ b/server/src/main/java/com/defold/extender/Extender.java @@ -219,17 +219,15 @@ private Extender(Builder builder) throws IOException, ExtenderException { baseVariantManifest = Extender.loadYaml(builder.jobDirectory, baseVariantFile, AppManifestConfiguration.class); } } - this.buildState = new ExtenderBuildState(builder, appManifest); - - if (config.platforms.get(buildState.fullPlatform) == null) { - throw new ExtenderException(String.format("Unsupported platform %s by this sdk", buildState.fullPlatform)); + if (config.platforms.get(builder.platform) == null) { + throw new ExtenderException(String.format("Unsupported platform %s by this sdk", builder.platform)); } // Merge the platform configs from build.yml into a single instance: common -> platform -> arch-platform this.platformConfig = new PlatformConfig(); this.platformConfig.context = new HashMap<>(config.context); // the context from build.yml - for (String platformAlt : ExtenderUtil.getPlatformAlternatives(buildState.fullPlatform)) { + for (String platformAlt : ExtenderUtil.getPlatformAlternatives(builder.platform)) { PlatformConfig platformConfigAlt = config.platforms.get(platformAlt); if (platformConfigAlt == null) continue; @@ -240,7 +238,7 @@ private Extender(Builder builder) throws IOException, ExtenderException { // Merge the variant info into a single config this.platformVariantConfig = new PlatformConfig(); if (baseVariantManifest != null) { - for (String platformAlt : ExtenderUtil.getPlatformAlternatives(buildState.fullPlatform)) { + for (String platformAlt : ExtenderUtil.getPlatformAlternatives(builder.platform)) { AppManifestPlatformConfig configAlt = baseVariantManifest.platforms.get(platformAlt); if (configAlt == null) continue; @@ -251,7 +249,7 @@ private Extender(Builder builder) throws IOException, ExtenderException { // Merge the app manifest info into a single config this.platformAppConfig = new PlatformConfig(); - for (String platformAlt : ExtenderUtil.getPlatformAlternatives(buildState.fullPlatform)) { + for (String platformAlt : ExtenderUtil.getPlatformAlternatives(builder.platform)) { AppManifestPlatformConfig configAlt = appManifest.platforms.get(platformAlt); if (configAlt == null) { continue; @@ -261,17 +259,17 @@ private Extender(Builder builder) throws IOException, ExtenderException { ExtenderUtil.mergeObjects(this.platformAppConfig, platformConfigAlt); } - LOGGER.info("Using context for platform: {}", buildState.fullPlatform); + LOGGER.info("Using context for platform: {}", builder.platform); - processExecutor.setCwd(buildState.jobDir); + processExecutor.setCwd(builder.jobDirectory); { HashMap envContext = new HashMap<>(); - envContext.put("build_folder", buildState.buildDir); - envContext.put("dynamo_home", buildState.sdk); + envContext.put("build_folder", builder.buildDirectory); + envContext.put("dynamo_home", builder.sdk); envContext.put("env.LD_LIBRARY_PATH", "."); // Easier when running a standalone local without such a variable - processExecutor.putEnv("DYNAMO_HOME", buildState.sdk.getAbsolutePath()); + processExecutor.putEnv("DYNAMO_HOME", builder.sdk.getAbsolutePath()); String java_home = System.getenv("JAVA_HOME"); if (java_home != null) { @@ -307,6 +305,12 @@ private Extender(Builder builder) throws IOException, ExtenderException { // The user input (ext.manifest + _app/app.manifest) will be checked against this validator ExtensionManifestValidator manifestValidator = new ExtensionManifestValidator(new WhitelistConfig(), this.platformConfig.allowedFlags, allowedSymbols); + // Validate the top-level app.manifest context (e.g. debugSourcePath) before + // ExtenderBuildState reads any field from it. + manifestValidator.validateAppManifestContext(appManifest.context); + + this.buildState = new ExtenderBuildState(builder, appManifest); + // Make sure the user hasn't input anything invalid in the manifest manifestValidator.validate(this.appManifestPath, buildState.uploadDir, this.platformAppConfig.context); diff --git a/server/src/main/java/com/defold/extender/ExtensionManifestValidator.java b/server/src/main/java/com/defold/extender/ExtensionManifestValidator.java index 92976fab..4c2bbbd0 100644 --- a/server/src/main/java/com/defold/extender/ExtensionManifestValidator.java +++ b/server/src/main/java/com/defold/extender/ExtensionManifestValidator.java @@ -35,6 +35,30 @@ private static boolean isListOfStrings(List list) { return list != null && list.stream().allMatch(o -> o instanceof String); } + // Validates the top-level app.manifest `context` map (the one read by + // ExtenderBuildState). Whitespace is intentionally rejected: the rendered + // command is split on spaces in ProcessExecutor.execute(String), so any + // space here would inject extra argv elements (compiler-flag injection). + void validateAppManifestContext(Map appContext) throws ExtenderException { + if (appContext == null) { + return; + } + Object debugSourcePath = appContext.get(ExtenderBuildState.APPMANIFEST_DEBUG_SOURCE_PATH); + if (debugSourcePath != null) { + if (!(debugSourcePath instanceof String)) { + throw new ExtenderException(String.format( + "Error in app.manifest: '%s' must be a string.", + ExtenderBuildState.APPMANIFEST_DEBUG_SOURCE_PATH)); + } + String s = (String) debugSourcePath; + if (!s.isEmpty() && !VALID_INCLUDE_PATH.matcher(s).matches()) { + throw new ExtenderException(String.format( + "Error in app.manifest: invalid '%s' value '%s'. Allowed characters: letters, digits and '._+-/'.", + ExtenderBuildState.APPMANIFEST_DEBUG_SOURCE_PATH, s)); + } + } + } + private void validateIncludePaths(String extensionName, File extensionFolder, List includes) throws ExtenderException { for (String include : includes) { if (include == null || include.isEmpty() || !VALID_INCLUDE_PATH.matcher(include).matches()) { diff --git a/server/src/test/java/com/defold/extender/ExtensionManifestValidatorTest.java b/server/src/test/java/com/defold/extender/ExtensionManifestValidatorTest.java index bcc5f813..c1dfbaa7 100644 --- a/server/src/test/java/com/defold/extender/ExtensionManifestValidatorTest.java +++ b/server/src/test/java/com/defold/extender/ExtensionManifestValidatorTest.java @@ -213,6 +213,68 @@ public void testValidateIncludePaths() throws ExtenderException { } } + @Test + public void testValidateAppManifestContextDebugSourcePath() throws ExtenderException { + List empty = new ArrayList<>(); + ExtensionManifestValidator validator = new ExtensionManifestValidator(new WhitelistConfig(), empty, empty); + + // Null context and empty / missing debugSourcePath are accepted + assertDoesNotThrow(() -> validator.validateAppManifestContext(null)); + assertDoesNotThrow(() -> validator.validateAppManifestContext(new HashMap<>())); + assertDoesNotThrow(() -> { + Map ctx = new HashMap<>(); + ctx.put("debugSourcePath", ""); + validator.validateAppManifestContext(ctx); + }); + + // Legitimate paths + String[] good = new String[] { + "/tmp/build", + "src", + "./src", + "build/intermediate-1.2_x", + "/Users/me/project/src", + }; + for (String s : good) { + Map ctx = new HashMap<>(); + ctx.put("debugSourcePath", s); + assertDoesNotThrow(() -> validator.validateAppManifestContext(ctx), + "expected to accept debugSourcePath: " + s); + } + + // PoC + injection variants + String[] bad = new String[] { + "/tmp -Xclang -load /etc/libs/evil.so", + "/tmp -fplugin=/etc/libs/evil.so", + "/tmp\t-I/etc", + "/tmp\n-I/etc", + "/tmp;rm -rf /", + "/tmp$(whoami)", + "/tmp`id`", + "/tmp|nc attacker 1337", + "/tmp\"quoted\"", + "/tmp\\backslash", + "with space", + }; + for (String s : bad) { + Map ctx = new HashMap<>(); + ctx.put("debugSourcePath", s); + ExtenderException exc = assertThrows(ExtenderException.class, + () -> validator.validateAppManifestContext(ctx), + "expected rejection of debugSourcePath: " + s); + assertTrue(exc.getMessage().contains("debugSourcePath"), + "message should mention debugSourcePath, got: " + exc.getMessage()); + } + + // Wrong type is rejected + { + Map ctx = new HashMap<>(); + ctx.put("debugSourcePath", 42); + assertThrows(ExtenderException.class, + () -> validator.validateAppManifestContext(ctx)); + } + } + @Test public void testValidateSymbols() throws ExtenderException { List empty = new ArrayList<>(); From 96dd3fb2b3ac845768a9587b2d436ce3e663a63a Mon Sep 17 00:00:00 2001 From: Kharkunov Eugene Date: Tue, 14 Apr 2026 14:32:44 +0300 Subject: [PATCH 06/10] Added 1.12.3 to integration tests --- server/src/test/java/com/defold/extender/IntegrationTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/com/defold/extender/IntegrationTest.java b/server/src/test/java/com/defold/extender/IntegrationTest.java index 21070e8b..b5b38a7e 100644 --- a/server/src/test/java/com/defold/extender/IntegrationTest.java +++ b/server/src/test/java/com/defold/extender/IntegrationTest.java @@ -122,8 +122,8 @@ public static List data() { new DefoldVersion("16c6fd602f32de4814660672c38ce3ccbbc1fb59", new Version(1, 12, 1), new String[] {"armv7-android", "arm64-android", "x86_64-linux", "x86_64-win32", "js-web", "wasm-web"}), // // https://github.com/defold/defold/releases/tag/1.12.2 new DefoldVersion("e43be333aa7a4fc319ab62adc8d405c8e98bf92f", new Version(1, 12, 2), new String[] {"armv7-android", "arm64-android", "x86_64-linux", "x86_64-win32", "js-web", "wasm-web"}), - - // new DefoldVersion("29cfe7d28acc180d55385273ea3d0b6c9e5f5a08", new Version(1, 12, 3), new String[] {"armv7-android", "arm64-android", "x86_64-linux", "x86_64-win32", "js-web", "wasm-web"}) + // // https://github.com/defold/defold/releases/tag/1.12.3 + new DefoldVersion("0ad9c86fa0a9f7ac19bc468b1a67ee06bb2578b5", new Version(1, 12, 3), new String[] {"armv7-android", "arm64-android", "x86_64-linux", "x86_64-win32", "js-web", "wasm-web"}) // Use test-data/createdebugsdk.sh to package your preferred platform sdk and it ends up in the sdk/debugsdk folder // Then you can write your tests without waiting for the next release //new DefoldVersion("debugsdk", new Version(1, 2, 104), new String[] {"js-web"}), From 261f45aa84377faba3321be51ac830aec541b8fe Mon Sep 17 00:00:00 2001 From: Kharkunov Eugene Date: Tue, 14 Apr 2026 14:58:12 +0300 Subject: [PATCH 07/10] Changed sdk verification logic. Now if checksum wan't found - verification failed --- .../extender/services/DefoldSdkService.java | 22 +++++++------- .../services/DefoldSDKServiceTest.java | 29 +++++++++++++++++++ 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/server/src/main/java/com/defold/extender/services/DefoldSdkService.java b/server/src/main/java/com/defold/extender/services/DefoldSdkService.java index 26f2e789..26eaf985 100644 --- a/server/src/main/java/com/defold/extender/services/DefoldSdkService.java +++ b/server/src/main/java/com/defold/extender/services/DefoldSdkService.java @@ -212,17 +212,19 @@ public CompletableFuture getRemoteSdk(String hash) { } catch (IOException exc) { LOGGER.warn(String.format("Can't download checksum for sdk %s", hash), exc); } + if (expectedChecksum == null) { + LOGGER.warn(String.format("No checksum for sdk %s. Verification failed.", hash)); + break; + } - boolean isChecksumValid = true; - if (expectedChecksum != null) { - LOGGER.info("Verify checksum for downloaded sdk {}", hash); - try { - String actualChecksum = ExtenderUtil.calculateSHA256(new FileInputStream(tmpResponseBody)); - isChecksumValid = expectedChecksum.equals(actualChecksum); - LOGGER.info("Checksum verification result {}", isChecksumValid); - } catch(NoSuchAlgorithmException|IOException exc) { - LOGGER.warn(String.format("Error during checksum calculation"), exc); - } + boolean isChecksumValid = false; + LOGGER.info("Verify checksum for downloaded sdk {}", hash); + try { + String actualChecksum = ExtenderUtil.calculateSHA256(new FileInputStream(tmpResponseBody)); + isChecksumValid = expectedChecksum.equals(actualChecksum); + LOGGER.info("Checksum verification result {}", isChecksumValid); + } catch(NoSuchAlgorithmException|IOException exc) { + LOGGER.warn(String.format("Error during checksum calculation"), exc); } if (!isChecksumValid) { ++attempt; diff --git a/server/src/test/java/com/defold/extender/services/DefoldSDKServiceTest.java b/server/src/test/java/com/defold/extender/services/DefoldSDKServiceTest.java index 227936ad..5b908213 100644 --- a/server/src/test/java/com/defold/extender/services/DefoldSDKServiceTest.java +++ b/server/src/test/java/com/defold/extender/services/DefoldSDKServiceTest.java @@ -114,6 +114,18 @@ public static void beforeAll() throws IOException { .withStatus(200) .withBodyFile("test_sdk_invalid.sha256") .withHeader("Content-Type", "text/plain"))); + // stub for missing checksum file - zip exists but .sha256 is not found + stubFor(head(urlEqualTo("/test_sdk_no_checksum.zip")) + .willReturn(aResponse() + .withStatus(200))); + stubFor(get(urlEqualTo("/test_sdk_no_checksum.zip")) + .willReturn(aResponse() + .withStatus(200) + .withBodyFile("test_sdk.zip") + .withHeader("Content-Type", "application/zip"))); + stubFor(get(urlEqualTo("/test_sdk_no_checksum.sha256")) + .willReturn(aResponse() + .withStatus(404))); // first call should fail; second - should be successful stubFor(get(urlEqualTo("/unstable_sdk_mapping.json")) @@ -366,6 +378,23 @@ public void testInvalidVerification() throws IOException { assertTrue(exc.getMessage().contains("Sdk verification failed")); } + @Test + public void testMissingChecksumVerification() throws IOException { + DefoldSdkServiceConfiguration conf = DefoldSdkServiceConfiguration.builder() + .location(DefoldSDKServiceTest.configuration.getLocation()) + .cacheSize(0) + .sdkUrls(new String[] {"http://localhost:" + String.valueOf(serverPort) + "/%s.zip"}) + .enableSdkVerification(true) + .maxVerificationRetryCount(3) + .build(); + DefoldSdkService sdkService = new DefoldSdkService(conf, new SimpleMeterRegistry()); + // ensure nothing is left in cache from previous runs + sdkService.evictCache(); + + ExtenderException exc = assertThrows(ExtenderException.class, () -> sdkService.getSdk("test_sdk_no_checksum")); + assertTrue(exc.getMessage().contains("Sdk verification failed")); + } + @Test public void testUnstableAccessSdkMappings() throws IOException { DefoldSdkServiceConfiguration conf = DefoldSdkServiceConfiguration.builder() From a95d57674650c8fc3d33ca3c67075128f286701e Mon Sep 17 00:00:00 2001 From: Kharkunov Eugene Date: Tue, 14 Apr 2026 23:01:08 +0300 Subject: [PATCH 08/10] Fixed missed place without handling redirects --- .../java/com/defold/extender/services/DefoldSdkService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/main/java/com/defold/extender/services/DefoldSdkService.java b/server/src/main/java/com/defold/extender/services/DefoldSdkService.java index 26eaf985..d00fb1b8 100644 --- a/server/src/main/java/com/defold/extender/services/DefoldSdkService.java +++ b/server/src/main/java/com/defold/extender/services/DefoldSdkService.java @@ -203,9 +203,8 @@ public CompletableFuture getRemoteSdk(String hash) { if (this.configuration.isEnableSdkVerification()) { LOGGER.info("Download checksum for sdk {}", hash); URI checksumURI = URI.create(url.replace(".zip", ".sha256")); - ClientHttpRequest checksumRequest = clientHttpRequestFactory.createRequest(checksumURI, HttpMethod.GET); String expectedChecksum = null; - try (ClientHttpResponse checksumResponse = checksumRequest.execute()) { + try (ClientHttpResponse checksumResponse = doRequestWithRedirects(checksumURI, HttpMethod.GET, configuration.getMaxRedirectCount())) { if (checksumResponse.getStatusCode() == HttpStatus.OK) { expectedChecksum = new String(checksumResponse.getBody().readAllBytes(), StandardCharsets.UTF_8); } From 83464302fdd9151461f32ec68473e48ccd815c6a Mon Sep 17 00:00:00 2001 From: Kharkunov Eugene Date: Wed, 15 Apr 2026 10:57:05 +0300 Subject: [PATCH 09/10] Review fixes --- .../java/com/defold/extender/services/DefoldSdkService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/defold/extender/services/DefoldSdkService.java b/server/src/main/java/com/defold/extender/services/DefoldSdkService.java index d00fb1b8..7d09ee80 100644 --- a/server/src/main/java/com/defold/extender/services/DefoldSdkService.java +++ b/server/src/main/java/com/defold/extender/services/DefoldSdkService.java @@ -94,7 +94,7 @@ private static ClientHttpResponse doRequestWithRedirects(URI url, HttpMethod met } return response; } while(counter < maxRedirects); - throw new NullPointerException(String.format("Mac redirect count reached for request {}", url.toString())); + throw new NullPointerException(String.format("Mac redirect count reached for request %s", url.toString())); } DefoldSdkService(DefoldSdkServiceConfiguration configuration, @@ -218,8 +218,8 @@ public CompletableFuture getRemoteSdk(String hash) { boolean isChecksumValid = false; LOGGER.info("Verify checksum for downloaded sdk {}", hash); - try { - String actualChecksum = ExtenderUtil.calculateSHA256(new FileInputStream(tmpResponseBody)); + try (InputStream is = new FileInputStream(tmpResponseBody)) { + String actualChecksum = ExtenderUtil.calculateSHA256(is); isChecksumValid = expectedChecksum.equals(actualChecksum); LOGGER.info("Checksum verification result {}", isChecksumValid); } catch(NoSuchAlgorithmException|IOException exc) { From d5e0e657584e447e04cd8e3a57d24cf556327a75 Mon Sep 17 00:00:00 2001 From: Kharkunov Eugene Date: Wed, 15 Apr 2026 11:02:08 +0300 Subject: [PATCH 10/10] Review fixes --- .../java/com/defold/extender/services/DefoldSdkService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/defold/extender/services/DefoldSdkService.java b/server/src/main/java/com/defold/extender/services/DefoldSdkService.java index 7d09ee80..f813197c 100644 --- a/server/src/main/java/com/defold/extender/services/DefoldSdkService.java +++ b/server/src/main/java/com/defold/extender/services/DefoldSdkService.java @@ -93,8 +93,8 @@ private static ClientHttpResponse doRequestWithRedirects(URI url, HttpMethod met continue; } return response; - } while(counter < maxRedirects); - throw new NullPointerException(String.format("Mac redirect count reached for request %s", url.toString())); + } while(counter <= maxRedirects); + throw new NullPointerException(String.format("Max redirect count reached for request %s", url.toString())); } DefoldSdkService(DefoldSdkServiceConfiguration configuration,