{"id":"eecc49a2367e6747","repo":"apache/kafka","slug":"path-normalisedpath-could-not-be-resolved","errorCode":null,"errorMessage":"Path normalisedPath could not be resolved","messagePattern":"Path normalisedPath could not be resolved","errorType":"validation","errorClass":"ConfigException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/common/config/internals/AllowedPaths.java","lineNumber":56,"sourceCode":"        this.allowedPaths = getAllowedPaths(configValue);\n    }\n\n    private List<Path> getAllowedPaths(String configValue) {\n        if (configValue != null && !configValue.isEmpty()) {\n            List<Path> allowedPaths = new ArrayList<>();\n\n            Arrays.stream(configValue.split(\",\")).forEach(b -> {\n                Path normalisedPath = Paths.get(b).normalize();\n\n                if (!normalisedPath.isAbsolute()) {\n                    throw new ConfigException(\"Path \" + normalisedPath + \" is not absolute\");\n                } else if (!Files.exists(normalisedPath)) {\n                    throw new ConfigException(\"Path \" + normalisedPath + \" does not exist\");\n                } else {\n                    try {\n                        allowedPaths.add(normalisedPath.toRealPath());\n                    } catch (IOException e) {\n                        throw new ConfigException(\"Path \" + normalisedPath + \" could not be resolved\", e);\n                    }\n                }\n            });\n\n            return allowedPaths;\n        }\n\n        return null;\n    }\n\n    /**\n     * Checks if the given {@code path} resides in the configured {@code allowed.paths}.\n     * If {@code allowed.paths} is not configured, the given Path is returned as allowed.\n     * @param path the Path to check if allowed\n     * @return Path that can be accessed or null if the given Path does not reside in the configured {@code allowed.paths}.\n     */\n    public Path parseUntrustedPath(String path) {\n        Path parsedPath = Paths.get(path);","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/common/config/internals/AllowedPaths.java#L38-L74","documentation":"Thrown by AllowedPaths.getAllowedPaths() when Path.toRealPath() raises an IOException during resolution of an otherwise valid, existing absolute path. This is the fallback ConfigException (with the underlying IOException as cause) for filesystem-level resolution failures that the existence check did not catch, such as broken symlinks encountered mid-resolution or permission errors reading directory metadata.","triggerScenarios":"AllowedPaths construction with an allowed.paths entry that is absolute and exists at the top level but contains a broken symlink, a cyclic symlink chain, or an intermediate directory the JVM cannot read/stat. toRealPath() throws IOException, wrapped as ConfigException.","commonSituations":"allowed.paths entry points at a symlink whose target is missing or unreachable. Container layers where intermediate mount points are not stat-able by the JVM user. NFS/network filesystem hiccups during startup. SELinux/AppArmor denying stat on an intermediate directory.","solutions":["Inspect the underlying IOException cause in the log (ConfigException wraps it) to see the exact I/O failure.","Resolve or remove broken symlinks along the path: readlink -f <path> && ls -la.","Grant the JVM user read+execute permissions on every directory in the chain.","Replace the symlinked entry with the canonical real target path in allowed.paths."],"exampleFix":"# before\nln -s /mnt/missing /etc/kafka/secrets\nallowed.paths=/etc/kafka/secrets\n\n# after\n# fix or recreate the symlink target\nln -sfn /mnt/present/secrets /etc/kafka/secrets\nallowed.paths=/mnt/present/secrets","handlingStrategy":"try-catch","validationCode":"// Best-effort probe of toRealPath() before AllowedPaths construction:\nfor (String raw : configValue.split(\",\")) {\n    Path p = Paths.get(raw.trim()).normalize();\n    try {\n        p.toRealPath(); // surfaces broken symlinks / permission issues early\n    } catch (IOException e) {\n        throw new IllegalArgumentException(\n            \"allowed.paths entry '\" + p + \"' cannot be resolved: \" + e.getMessage(), e);\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    AllowedPaths ap = new AllowedPaths(configValue);\n} catch (ConfigException e) {\n    if (e.getMessage().endsWith(\"could not be resolved\")\n            && e.getCause() instanceof IOException) {\n        // broken symlink / I/O fault: log cause, retry, or fall back to a known-good path\n    } else { throw e; }\n}","preventionTips":["Avoid broken or circular symlinks inside allowed.paths trees; resolve them at provisioning time.","Ensure the JVM process has read+execute permission on every path component leading to the configured entry.","Treat toRealPath failures as environmental (filesystem race, NFS hiccup): prefer retry-with-backoff or fail-fast over silent skip."],"tags":["config","allowed-paths","filesystem","symlink","config-provider"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}