You are viewing a plain text version of this content. The canonical link for it is here.
Posted to jira@kafka.apache.org by "Mickael Maison (Jira)" <ji...@apache.org> on 2026/06/24 18:19:00 UTC
[jira] [Resolved] (KAFKA-20700) AllowedPaths should resolve symlinks before validating paths against allowed.paths
[ https://issues.apache.org/jira/browse/KAFKA-20700?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]
Mickael Maison resolved KAFKA-20700.
------------------------------------
Fix Version/s: 4.4.0
Resolution: Fixed
> AllowedPaths should resolve symlinks before validating paths against allowed.paths
> ----------------------------------------------------------------------------------
>
> Key: KAFKA-20700
> URL: https://issues.apache.org/jira/browse/KAFKA-20700
> Project: Kafka
> Issue Type: Bug
> Reporter: Nilesh Kumar
> Assignee: Nilesh Kumar
> Priority: Major
> Fix For: 4.4.0
>
>
> h2. Problem
> `AllowedPaths.parseUntrustedPath()` validates user-supplied paths using lexical `Path.normalize()` only. It does not resolve symbolic links before checking whether a path is under a configured `allowed.paths` base directory.
> As a result, if a symlink exists inside an allowed directory (e.g. `/opt/kafka/secrets/link -> /some/other/path`), validation passes because the lexical path appears under the allowed base, but `FileConfigProvider` and `DirectoryConfigProvider` later read the symlink target when accessing the filesystem.
> h2. Affected components
> * `org.apache.kafka.common.config.internals.AllowedPaths`
> * `org.apache.kafka.common.config.provider.FileConfigProvider`
> * `org.apache.kafka.common.config.provider.DirectoryConfigProvider`
> h2. Example
> * `allowed.paths` configured as `/opt/kafka/secrets`
> * Symlink present: `/opt/kafka/secrets/test -> /etc/sensitive`
> * Config reference: `${file:/opt/kafka/secrets/test:key}`
> * Validation allows `/opt/kafka/secrets/test`
> * File read follows symlink and accesses `/etc/sensitive`
> h2. Current behavior
> {code:java}
> Path normalisedPath = parsedPath.normalize();
> long allowed = allowedPaths.stream().filter(normalisedPath::startsWith).count();
> {code}
> `normalize()` collapses `.` and `..` but does not resolve symlinks.
> h2. Expected behavior
> Path validation should use the resolved filesystem path (e.g. `toRealPath()`) so that a symlink inside an allowed directory pointing outside that directory is rejected.
> h2. Proposed fix
> {code:java}
> try {
> Path realPath = parsedPath.toRealPath();
> long allowed = allowedPaths.stream().filter(realPath::startsWith).count();
> if (allowed == 0) {
> return null;
> }
> return realPath;
> } catch (IOException e) {
> return null;
> }
> {code}
> Consider also resolving allowed base paths with `toRealPath()` at configuration time for consistent `startsWith` comparisons.
> h2. Test coverage
> `AllowedPathsTest` covers `..` traversal but has no test for symlink resolution. Add tests for:
> * Symlink inside allowed dir pointing outside → rejected
> * Symlink inside allowed dir pointing inside → allowed
> * Direct path (no symlink) → unchanged behavior
> h2. Context
> This was reported to the security team. They noted that exploiting this requires write access to the Connect worker filesystem to create symlinks, and therefore do not classify it as a security issue. They agreed the behavior is not intuitive and welcomed a community PR to improve it.
> h2. References
> * `clients/src/main/java/org/apache/kafka/common/config/internals/AllowedPaths.java`
> * `clients/src/test/java/org/apache/kafka/common/config/provider/AllowedPathsTest.java`
> * Related hardening: CVE-2024-31141 (`allowed.paths`)
--
This message was sent by Atlassian Jira
(v8.20.10#820010)