kestra-io/kestra · error · SecurityException

The path {} is not authorized. Only files inside the working

Error message

The path {} is not authorized. Only files inside the working directory are allowed by default, other path must be allowed either globally inside the Kestra configuration using the `kestra.local-files.allowed-paths` property, or by plugin using the `allowed-paths` plugin configuration.

What it means

Thrown by `RunContextLocalPath.checkPath` (the variant that has a `RunContext`) when a resolved real path is neither inside the task's working directory, nor in the globally-configured allowed paths, nor in the plugin's `allowed-paths` configuration. This is a security control preventing tasks from reading arbitrary host files. Thrown as `SecurityException`. `toRealPath()` is applied first, so symlink traversal is also caught.

Source

Thrown at core/src/main/java/io/kestra/core/runners/LocalPathFactory.java:118

        private final List<Path> globalAllowedPaths;
        private final RunContext runContext;

        RunContextLocalPath(List<Path> globalAllowedPaths, RunContext runContext) {
            this.globalAllowedPaths = globalAllowedPaths;
            this.runContext = runContext;
        }

        @Override
        @SuppressWarnings("unchecked")
        protected Path checkPath(URI uri) throws IOException {
            Path workingDirectory = runContext.workingDir().path(true);
            Path path = Path.of(uri).toRealPath(); // toRealPath() will protect about path traversal issues
            // We allow working directory or globally allowed path
            if (!path.startsWith(workingDirectory) && globalAllowedPaths.stream().noneMatch(path::startsWith)) {
                // if not globally allowed, we check if it's allowed for this specific plugin
                List<String> pluginAllowedPaths = (List<String>) runContext.pluginConfiguration("allowed-paths").orElse(Collections.emptyList());
                if (pluginAllowedPaths.stream().map(LocalPathFactory::resolveAllowedPath).noneMatch(path::startsWith)) {
                    throw new SecurityException(
                        "The path " + path + " is not authorized. " +
                            "Only files inside the working directory are allowed by default, other path must be allowed either globally inside the Kestra configuration using the `"
                            + LocalPath.ALLOWED_PATHS_CONFIG + "` property, " +
                            "or by plugin using the `allowed-paths` plugin configuration."
                    );
                }
            }

            return path;
        }
    }

    static class DefaultLocalPath extends AbstractLocalPath {
        private final List<Path> globalAllowedPaths;

        DefaultLocalPath(List<Path> globalAllowedPaths) {
            this.globalAllowedPaths = globalAllowedPaths;
        }

View on GitHub (pinned to 823fada927)

Solutions

  1. Copy the needed file into the task's working directory and reference it relatively.
  2. Add the directory to `kestra.local-files.allowed-paths` in the Kestra configuration (server-wide).
  3. Add the directory to the plugin's `allowed-paths` plugin configuration (task-scoped).
  4. Avoid absolute host paths; use internal storage (`kestra://`) for cross-task file passing.

Example fix

# before — task reads a host file outside the working dir
- id: read
  type: io.kestra.plugin.core.log.Log
  message: "{{ read('file:///etc/hosts') }}"

# after — allow the path via plugin config or copy into working dir
# option A: plugin-level allowed-paths
- id: read
  type: io.kestra.plugin.scripts.shell.Commands
  allowed-paths:
    - /etc
  commands:
    - cat /etc/hosts
Defensive patterns

Strategy: validation

Validate before calling

Path workingDir = runContext.workingDir().path(true);
Path real = Path.of(uri).toRealPath();
if (!real.startsWith(workingDir)
    && globalAllowedPaths.stream().noneMatch(real::startsWith)
    && pluginAllowedPaths.stream().map(LocalPathFactory::resolveAllowedPath).noneMatch(real::startsWith)) {
    throw new SecurityException("Path not allowed: " + real);
}

Try / catch

try {
    return localPath.get(uri);
} catch (SecurityException e) {
    runContext.logger().error("Access denied to {}: configure allowed-paths or use internal storage.", uri);
    throw e;
}

Prevention

When it happens

Trigger: A task attempts to read/write a host file outside its working directory via a `file://` URI, and neither `kestra.local-files.allowed-paths` nor the plugin's `allowed-paths` lists that directory. Common with the `LocalFiles` task or plugins that resolve `file://` URIs.

Common situations: Reading `/etc/...`, `/tmp/shared/...`, or an absolute host path from a task; deploying to a stricter environment where previously-allowed paths were removed; symlinking outside the working dir; a path that worked locally fails in a containerized worker with a different filesystem layout.

Related errors


AI-assisted analysis of kestra-io/kestra@823fada927 (2026-08-14). Data as JSON: /api/errors/2e5747f94c2ed385. Report an issue: GitHub.