Author: msahyoun
Date: Thu Apr 2 09:06:14 2026
New Revision: 1932731
Log:
PDFBOX-6185: improve handling of files in temp directory; Windows settings by
Claude Sonnet 4.6
Modified:
pdfbox/branches/3.0/debugger/src/main/java/org/apache/pdfbox/debugger/ui/Tree.java
pdfbox/branches/3.0/io/src/main/java/org/apache/pdfbox/io/IOUtils.java
Modified:
pdfbox/branches/3.0/debugger/src/main/java/org/apache/pdfbox/debugger/ui/Tree.java
==============================================================================
---
pdfbox/branches/3.0/debugger/src/main/java/org/apache/pdfbox/debugger/ui/Tree.java
Thu Apr 2 08:53:31 2026 (r1932730)
+++
pdfbox/branches/3.0/debugger/src/main/java/org/apache/pdfbox/debugger/ui/Tree.java
Thu Apr 2 09:06:14 2026 (r1932731)
@@ -41,6 +41,7 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
@@ -59,6 +60,11 @@ public class Tree extends JTree
private final JPopupMenu treePopupMenu;
private final Object rootNode;
+ // Temporary files are stored in a private temp directory with restricted
permissions,
+ // which is deleted on exit using a shutdown hook.
+ // PDFBOX-6185
+ private Path tempDir;
+
/**
* Constructor.
*/
@@ -294,8 +300,11 @@ public class Tree extends JTree
{
try
{
- File temp = Files.createTempFile("pdfbox", "." +
extension).toFile();
- temp.deleteOnExit();
+ if (tempDir == null)
+ {
+ tempDir = IOUtils.createProtectedTempDir();
+ }
+ File temp = Files.createTempFile(tempDir, "pdfbox", "." +
extension).toFile();
try (InputStream is = cosStream.createInputStream())
{
Modified: pdfbox/branches/3.0/io/src/main/java/org/apache/pdfbox/io/IOUtils.java
==============================================================================
--- pdfbox/branches/3.0/io/src/main/java/org/apache/pdfbox/io/IOUtils.java
Thu Apr 2 08:53:31 2026 (r1932730)
+++ pdfbox/branches/3.0/io/src/main/java/org/apache/pdfbox/io/IOUtils.java
Thu Apr 2 09:06:14 2026 (r1932731)
@@ -29,6 +29,7 @@ import static java.util.Objects.nonNull;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -38,11 +39,23 @@ import java.lang.invoke.MethodHandles.Lo
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.AclEntry;
+import java.nio.file.attribute.AclEntryPermission;
+import java.nio.file.attribute.AclEntryType;
+import java.nio.file.attribute.AclFileAttributeView;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.nio.file.attribute.UserPrincipal;
import java.security.AccessController;
import java.security.PrivilegedAction;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
+import java.util.stream.Stream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -335,4 +348,93 @@ public final class IOUtils
{
return MemoryUsageSetting.setupTempFileOnly().streamCache;
}
+
+ /**
+ * Creates a temporary directory in the default temporary-file directory
+ * with owner-only permissions and registers a shutdown hook to delete it
on JVM exit.
+ *
+ * <p>Note: This method is designed to be used for storing temporary files
that may contain sensitive data
+ * in a temporary directories with restricted permissions, to mitigate the
risk of unauthorized access by
+ * other users or processes on the same system. Used e.g. by
PDFDebugger.</p>
+ *
+ * @return the path to the created temporary directory
+ * @throws IOException
+ */
+ public static Path createProtectedTempDir() throws IOException
+ {
+ // S5443: permissions are immediately restricted to owner-only by
+ // applyOwnerOnlyPermissions(), mitigating the default-permission risk.
+ @SuppressWarnings("java:S5443")
+ Path tempPath = Files.createTempDirectory("pdfbox-");
+ applyOwnerOnlyPermissions(tempPath);
+
+ // use shutdown hook instead of deleteOnExit() to ensure deletion
+ // of the entire directory in case of not automatically deleted on
+ // JVM exit (e.g. due to open file handles or when the temp directory
is not empty)
+ Runtime.getRuntime().addShutdownHook(new Thread(() ->
+ {
+ try (Stream<Path> entries = Files.walk(tempPath))
+ {
+ entries.sorted(Comparator.reverseOrder())
+ .forEach(p -> p.toFile().delete());
+ }
+ catch (IOException ignored) {}
+ }));
+
+ return tempPath;
+ }
+
+ private static void applyOwnerOnlyPermissions(Path dir) throws IOException
+ {
+ if
(FileSystems.getDefault().supportedFileAttributeViews().contains("posix"))
+ {
+ // Unix/macOS — rwx------
+ Files.setPosixFilePermissions(dir,
PosixFilePermissions.fromString("rwx------"));
+ }
+ else
+ {
+ // Windows — replace the entire ACL with a single owner-only ALLOW
entry
+ AclFileAttributeView aclView =
+ Files.getFileAttributeView(dir, AclFileAttributeView.class);
+
+ if (aclView == null)
+ {
+ File tempDir = dir.toFile();
+ boolean isReadable = tempDir.setReadable(true, true);
+ boolean isWritable = tempDir.setWritable(true, true);
+ boolean isExecutable = tempDir.setExecutable(true, true);
+ if (!isReadable || !isWritable || !isExecutable)
+ {
+ LOG.warn("Unable to set owner-only permissions on
temporary directory: " + dir +
+ ". Please ensure that the temporary directory is
protected against unauthorized access.");
+ }
+ return;
+ }
+
+ UserPrincipal owner = aclView.getOwner();
+
+ AclEntry ownerFullControl = AclEntry.newBuilder()
+ .setType(AclEntryType.ALLOW)
+ .setPrincipal(owner)
+ .setPermissions(
+ AclEntryPermission.READ_DATA,
+ AclEntryPermission.WRITE_DATA,
+ AclEntryPermission.APPEND_DATA,
+ AclEntryPermission.READ_NAMED_ATTRS,
+ AclEntryPermission.WRITE_NAMED_ATTRS,
+ AclEntryPermission.EXECUTE,
+ AclEntryPermission.DELETE_CHILD,
+ AclEntryPermission.READ_ATTRIBUTES,
+ AclEntryPermission.WRITE_ATTRIBUTES,
+ AclEntryPermission.DELETE,
+ AclEntryPermission.READ_ACL,
+ AclEntryPermission.WRITE_ACL,
+ AclEntryPermission.SYNCHRONIZE
+ )
+ .build();
+
+ // Set so that only the owner has permissions, and remove any
inherited ACL entries
+ aclView.setAcl(Collections.singletonList(ownerFullControl));
+ }
+ }
}