From 82376e969909038e6d62cdd4665b01a4e677f0e6 Mon Sep 17 00:00:00 2001 From: Florian Hotze Date: Fri, 28 Feb 2025 11:29:18 +0100 Subject: [PATCH] KnowledgeManagerImpl: Create temporary files in secure sub-folder instead of publicly writable /tmp Signed-off-by: Florian Hotze --- .../com/github/llamara/ai/internal/Utils.java | 31 +++++++++++++++++++ .../knowledge/KnowledgeManagerImpl.java | 30 +++++++++++++++++- .../github/llamara/ai/internal/UtilsTest.java | 27 ++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/github/llamara/ai/internal/Utils.java b/src/main/java/com/github/llamara/ai/internal/Utils.java index c6fd6e9..70dd693 100644 --- a/src/main/java/com/github/llamara/ai/internal/Utils.java +++ b/src/main/java/com/github/llamara/ai/internal/Utils.java @@ -29,6 +29,8 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import org.apache.commons.lang3.SystemProperties; + /** * Shared utilities and constants. * @@ -99,4 +101,33 @@ public static String buildAzureOpenaiEndpoint(EmbeddingModelConfig config) + ".openai.azure.com/openai/deployments/" + config.model(); } + + /** + * Checks if the host operating system is Unix-like. + * + * @return true if the host operating system is Unix-like, false + * otherwise + */ + public static boolean isOsUnix() { + String osName = SystemProperties.getOsName().toLowerCase(); + return isOsUnix(osName); + } + + /** + * Check if the given operating system is Unix-like. + * + * @param osName the name of the operating system + * @return true if the operating system is Unix-like, false otherwise + */ + public static boolean isOsUnix(String osName) { + osName = osName.toLowerCase(); + return osName.startsWith("aix") + || osName.startsWith("hp-ux") + || osName.startsWith("irix") + || osName.startsWith("linux") + || osName.startsWith("mac") + || osName.startsWith("solaris") + || osName.startsWith("sunos") + || osName.contains("bsd"); + } } diff --git a/src/main/java/com/github/llamara/ai/internal/knowledge/KnowledgeManagerImpl.java b/src/main/java/com/github/llamara/ai/internal/knowledge/KnowledgeManagerImpl.java index 290f315..1a33c92 100644 --- a/src/main/java/com/github/llamara/ai/internal/knowledge/KnowledgeManagerImpl.java +++ b/src/main/java/com/github/llamara/ai/internal/knowledge/KnowledgeManagerImpl.java @@ -22,6 +22,8 @@ import static com.github.llamara.ai.internal.Utils.generateChecksum; import com.github.llamara.ai.internal.MetadataKeys; +import com.github.llamara.ai.internal.StartupException; +import com.github.llamara.ai.internal.Utils; import com.github.llamara.ai.internal.ingestion.DocumentIngestor; import com.github.llamara.ai.internal.ingestion.IngestionStatus; import com.github.llamara.ai.internal.knowledge.embedding.EmbeddingStorePermissionMetadataManager; @@ -39,9 +41,13 @@ import java.nio.file.Files; import java.nio.file.Path; 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.Collection; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.UUID; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @@ -76,6 +82,7 @@ class KnowledgeManagerImpl implements KnowledgeManager { private static final String FILE_STORAGE_FILE_NOT_FOUND_PATTERN = "File for knowledge '%s' should exist in storage, but is missing"; + private static final Path TEMP_DIR = Path.of("/tmp/llamara/knowledge"); private final DocumentIngestor ingestor; private final EmbeddingStore embeddingStore; @@ -97,6 +104,27 @@ class KnowledgeManagerImpl implements KnowledgeManager { this.embeddingStorePermissionMetadataManager = embeddingStorePermissionMetadataManager; } + @Startup + void init() { + try { + if (Utils.isOsUnix()) { + FileAttribute> attr = + PosixFilePermissions.asFileAttribute( + PosixFilePermissions.fromString("rwx------")); + Files.createDirectories(TEMP_DIR, attr); + } else { + File f = Files.createDirectories(TEMP_DIR).toFile(); + if (!f.setReadable(true, true) + || !f.setWritable(true, true) + || !f.setExecutable(true, true)) { + throw new StartupException("Failed to set permissions on temporary directory"); + } + } + } catch (IOException e) { + throw new StartupException("Failed to create temporary directory", e); + } + } + @Override public Collection getAllKnowledge() { return repository.listAll(); @@ -352,7 +380,7 @@ public void retryFailedIngestion(UUID id) NamedFileContainer fc = getFile(id); File tempFile; try { - tempFile = File.createTempFile("knowledge_" + id, null); + tempFile = File.createTempFile(id.toString(), null, TEMP_DIR.toFile()); tempFile.deleteOnExit(); // the file will be deleted when the JVM exits Files.copy(fc.content(), tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { diff --git a/src/test/java/com/github/llamara/ai/internal/UtilsTest.java b/src/test/java/com/github/llamara/ai/internal/UtilsTest.java index 8f5d2e5..cbb3663 100644 --- a/src/test/java/com/github/llamara/ai/internal/UtilsTest.java +++ b/src/test/java/com/github/llamara/ai/internal/UtilsTest.java @@ -28,11 +28,15 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; /** Tests for {@link Utils}. */ class UtilsTest { @@ -112,4 +116,27 @@ void buildAzureOpenAiEndpointThrowsIfResourceNameMissingForEmbeddingModelConfig( assertThrows( IllegalArgumentException.class, () -> Utils.buildAzureOpenaiEndpoint(modelConfig)); } + + @ParameterizedTest + @CsvSource({ + "AIX", + "HP-UX", + "Irix", + "Linux", + "Mac OS X", + "Solaris", + "SunOS", + "FreeBSD", + "OpenBSD", + "NetBSD" + }) + void isOsUnixReturnsTrueIfOnUnix(String osName) { + assertTrue(Utils.isOsUnix(osName)); + } + + @ParameterizedTest + @CsvSource({"Windows 10", "Windows 11", "OS/2", "OS/400", "z/OS"}) + void isOsUnixReturnsFalseIfNotOnUnix(String osName) { + assertFalse(Utils.isOsUnix(osName)); + } }