This is an automated email from the ASF dual-hosted git repository.

tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git


The following commit(s) were added to refs/heads/main by this push:
     new 9e58a5c9ee write the file config store with owner-only permissions 
(#3411)
9e58a5c9ee is described below

commit 9e58a5c9ee4850d7c1c86918218b78150ad17907
Author: Naveed <[email protected]>
AuthorDate: Mon Oct 26 23:39:03 2026 +0530

    write the file config store with owner-only permissions (#3411)
    
    * write the file config store with owner-only permissions
    
    FileBasedConfigStore wrote config-store.json through a sibling .tmp file
    which carry resolved credentials, were readable by every local user under
    the default 022 umask. The .tmp is now created owner-only on POSIX
    filesystems before it is written and atomically renamed into place.
    
    * use a unique owner-only temp file per config store save
---
 .../pipes/core/config/FileBasedConfigStore.java    | 32 ++++++++++++++++++++--
 .../tika/pipes/core/config/ConfigStoreTest.java    | 28 +++++++++++++++++++
 2 files changed, 57 insertions(+), 3 deletions(-)

diff --git 
a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/config/FileBasedConfigStore.java
 
b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/config/FileBasedConfigStore.java
index ee0de07fd5..b43b1bf2cd 100644
--- 
a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/config/FileBasedConfigStore.java
+++ 
b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/config/FileBasedConfigStore.java
@@ -17,10 +17,14 @@
 package org.apache.tika.pipes.core.config;
 
 import java.io.IOException;
+import java.nio.file.FileSystems;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.EnumSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -41,6 +45,9 @@ public class FileBasedConfigStore implements ConfigStore {
     
     private static final Logger LOG = 
LoggerFactory.getLogger(FileBasedConfigStore.class);
     private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+    // stored configs carry fetcher/emitter credentials; keep them from other 
local users
+    private static final Set<PosixFilePermission> OWNER_ONLY =
+            EnumSet.of(PosixFilePermission.OWNER_READ, 
PosixFilePermission.OWNER_WRITE);
     
     private final Path configFile;
     private final ConcurrentHashMap<String, ExtensionConfig> cache = new 
ConcurrentHashMap<>();
@@ -150,16 +157,35 @@ public class FileBasedConfigStore implements ConfigStore {
     }
 
     private synchronized void saveToFile() {
+        Path tempFile = null;
         try {
-            // Write to temp file first, then atomic rename
-            Path tempFile = Paths.get(configFile.toString() + ".tmp");
+            // Write to a unique temp file first, then atomic rename; a unique 
name keeps
+            // other instances/processes from deleting or recreating it 
mid-write
+            tempFile = 
Files.createTempFile(configFile.toAbsolutePath().getParent(),
+                    configFile.getFileName() + ".", ".tmp", ownerOnly());
             OBJECT_MAPPER.writerWithDefaultPrettyPrinter()
                 .writeValue(tempFile.toFile(), cache);
             Files.move(tempFile, configFile, 
StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
+            tempFile = null;
             LOG.debug("Saved {} configurations to {}", cache.size(), 
configFile);
         } catch (IOException e) {
             LOG.error("Failed to save configurations to {}", configFile, e);
             throw new RuntimeException("Failed to save config store", e);
+        } finally {
+            if (tempFile != null) {
+                try {
+                    Files.deleteIfExists(tempFile);
+                } catch (IOException e) {
+                    LOG.warn("Failed to delete temp file {}", tempFile, e);
+                }
+            }
         }
     }
+
+    private static FileAttribute<?>[] ownerOnly() {
+        if 
(FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) {
+            return new 
FileAttribute<?>[]{PosixFilePermissions.asFileAttribute(OWNER_ONLY)};
+        }
+        return new FileAttribute<?>[0];
+    }
 }
diff --git 
a/tika-pipes/tika-pipes-core/src/test/java/org/apache/tika/pipes/core/config/ConfigStoreTest.java
 
b/tika-pipes/tika-pipes-core/src/test/java/org/apache/tika/pipes/core/config/ConfigStoreTest.java
index dbffab352f..dbaab85dc8 100644
--- 
a/tika-pipes/tika-pipes-core/src/test/java/org/apache/tika/pipes/core/config/ConfigStoreTest.java
+++ 
b/tika-pipes/tika-pipes-core/src/test/java/org/apache/tika/pipes/core/config/ConfigStoreTest.java
@@ -21,8 +21,19 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 
 import org.apache.tika.plugins.ExtensionConfig;
 
@@ -112,4 +123,21 @@ public class ConfigStoreTest {
         
         assertNull(store.get("nonexistent"));
     }
+
+    @Test
+    public void testFileBasedConfigStoreIsOwnerOnly(@TempDir Path tmp) throws 
Exception {
+        
assumeTrue(FileSystems.getDefault().supportedFileAttributeViews().contains("posix"));
+        Path file = tmp.resolve("store").resolve("config-store.json");
+        FileBasedConfigStore store = new FileBasedConfigStore(file);
+        store.init();
+        store.put("s3", new ExtensionConfig("s3", "s3-fetcher", 
"{\"secretKey\":\"hunter2\"}"));
+        store.put("http", new ExtensionConfig("http", "http-fetcher", 
"{\"password\":\"x\"}"));
+
+        Set<PosixFilePermission> perms = Files.getPosixFilePermissions(file);
+        assertEquals(Set.of(PosixFilePermission.OWNER_READ, 
PosixFilePermission.OWNER_WRITE), perms);
+        try (Stream<Path> siblings = Files.list(file.getParent())) {
+            assertEquals(List.of(file), siblings.collect(Collectors.toList()));
+        }
+        assertEquals("s3-fetcher", new 
FileBasedConfigStore(file).get("s3").name());
+    }
 }

Reply via email to