diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/EntitlementBootstrap.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/EntitlementBootstrap.java
index 13ca29886a028..905cce08e2d8f 100644
--- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/EntitlementBootstrap.java
+++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/EntitlementBootstrap.java
@@ -15,8 +15,11 @@
 import com.sun.tools.attach.VirtualMachine;
 
 import org.elasticsearch.core.Nullable;
+import org.elasticsearch.core.PathUtils;
 import org.elasticsearch.core.SuppressForbidden;
 import org.elasticsearch.entitlement.initialization.EntitlementInitialization;
+import org.elasticsearch.entitlement.runtime.policy.PathLookup;
+import org.elasticsearch.entitlement.runtime.policy.PathLookupImpl;
 import org.elasticsearch.entitlement.runtime.policy.Policy;
 import org.elasticsearch.logging.LogManager;
 import org.elasticsearch.logging.Logger;
@@ -37,35 +40,15 @@ public record BootstrapArgs(
         @Nullable Policy serverPolicyPatch,
         Map<String, Policy> pluginPolicies,
         Function<Class<?>, String> pluginResolver,
-        Function<String, Stream<String>> settingResolver,
-        Path[] dataDirs,
-        Path[] sharedRepoDirs,
-        Path configDir,
-        Path libDir,
-        Path modulesDir,
-        Path pluginsDir,
+        PathLookup pathLookup,
         Map<String, Path> sourcePaths,
-        Path logsDir,
-        Path tempDir,
-        Path pidFile,
         Set<Class<?>> suppressFailureLogClasses
     ) {
         public BootstrapArgs {
             requireNonNull(pluginPolicies);
             requireNonNull(pluginResolver);
-            requireNonNull(settingResolver);
-            requireNonNull(dataDirs);
-            if (dataDirs.length == 0) {
-                throw new IllegalArgumentException("must provide at least one data directory");
-            }
-            requireNonNull(sharedRepoDirs);
-            requireNonNull(configDir);
-            requireNonNull(libDir);
-            requireNonNull(modulesDir);
-            requireNonNull(pluginsDir);
+            requireNonNull(pathLookup);
             requireNonNull(sourcePaths);
-            requireNonNull(logsDir);
-            requireNonNull(tempDir);
             requireNonNull(suppressFailureLogClasses);
         }
     }
@@ -121,23 +104,34 @@ public static void bootstrap(
             serverPolicyPatch,
             pluginPolicies,
             pluginResolver,
-            settingResolver,
-            dataDirs,
-            sharedRepoDirs,
-            configDir,
-            libDir,
-            modulesDir,
-            pluginsDir,
+            new PathLookupImpl(
+                getUserHome(),
+                configDir,
+                dataDirs,
+                sharedRepoDirs,
+                libDir,
+                modulesDir,
+                pluginsDir,
+                logsDir,
+                tempDir,
+                pidFile,
+                settingResolver
+            ),
             sourcePaths,
-            logsDir,
-            tempDir,
-            pidFile,
             suppressFailureLogClasses
         );
         exportInitializationToAgent();
         loadAgent(findAgentJar());
     }
 
+    private static Path getUserHome() {
+        String userHome = System.getProperty("user.home");
+        if (userHome == null) {
+            throw new IllegalStateException("user.home system property is required");
+        }
+        return PathUtils.get(userHome);
+    }
+
     @SuppressForbidden(reason = "The VirtualMachine API is the only way to attach a java agent dynamically")
     private static void loadAgent(String agentPath) {
         try {
diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java
index 911eff0615f73..74f16cf01e911 100644
--- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java
+++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java
@@ -58,9 +58,9 @@
 import java.nio.file.attribute.FileAttribute;
 import java.nio.file.spi.FileSystemProvider;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -70,10 +70,14 @@
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 
+import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.CONFIG;
+import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.DATA;
+import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.LIB;
+import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.LOGS;
+import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.MODULES;
+import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.PLUGINS;
+import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.SHARED_REPO;
 import static org.elasticsearch.entitlement.runtime.policy.Platform.LINUX;
-import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.BaseDir.CONFIG;
-import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.BaseDir.DATA;
-import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.BaseDir.SHARED_REPO;
 import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ;
 import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ_WRITE;
 
@@ -165,27 +169,20 @@ private static Class<?>[] findClassesToRetransform(Class<?>[] loadedClasses, Set
     private static PolicyManager createPolicyManager() {
         EntitlementBootstrap.BootstrapArgs bootstrapArgs = EntitlementBootstrap.bootstrapArgs();
         Map<String, Policy> pluginPolicies = bootstrapArgs.pluginPolicies();
-        var pathLookup = new PathLookup(
-            getUserHome(),
-            bootstrapArgs.configDir(),
-            bootstrapArgs.dataDirs(),
-            bootstrapArgs.sharedRepoDirs(),
-            bootstrapArgs.tempDir(),
-            bootstrapArgs.settingResolver()
-        );
+        PathLookup pathLookup = bootstrapArgs.pathLookup();
 
         List<Scope> serverScopes = new ArrayList<>();
         List<FileData> serverModuleFileDatas = new ArrayList<>();
         Collections.addAll(
             serverModuleFileDatas,
             // Base ES directories
-            FileData.ofPath(bootstrapArgs.pluginsDir(), READ),
-            FileData.ofPath(bootstrapArgs.modulesDir(), READ),
-            FileData.ofPath(bootstrapArgs.configDir(), READ),
-            FileData.ofPath(bootstrapArgs.logsDir(), READ_WRITE),
-            FileData.ofPath(bootstrapArgs.libDir(), READ),
-            FileData.ofRelativePath(Path.of(""), DATA, READ_WRITE),
-            FileData.ofRelativePath(Path.of(""), SHARED_REPO, READ_WRITE),
+            FileData.ofBaseDirPath(PLUGINS, READ),
+            FileData.ofBaseDirPath(MODULES, READ),
+            FileData.ofBaseDirPath(CONFIG, READ),
+            FileData.ofBaseDirPath(LOGS, READ_WRITE),
+            FileData.ofBaseDirPath(LIB, READ),
+            FileData.ofBaseDirPath(DATA, READ_WRITE),
+            FileData.ofBaseDirPath(SHARED_REPO, READ_WRITE),
             // exclusive settings file
             FileData.ofRelativePath(Path.of("operator/settings.json"), CONFIG, READ_WRITE).withExclusive(true),
             // OS release on Linux
@@ -206,8 +203,8 @@ private static PolicyManager createPolicyManager() {
             FileData.ofPath(Path.of("/proc/self/mountinfo"), READ).withPlatform(LINUX),
             FileData.ofPath(Path.of("/proc/diskstats"), READ).withPlatform(LINUX)
         );
-        if (bootstrapArgs.pidFile() != null) {
-            serverModuleFileDatas.add(FileData.ofPath(bootstrapArgs.pidFile(), READ_WRITE));
+        if (pathLookup.pidFile() != null) {
+            serverModuleFileDatas.add(FileData.ofPath(pathLookup.pidFile(), READ_WRITE));
         }
 
         Collections.addAll(
@@ -219,8 +216,8 @@ private static PolicyManager createPolicyManager() {
                     new FilesEntitlement(
                         List.of(
                             // TODO: what in es.base is accessing shared repo?
-                            FileData.ofRelativePath(Path.of(""), SHARED_REPO, READ_WRITE),
-                            FileData.ofRelativePath(Path.of(""), DATA, READ_WRITE)
+                            FileData.ofBaseDirPath(SHARED_REPO, READ_WRITE),
+                            FileData.ofBaseDirPath(DATA, READ_WRITE)
                         )
                     )
                 )
@@ -245,25 +242,17 @@ private static PolicyManager createPolicyManager() {
                 List.of(
                     new LoadNativeLibrariesEntitlement(),
                     new ManageThreadsEntitlement(),
-                    new FilesEntitlement(
-                        List.of(FileData.ofPath(bootstrapArgs.configDir(), READ), FileData.ofRelativePath(Path.of(""), DATA, READ_WRITE))
-                    )
+                    new FilesEntitlement(List.of(FileData.ofBaseDirPath(CONFIG, READ), FileData.ofBaseDirPath(DATA, READ_WRITE)))
                 )
             ),
-            new Scope(
-                "org.apache.lucene.misc",
-                List.of(new FilesEntitlement(List.of(FileData.ofRelativePath(Path.of(""), DATA, READ_WRITE))))
-            ),
+            new Scope("org.apache.lucene.misc", List.of(new FilesEntitlement(List.of(FileData.ofBaseDirPath(DATA, READ_WRITE))))),
             new Scope(
                 "org.apache.logging.log4j.core",
-                List.of(new ManageThreadsEntitlement(), new FilesEntitlement(List.of(FileData.ofPath(bootstrapArgs.logsDir(), READ_WRITE))))
+                List.of(new ManageThreadsEntitlement(), new FilesEntitlement(List.of(FileData.ofBaseDirPath(LOGS, READ_WRITE))))
             ),
             new Scope(
                 "org.elasticsearch.nativeaccess",
-                List.of(
-                    new LoadNativeLibrariesEntitlement(),
-                    new FilesEntitlement(List.of(FileData.ofRelativePath(Path.of(""), DATA, READ_WRITE)))
-                )
+                List.of(new LoadNativeLibrariesEntitlement(), new FilesEntitlement(List.of(FileData.ofBaseDirPath(DATA, READ_WRITE))))
             )
         );
 
@@ -288,7 +277,7 @@ private static PolicyManager createPolicyManager() {
                 new Scope(
                     "org.bouncycastle.fips.core",
                     // read to lib dir is required for checksum validation
-                    List.of(new FilesEntitlement(List.of(FileData.ofPath(bootstrapArgs.libDir(), READ))), new ManageThreadsEntitlement())
+                    List.of(new FilesEntitlement(List.of(FileData.ofBaseDirPath(LIB, READ))), new ManageThreadsEntitlement())
                 )
             );
         }
@@ -312,21 +301,14 @@ private static PolicyManager createPolicyManager() {
             new LoadNativeLibrariesEntitlement(),
             new FilesEntitlement(
                 List.of(
-                    FileData.ofPath(bootstrapArgs.logsDir(), READ_WRITE),
+                    FileData.ofBaseDirPath(LOGS, READ_WRITE),
                     FileData.ofPath(Path.of("/proc/meminfo"), READ),
                     FileData.ofPath(Path.of("/sys/fs/cgroup/"), READ)
                 )
             )
         );
 
-        validateFilesEntitlements(
-            pluginPolicies,
-            pathLookup,
-            bootstrapArgs.configDir(),
-            bootstrapArgs.pluginsDir(),
-            bootstrapArgs.modulesDir(),
-            bootstrapArgs.libDir()
-        );
+        validateFilesEntitlements(pluginPolicies, pathLookup);
 
         return new PolicyManager(
             serverPolicy,
@@ -341,21 +323,14 @@ private static PolicyManager createPolicyManager() {
         );
     }
 
-    private static Set<Path> pathSet(Path... paths) {
-        return Arrays.stream(paths).map(x -> x.toAbsolutePath().normalize()).collect(Collectors.toUnmodifiableSet());
-    }
-
     // package visible for tests
-    static void validateFilesEntitlements(
-        Map<String, Policy> pluginPolicies,
-        PathLookup pathLookup,
-        Path configDir,
-        Path pluginsDir,
-        Path modulesDir,
-        Path libDir
-    ) {
-        var readAccessForbidden = pathSet(pluginsDir, modulesDir, libDir);
-        var writeAccessForbidden = pathSet(configDir);
+    static void validateFilesEntitlements(Map<String, Policy> pluginPolicies, PathLookup pathLookup) {
+        Set<Path> readAccessForbidden = new HashSet<>();
+        pathLookup.getBaseDirPaths(PLUGINS).forEach(p -> readAccessForbidden.add(p.toAbsolutePath().normalize()));
+        pathLookup.getBaseDirPaths(MODULES).forEach(p -> readAccessForbidden.add(p.toAbsolutePath().normalize()));
+        pathLookup.getBaseDirPaths(LIB).forEach(p -> readAccessForbidden.add(p.toAbsolutePath().normalize()));
+        Set<Path> writeAccessForbidden = new HashSet<>();
+        pathLookup.getBaseDirPaths(CONFIG).forEach(p -> writeAccessForbidden.add(p.toAbsolutePath().normalize()));
         for (var pluginPolicy : pluginPolicies.entrySet()) {
             for (var scope : pluginPolicy.getValue().scopes()) {
                 var filesEntitlement = scope.entitlements()
diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java
index cae5ddda6eb03..58616f23fb4ea 100644
--- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java
+++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java
@@ -34,6 +34,8 @@
 import static java.util.Comparator.comparing;
 import static org.elasticsearch.core.PathUtils.getDefaultFileSystem;
 import static org.elasticsearch.entitlement.runtime.policy.FileUtils.PATH_ORDER;
+import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.CONFIG;
+import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.TEMP;
 import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ_WRITE;
 
 public final class FileAccessTree {
@@ -167,9 +169,9 @@ private FileAccessTree(FilesEntitlement filesEntitlement, PathLookup pathLookup,
         }
 
         // everything has access to the temp dir, config dir, to their own dir (their own jar files) and the jdk
-        addPathAndMaybeLink.accept(pathLookup.tempDir(), READ_WRITE);
+        pathLookup.getBaseDirPaths(TEMP).forEach(tempPath -> addPathAndMaybeLink.accept(tempPath, READ_WRITE));
         // TODO: this grants read access to the config dir for all modules until explicit read entitlements can be added
-        addPathAndMaybeLink.accept(pathLookup.configDir(), Mode.READ);
+        pathLookup.getBaseDirPaths(CONFIG).forEach(configPath -> addPathAndMaybeLink.accept(configPath, Mode.READ));
         if (componentPath != null) {
             addPathAndMaybeLink.accept(componentPath, Mode.READ);
         }
diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookup.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookup.java
index 5cbe6108e009c..0781ee3a92059 100644
--- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookup.java
+++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookup.java
@@ -10,14 +10,29 @@
 package org.elasticsearch.entitlement.runtime.policy;
 
 import java.nio.file.Path;
-import java.util.function.Function;
 import java.util.stream.Stream;
 
-public record PathLookup(
-    Path homeDir,
-    Path configDir,
-    Path[] dataDirs,
-    Path[] sharedRepoDirs,
-    Path tempDir,
-    Function<String, Stream<String>> settingResolver
-) {}
+/**
+ * Resolves paths for known directories checked by entitlements.
+ */
+public interface PathLookup {
+    enum BaseDir {
+        USER_HOME,
+        CONFIG,
+        DATA,
+        SHARED_REPO,
+        LIB,
+        MODULES,
+        PLUGINS,
+        LOGS,
+        TEMP
+    }
+
+    Path pidFile();
+
+    Stream<Path> getBaseDirPaths(BaseDir baseDir);
+
+    Stream<Path> resolveRelativePaths(BaseDir baseDir, Path relativePath);
+
+    Stream<Path> resolveSettingPaths(BaseDir baseDir, String settingName);
+}
diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookupImpl.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookupImpl.java
new file mode 100644
index 0000000000000..59ca7fd9c641c
--- /dev/null
+++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookupImpl.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the "Elastic License
+ * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
+ * Public License v 1"; you may not use this file except in compliance with, at
+ * your election, the "Elastic License 2.0", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
+ */
+
+package org.elasticsearch.entitlement.runtime.policy;
+
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Standard manager for resolving known paths.
+ */
+public record PathLookupImpl(
+    Path homeDir,
+    Path configDir,
+    Path[] dataDirs,
+    Path[] sharedRepoDirs,
+    Path libDir,
+    Path modulesDir,
+    Path pluginsDir,
+    Path logsDir,
+    Path tempDir,
+    Path pidFile,
+    Function<String, Stream<String>> settingResolver
+) implements PathLookup {
+
+    public PathLookupImpl {
+        requireNonNull(homeDir);
+        requireNonNull(dataDirs);
+        if (dataDirs.length == 0) {
+            throw new IllegalArgumentException("must provide at least one data directory");
+        }
+        requireNonNull(sharedRepoDirs);
+        requireNonNull(configDir);
+        requireNonNull(libDir);
+        requireNonNull(modulesDir);
+        requireNonNull(pluginsDir);
+        requireNonNull(logsDir);
+        requireNonNull(tempDir);
+        requireNonNull(settingResolver);
+    }
+
+    @Override
+    public Stream<Path> getBaseDirPaths(BaseDir baseDir) {
+        return switch (baseDir) {
+            case USER_HOME -> Stream.of(homeDir);
+            case DATA -> Arrays.stream(dataDirs);
+            case SHARED_REPO -> Arrays.stream(sharedRepoDirs);
+            case CONFIG -> Stream.of(configDir);
+            case LIB -> Stream.of(libDir);
+            case MODULES -> Stream.of(modulesDir);
+            case PLUGINS -> Stream.of(pluginsDir);
+            case LOGS -> Stream.of(logsDir);
+            case TEMP -> Stream.of(tempDir);
+        };
+    }
+
+    @Override
+    public Stream<Path> resolveRelativePaths(BaseDir baseDir, Path relativePath) {
+        return getBaseDirPaths(baseDir).map(path -> path.resolve(relativePath));
+    }
+
+    @Override
+    public Stream<Path> resolveSettingPaths(BaseDir baseDir, String settingName) {
+        List<Path> relativePaths = settingResolver.apply(settingName)
+            .filter(s -> s.toLowerCase(Locale.ROOT).startsWith("https://") == false)
+            .distinct()
+            .map(Path::of)
+            .toList();
+        return getBaseDirPaths(baseDir).flatMap(path -> relativePaths.stream().map(path::resolve));
+    }
+}
diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java
index 71c0f9909d3e3..5461a033f8621 100644
--- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java
+++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java
@@ -58,6 +58,7 @@
 import static java.util.zip.ZipFile.OPEN_DELETE;
 import static java.util.zip.ZipFile.OPEN_READ;
 import static org.elasticsearch.entitlement.bridge.Util.NO_CLASS;
+import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.TEMP;
 
 public class PolicyManager {
     /**
@@ -432,7 +433,9 @@ public void checkFileWrite(Class<?> callerClass, Path path) {
     }
 
     public void checkCreateTempFile(Class<?> callerClass) {
-        checkFileWrite(callerClass, pathLookup.tempDir());
+        // in production there should only ever be a single temp directory
+        // so we can safely assume we only need to check the sole element in this stream
+        checkFileWrite(callerClass, pathLookup.getBaseDirPaths(TEMP).findFirst().get());
     }
 
     @SuppressForbidden(reason = "Explicitly checking File apis")
diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlement.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlement.java
index 74afdb2e572f2..b15280a9279b6 100644
--- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlement.java
+++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlement.java
@@ -13,6 +13,7 @@
 import org.elasticsearch.entitlement.runtime.policy.ExternalEntitlement;
 import org.elasticsearch.entitlement.runtime.policy.FileUtils;
 import org.elasticsearch.entitlement.runtime.policy.PathLookup;
+import org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir;
 import org.elasticsearch.entitlement.runtime.policy.Platform;
 import org.elasticsearch.entitlement.runtime.policy.PolicyValidationException;
 
@@ -21,9 +22,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
-import java.util.Objects;
 import java.util.function.BiFunction;
 import java.util.stream.Stream;
 
@@ -41,13 +40,6 @@ public enum Mode {
         READ_WRITE
     }
 
-    public enum BaseDir {
-        CONFIG,
-        DATA,
-        SHARED_REPO,
-        HOME
-    }
-
     public sealed interface FileData {
 
         Stream<Path> resolvePaths(PathLookup pathLookup);
@@ -68,6 +60,10 @@ static FileData ofPath(Path path, Mode mode) {
             return new AbsolutePathFileData(path, mode, null, false);
         }
 
+        static FileData ofBaseDirPath(BaseDir baseDir, Mode mode) {
+            return new RelativePathFileData(Path.of(""), baseDir, mode, null, false);
+        }
+
         static FileData ofRelativePath(Path relativePath, BaseDir baseDir, Mode mode) {
             return new RelativePathFileData(relativePath, baseDir, mode, null, false);
         }
@@ -77,41 +73,6 @@ static FileData ofPathSetting(String setting, BaseDir baseDir, Mode mode) {
         }
     }
 
-    private sealed interface RelativeFileData extends FileData {
-        BaseDir baseDir();
-
-        Stream<Path> resolveRelativePaths(PathLookup pathLookup);
-
-        @Override
-        default Stream<Path> resolvePaths(PathLookup pathLookup) {
-            Objects.requireNonNull(pathLookup);
-            var relativePaths = resolveRelativePaths(pathLookup);
-            switch (baseDir()) {
-                case CONFIG:
-                    return relativePaths.map(relativePath -> pathLookup.configDir().resolve(relativePath));
-                case DATA:
-                    return relativePathsCombination(pathLookup.dataDirs(), relativePaths);
-                case SHARED_REPO:
-                    return relativePathsCombination(pathLookup.sharedRepoDirs(), relativePaths);
-                case HOME:
-                    return relativePaths.map(relativePath -> pathLookup.homeDir().resolve(relativePath));
-                default:
-                    throw new IllegalArgumentException();
-            }
-        }
-    }
-
-    private static Stream<Path> relativePathsCombination(Path[] baseDirs, Stream<Path> relativePaths) {
-        // multiple base dirs are a pain...we need the combination of the base dirs and relative paths
-        List<Path> paths = new ArrayList<>();
-        for (var relativePath : relativePaths.toList()) {
-            for (var dataDir : baseDirs) {
-                paths.add(dataDir.resolve(relativePath));
-            }
-        }
-        return paths.stream();
-    }
-
     private record AbsolutePathFileData(Path path, Mode mode, Platform platform, boolean exclusive) implements FileData {
 
         @Override
@@ -119,11 +80,6 @@ public AbsolutePathFileData withExclusive(boolean exclusive) {
             return new AbsolutePathFileData(path, mode, platform, exclusive);
         }
 
-        @Override
-        public Stream<Path> resolvePaths(PathLookup pathLookup) {
-            return Stream.of(path);
-        }
-
         @Override
         public FileData withPlatform(Platform platform) {
             if (platform == platform()) {
@@ -132,6 +88,11 @@ public FileData withPlatform(Platform platform) {
             return new AbsolutePathFileData(path, mode, platform, exclusive);
         }
 
+        @Override
+        public Stream<Path> resolvePaths(PathLookup pathLookup) {
+            return Stream.of(path);
+        }
+
         @Override
         public String description() {
             return Strings.format("[%s] %s%s", mode, path.toAbsolutePath().normalize(), exclusive ? " (exclusive)" : "");
@@ -140,19 +101,13 @@ public String description() {
 
     private record RelativePathFileData(Path relativePath, BaseDir baseDir, Mode mode, Platform platform, boolean exclusive)
         implements
-            FileData,
-            RelativeFileData {
+            FileData {
 
         @Override
         public RelativePathFileData withExclusive(boolean exclusive) {
             return new RelativePathFileData(relativePath, baseDir, mode, platform, exclusive);
         }
 
-        @Override
-        public Stream<Path> resolveRelativePaths(PathLookup pathLookup) {
-            return Stream.of(relativePath);
-        }
-
         @Override
         public FileData withPlatform(Platform platform) {
             if (platform == platform()) {
@@ -161,6 +116,11 @@ public FileData withPlatform(Platform platform) {
             return new RelativePathFileData(relativePath, baseDir, mode, platform, exclusive);
         }
 
+        @Override
+        public Stream<Path> resolvePaths(PathLookup pathLookup) {
+            return pathLookup.resolveRelativePaths(baseDir, relativePath);
+        }
+
         @Override
         public String description() {
             return Strings.format("[%s] <%s>%s%s%s", mode, baseDir, SEPARATOR, relativePath, exclusive ? " (exclusive)" : "");
@@ -169,22 +129,13 @@ public String description() {
 
     private record PathSettingFileData(String setting, BaseDir baseDir, Mode mode, Platform platform, boolean exclusive)
         implements
-            RelativeFileData {
+            FileData {
 
         @Override
         public PathSettingFileData withExclusive(boolean exclusive) {
             return new PathSettingFileData(setting, baseDir, mode, platform, exclusive);
         }
 
-        @Override
-        public Stream<Path> resolveRelativePaths(PathLookup pathLookup) {
-            Stream<String> result = pathLookup.settingResolver()
-                .apply(setting)
-                .filter(s -> s.toLowerCase(Locale.ROOT).startsWith("https://") == false)
-                .distinct();
-            return result.map(Path::of);
-        }
-
         @Override
         public FileData withPlatform(Platform platform) {
             if (platform == platform()) {
@@ -193,6 +144,11 @@ public FileData withPlatform(Platform platform) {
             return new PathSettingFileData(setting, baseDir, mode, platform, exclusive);
         }
 
+        @Override
+        public Stream<Path> resolvePaths(PathLookup pathLookup) {
+            return pathLookup.resolveSettingPaths(baseDir, setting);
+        }
+
         @Override
         public String description() {
             return Strings.format("[%s] <%s>%s<%s>%s", mode, baseDir, SEPARATOR, setting, exclusive ? " (exclusive)" : "");
@@ -225,7 +181,7 @@ private static BaseDir parseBaseDir(String baseDir) {
         return switch (baseDir) {
             case "config" -> BaseDir.CONFIG;
             case "data" -> BaseDir.DATA;
-            case "home" -> BaseDir.HOME;
+            case "home" -> BaseDir.USER_HOME;
             // NOTE: shared_repo is _not_ accessible to policy files, only internally
             default -> throw new PolicyValidationException(
                 "invalid relative directory: " + baseDir + ", valid values: [config, data, home]"
diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/initialization/EntitlementInitializationTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/initialization/EntitlementInitializationTests.java
index 80c7d0d77d449..6bbcec9cc400a 100644
--- a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/initialization/EntitlementInitializationTests.java
+++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/initialization/EntitlementInitializationTests.java
@@ -11,6 +11,7 @@
 
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.entitlement.runtime.policy.PathLookup;
+import org.elasticsearch.entitlement.runtime.policy.PathLookupImpl;
 import org.elasticsearch.entitlement.runtime.policy.Policy;
 import org.elasticsearch.entitlement.runtime.policy.Scope;
 import org.elasticsearch.entitlement.runtime.policy.entitlements.CreateClassLoaderEntitlement;
@@ -33,7 +34,6 @@ public class EntitlementInitializationTests extends ESTestCase {
     private static Path TEST_CONFIG_DIR;
 
     private static Path TEST_PLUGINS_DIR;
-    private static Path TEST_MODULES_DIR;
     private static Path TEST_LIBS_DIR;
 
     @BeforeClass
@@ -42,15 +42,19 @@ public static void beforeClass() {
             Path testBaseDir = createTempDir().toAbsolutePath();
             TEST_CONFIG_DIR = testBaseDir.resolve("config");
             TEST_PLUGINS_DIR = testBaseDir.resolve("plugins");
-            TEST_MODULES_DIR = testBaseDir.resolve("modules");
             TEST_LIBS_DIR = testBaseDir.resolve("libs");
 
-            TEST_PATH_LOOKUP = new PathLookup(
+            TEST_PATH_LOOKUP = new PathLookupImpl(
                 testBaseDir.resolve("user/home"),
                 TEST_CONFIG_DIR,
                 new Path[] { testBaseDir.resolve("data1"), testBaseDir.resolve("data2") },
                 new Path[] { testBaseDir.resolve("shared1"), testBaseDir.resolve("shared2") },
+                TEST_LIBS_DIR,
+                testBaseDir.resolve("modules"),
+                TEST_PLUGINS_DIR,
+                testBaseDir.resolve("logs"),
                 testBaseDir.resolve("temp"),
+                null,
                 Settings.EMPTY::getValues
             );
         } catch (Exception e) {
@@ -71,14 +75,7 @@ public void testValidationPass() {
                 )
             )
         );
-        EntitlementInitialization.validateFilesEntitlements(
-            Map.of("plugin", policy),
-            TEST_PATH_LOOKUP,
-            TEST_CONFIG_DIR,
-            TEST_PLUGINS_DIR,
-            TEST_MODULES_DIR,
-            TEST_LIBS_DIR
-        );
+        EntitlementInitialization.validateFilesEntitlements(Map.of("plugin", policy), TEST_PATH_LOOKUP);
     }
 
     public void testValidationFailForRead() {
@@ -97,14 +94,7 @@ public void testValidationFailForRead() {
 
         var ex = expectThrows(
             IllegalArgumentException.class,
-            () -> EntitlementInitialization.validateFilesEntitlements(
-                Map.of("plugin", policy),
-                TEST_PATH_LOOKUP,
-                TEST_CONFIG_DIR,
-                TEST_PLUGINS_DIR,
-                TEST_MODULES_DIR,
-                TEST_LIBS_DIR
-            )
+            () -> EntitlementInitialization.validateFilesEntitlements(Map.of("plugin", policy), TEST_PATH_LOOKUP)
         );
         assertThat(
             ex.getMessage(),
@@ -129,14 +119,7 @@ public void testValidationFailForRead() {
 
         ex = expectThrows(
             IllegalArgumentException.class,
-            () -> EntitlementInitialization.validateFilesEntitlements(
-                Map.of("plugin2", policy2),
-                TEST_PATH_LOOKUP,
-                TEST_CONFIG_DIR,
-                TEST_PLUGINS_DIR,
-                TEST_MODULES_DIR,
-                TEST_LIBS_DIR
-            )
+            () -> EntitlementInitialization.validateFilesEntitlements(Map.of("plugin2", policy2), TEST_PATH_LOOKUP)
         );
         assertThat(
             ex.getMessage(),
@@ -162,14 +145,7 @@ public void testValidationFailForWrite() {
 
         var ex = expectThrows(
             IllegalArgumentException.class,
-            () -> EntitlementInitialization.validateFilesEntitlements(
-                Map.of("plugin", policy),
-                TEST_PATH_LOOKUP,
-                TEST_CONFIG_DIR,
-                TEST_PLUGINS_DIR,
-                TEST_MODULES_DIR,
-                TEST_LIBS_DIR
-            )
+            () -> EntitlementInitialization.validateFilesEntitlements(Map.of("plugin", policy), TEST_PATH_LOOKUP)
         );
         assertThat(
             ex.getMessage(),
diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeTests.java
index faa6424eabfc0..b19123c4e9311 100644
--- a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeTests.java
+++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeTests.java
@@ -31,6 +31,8 @@
 import static org.elasticsearch.core.PathUtils.getDefaultFileSystem;
 import static org.elasticsearch.entitlement.runtime.policy.FileAccessTree.buildExclusivePathList;
 import static org.elasticsearch.entitlement.runtime.policy.FileAccessTree.normalizePath;
+import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.CONFIG;
+import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.TEMP;
 import static org.elasticsearch.entitlement.runtime.policy.Platform.WINDOWS;
 import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ;
 import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ_WRITE;
@@ -53,12 +55,17 @@ private static Path path(String s) {
         return root.resolve(s);
     }
 
-    private static final PathLookup TEST_PATH_LOOKUP = new PathLookup(
+    private static final PathLookup TEST_PATH_LOOKUP = new PathLookupImpl(
         Path.of("/home"),
         Path.of("/config"),
         new Path[] { Path.of("/data1"), Path.of("/data2") },
         new Path[] { Path.of("/shared1"), Path.of("/shared2") },
+        Path.of("/lib"),
+        Path.of("/modules"),
+        Path.of("/plugins"),
+        Path.of("/logs"),
         Path.of("/tmp"),
+        null,
         pattern -> settings.getValues(pattern)
     );
 
@@ -293,14 +300,14 @@ public void testFollowLinks() throws IOException {
 
     public void testTempDirAccess() {
         var tree = FileAccessTree.of("test-component", "test-module", FilesEntitlement.EMPTY, TEST_PATH_LOOKUP, null, List.of());
-        assertThat(tree.canRead(TEST_PATH_LOOKUP.tempDir()), is(true));
-        assertThat(tree.canWrite(TEST_PATH_LOOKUP.tempDir()), is(true));
+        assertThat(tree.canRead(TEST_PATH_LOOKUP.resolveRelativePaths(TEMP, Path.of("")).findFirst().get()), is(true));
+        assertThat(tree.canWrite(TEST_PATH_LOOKUP.resolveRelativePaths(TEMP, Path.of("")).findFirst().get()), is(true));
     }
 
     public void testConfigDirAccess() {
         var tree = FileAccessTree.of("test-component", "test-module", FilesEntitlement.EMPTY, TEST_PATH_LOOKUP, null, List.of());
-        assertThat(tree.canRead(TEST_PATH_LOOKUP.configDir()), is(true));
-        assertThat(tree.canWrite(TEST_PATH_LOOKUP.configDir()), is(false));
+        assertThat(tree.canRead(TEST_PATH_LOOKUP.resolveRelativePaths(CONFIG, Path.of("")).findFirst().get()), is(true));
+        assertThat(tree.canWrite(TEST_PATH_LOOKUP.resolveRelativePaths(CONFIG, Path.of("")).findFirst().get()), is(false));
     }
 
     public void testBasicExclusiveAccess() {
diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyManagerTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyManagerTests.java
index 4e8ac7c547984..2caa7f1715de7 100644
--- a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyManagerTests.java
+++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyManagerTests.java
@@ -69,12 +69,17 @@ public static void beforeClass() {
             NO_ENTITLEMENTS_MODULE = makeClassInItsOwnModule().getModule();
 
             TEST_BASE_DIR = createTempDir().toAbsolutePath();
-            TEST_PATH_LOOKUP = new PathLookup(
+            TEST_PATH_LOOKUP = new PathLookupImpl(
                 TEST_BASE_DIR.resolve("/user/home"),
                 TEST_BASE_DIR.resolve("/config"),
                 new Path[] { TEST_BASE_DIR.resolve("/data1/"), TEST_BASE_DIR.resolve("/data2") },
                 new Path[] { TEST_BASE_DIR.resolve("/shared1"), TEST_BASE_DIR.resolve("/shared2") },
-                TEST_BASE_DIR.resolve("/temp"),
+                TEST_BASE_DIR.resolve("/lib"),
+                TEST_BASE_DIR.resolve("/modules"),
+                TEST_BASE_DIR.resolve("/plugins"),
+                TEST_BASE_DIR.resolve("/logs"),
+                TEST_BASE_DIR.resolve("/tmp"),
+                null,
                 Settings.EMPTY::getValues
             );
         } catch (Exception e) {
diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyUtilsTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyUtilsTests.java
index 8ee0ce3736a8d..5fb07cad2a1a1 100644
--- a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyUtilsTests.java
+++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyUtilsTests.java
@@ -215,7 +215,7 @@ public void testMergeFilesEntitlement() {
             List.of(
                 FilesEntitlement.FileData.ofPath(Path.of("/a/b"), FilesEntitlement.Mode.READ),
                 FilesEntitlement.FileData.ofPath(Path.of("/a/c"), FilesEntitlement.Mode.READ_WRITE),
-                FilesEntitlement.FileData.ofRelativePath(Path.of("c/d"), FilesEntitlement.BaseDir.CONFIG, FilesEntitlement.Mode.READ)
+                FilesEntitlement.FileData.ofRelativePath(Path.of("c/d"), PathLookup.BaseDir.CONFIG, FilesEntitlement.Mode.READ)
             )
         );
         var e2 = new FilesEntitlement(
@@ -235,7 +235,7 @@ public void testMergeFilesEntitlement() {
                     FilesEntitlement.FileData.ofPath(Path.of("/a/b"), FilesEntitlement.Mode.READ),
                     FilesEntitlement.FileData.ofPath(Path.of("/a/c"), FilesEntitlement.Mode.READ),
                     FilesEntitlement.FileData.ofPath(Path.of("/a/c"), FilesEntitlement.Mode.READ_WRITE),
-                    FilesEntitlement.FileData.ofRelativePath(Path.of("c/d"), FilesEntitlement.BaseDir.CONFIG, FilesEntitlement.Mode.READ),
+                    FilesEntitlement.FileData.ofRelativePath(Path.of("c/d"), PathLookup.BaseDir.CONFIG, FilesEntitlement.Mode.READ),
                     FilesEntitlement.FileData.ofPath(Path.of("/c/d"), FilesEntitlement.Mode.READ)
                 )
             )
@@ -328,7 +328,7 @@ public void testFormatFilesEntitlement() {
                         new FilesEntitlement(
                             List.of(
                                 FilesEntitlement.FileData.ofPath(pathAB, FilesEntitlement.Mode.READ_WRITE),
-                                FilesEntitlement.FileData.ofRelativePath(pathCD, FilesEntitlement.BaseDir.DATA, FilesEntitlement.Mode.READ)
+                                FilesEntitlement.FileData.ofRelativePath(pathCD, PathLookup.BaseDir.DATA, FilesEntitlement.Mode.READ)
                             )
                         )
                     )
@@ -339,11 +339,7 @@ public void testFormatFilesEntitlement() {
                         new FilesEntitlement(
                             List.of(
                                 FilesEntitlement.FileData.ofPath(pathAB, FilesEntitlement.Mode.READ_WRITE),
-                                FilesEntitlement.FileData.ofPathSetting(
-                                    "setting",
-                                    FilesEntitlement.BaseDir.DATA,
-                                    FilesEntitlement.Mode.READ
-                                )
+                                FilesEntitlement.FileData.ofPathSetting("setting", PathLookup.BaseDir.DATA, FilesEntitlement.Mode.READ)
                             )
                         )
                     )
diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlementTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlementTests.java
index 7bc8e39fb1b27..84c4833ca6aae 100644
--- a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlementTests.java
+++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlementTests.java
@@ -11,6 +11,7 @@
 
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.entitlement.runtime.policy.PathLookup;
+import org.elasticsearch.entitlement.runtime.policy.PathLookupImpl;
 import org.elasticsearch.entitlement.runtime.policy.Policy;
 import org.elasticsearch.entitlement.runtime.policy.PolicyParser;
 import org.elasticsearch.entitlement.runtime.policy.PolicyValidationException;
@@ -25,7 +26,7 @@
 import java.util.List;
 import java.util.Map;
 
-import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.BaseDir.CONFIG;
+import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.CONFIG;
 import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ;
 import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ_WRITE;
 import static org.hamcrest.Matchers.contains;
@@ -42,12 +43,17 @@ public static void setupRoot() {
         settings = Settings.EMPTY;
     }
 
-    private static final PathLookup TEST_PATH_LOOKUP = new PathLookup(
-        Path.of("home"),
+    private static final PathLookup TEST_PATH_LOOKUP = new PathLookupImpl(
+        Path.of("/home"),
         Path.of("/config"),
         new Path[] { Path.of("/data1"), Path.of("/data2") },
         new Path[] { Path.of("/shared1"), Path.of("/shared2") },
+        Path.of("/lib"),
+        Path.of("/modules"),
+        Path.of("/plugins"),
+        Path.of("/logs"),
         Path.of("/tmp"),
+        null,
         pattern -> settings.getValues(pattern)
     );
 
@@ -67,7 +73,7 @@ public void testInvalidRelativeDirectory() {
     }
 
     public void testFileDataRelativeWithAbsoluteDirectoryFails() {
-        var fileData = FileData.ofRelativePath(Path.of(""), FilesEntitlement.BaseDir.DATA, READ_WRITE);
+        var fileData = FileData.ofRelativePath(Path.of(""), PathLookup.BaseDir.DATA, READ_WRITE);
         var dataDirs = fileData.resolvePaths(TEST_PATH_LOOKUP);
         assertThat(dataDirs.toList(), contains(Path.of("/data1/"), Path.of("/data2")));
     }