Skip to content

Deterministic Bundle Id generation#220

Open
lbloder wants to merge 3 commits intomainfrom
feat/deterministic-bundle-id
Open

Deterministic Bundle Id generation#220
lbloder wants to merge 3 commits intomainfrom
feat/deterministic-bundle-id

Conversation

@lbloder
Copy link
Collaborator

@lbloder lbloder commented Jan 26, 2026

📜 Description

Add the ability to calculate the bundleId based on Hashes of filepaths and file contents of the source context.
This allows for reproducible builds by having the bundleId stay the same if no files change.
This is an opt-in feature that can be enabled with:

<reproducibleBundleId>true</reproducibleBundleId>

💡 Motivation and Context

Resolves #213

💚 How did you test it?

📝 Checklist

  • I added GH Issue ID & Linear ID
  • I reviewed the submitted code
  • I added tests to verify the changes
  • I updated the docs if needed
  • No breaking changes

🔮 Next steps

@lbloder lbloder changed the title set correct maven version in wrapper, generate bundle id from file ha… Deterministic Bundle Id generation Feb 4, 2026
@lbloder lbloder marked this pull request as ready for review February 5, 2026 21:04
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Missing delimiter between path and content in hash
    • The deterministic bundle hash now prefixes both path bytes and file bytes with their lengths before updating the digest, removing ambiguous concatenation collisions.
  • ✅ Fixed: Platform-dependent newLine breaks cross-OS reproducibility
    • The properties file writer now uses explicit "\n" line endings instead of platform-dependent newLine() to keep output byte-identical across OSes.

Create PR

Or push these changes by commenting:

@cursor push 13fbb069ed
Preview (13fbb069ed)
diff --git a/src/main/java/io/sentry/UploadSourceBundleMojo.java b/src/main/java/io/sentry/UploadSourceBundleMojo.java
--- a/src/main/java/io/sentry/UploadSourceBundleMojo.java
+++ b/src/main/java/io/sentry/UploadSourceBundleMojo.java
@@ -213,11 +213,11 @@
           for (final @NotNull Path file : sortedFiles) {
             final @NotNull String relativePath =
                 collectedSourcesDir.toPath().relativize(file).toString().replace('\\', '/');
-            digest.update(relativePath.getBytes(StandardCharsets.UTF_8));
+            updateDigestWithLengthPrefix(digest, relativePath.getBytes(StandardCharsets.UTF_8));
 
             // Include the file content in the hash
             final byte[] fileBytes = Files.readAllBytes(file);
-            digest.update(fileBytes);
+            updateDigestWithLengthPrefix(digest, fileBytes);
           }
         }
       }
@@ -257,6 +257,12 @@
     return new UUID(buffer.getLong(), buffer.getLong()).toString();
   }
 
+  private static void updateDigestWithLengthPrefix(
+      final @NotNull MessageDigest digest, final byte[] data) {
+    digest.update(ByteBuffer.allocate(Integer.BYTES).putInt(data.length).array());
+    digest.update(data);
+  }
+
   private void bundleSources(
       final @NotNull SentryCliRunner cliRunner,
       final @NotNull String bundleId,
@@ -375,11 +381,11 @@
       // Write properties without timestamp comment for reproducible builds
       // Properties are written in sorted order for consistency
       fileWriter.write("# Generated by sentry-maven-plugin");
-      fileWriter.newLine();
+      fileWriter.write("\n");
       fileWriter.write("io.sentry.build-tool=maven");
-      fileWriter.newLine();
+      fileWriter.write("\n");
       fileWriter.write("io.sentry.bundle-ids=" + bundleId);
-      fileWriter.newLine();
+      fileWriter.write("\n");
 
       final @NotNull Resource resource = new Resource();
       resource.setDirectory(sentryBuildDir.getPath());
This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.


// Include the file content in the hash
final byte[] fileBytes = Files.readAllBytes(file);
digest.update(fileBytes);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing delimiter between path and content in hash

Medium Severity

The hash computation feeds relativePath bytes and fileBytes into the digest without any separator or length prefix between them. This means two different file trees can produce the same hash — for example, a file named "ab" with content "cd" produces the same digest input as a file named "a" with content "bcd". Adding a delimiter (e.g. a null byte) or a length prefix between the path and content would prevent these ambiguous collisions and make the deterministic bundle ID more robust.

Fix in Cursor Fix in Web

fileWriter.write("io.sentry.build-tool=maven");
fileWriter.newLine();
fileWriter.write("io.sentry.bundle-ids=" + bundleId);
fileWriter.newLine();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Platform-dependent newLine breaks cross-OS reproducibility

Medium Severity

BufferedWriter.newLine() writes the platform-specific line separator (\r\n on Windows, \n on Unix). Since the purpose of this change is to produce reproducible builds, the properties file will differ across operating systems, resulting in different JAR contents. Using an explicit "\n" instead of newLine() would ensure byte-identical output regardless of platform.

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Re-running sentry plugin breaks binary-reproducible builds

1 participant