diff --git a/core/src/main/java/org/testcontainers/utility/MountableFile.java b/core/src/main/java/org/testcontainers/utility/MountableFile.java index a8a9f465a19..4ecec02a9a1 100644 --- a/core/src/main/java/org/testcontainers/utility/MountableFile.java +++ b/core/src/main/java/org/testcontainers/utility/MountableFile.java @@ -356,6 +356,10 @@ private void recursiveTar( // TarArchiveEntry automatically sets the mode for file/directory, but we can update to ensure that the mode is set exactly (inc executable bits) tarEntry.setMode(getUnixFileMode(itemPath)); + // Avoid propagating host UID/GID into containers. This is important for rootless Docker setups + // where host IDs may be unmapped and cause permission issues after copyArchiveToContainer. + tarEntry.setUserId(0); + tarEntry.setGroupId(0); tarArchive.putArchiveEntry(tarEntry); if (sourceFile.isFile()) { diff --git a/core/src/test/java/org/testcontainers/utility/MountableFileTest.java b/core/src/test/java/org/testcontainers/utility/MountableFileTest.java index 72db429c263..33bdee7255c 100644 --- a/core/src/test/java/org/testcontainers/utility/MountableFileTest.java +++ b/core/src/test/java/org/testcontainers/utility/MountableFileTest.java @@ -2,6 +2,7 @@ import lombok.Cleanup; import org.apache.commons.compress.archivers.ArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.jetbrains.annotations.NotNull; @@ -131,6 +132,20 @@ void noTrailingSlashesInTarEntryNames() throws Exception { } } + @Test + void tarEntriesShouldUseRootOwnership() throws Exception { + final Path file = createTempFile("owner-check.txt"); + final MountableFile mountableFile = MountableFile.forHostPath(file.toString()); + + @Cleanup + final TarArchiveInputStream tais = intoTarArchive(taos -> mountableFile.transferTo(taos, "/owner-check.txt")); + + final TarArchiveEntry entry = tais.getNextTarEntry(); + assertThat(entry).isNotNull(); + assertThat(entry.getLongUserId()).as("tar user id should not leak host uid").isZero(); + assertThat(entry.getLongGroupId()).as("tar group id should not leak host gid").isZero(); + } + private TarArchiveInputStream intoTarArchive(Consumer consumer) throws IOException { @Cleanup final ByteArrayOutputStream baos = new ByteArrayOutputStream();