{"record":{"id":"fdfc5d78f15d25ad","repo":"quarkusio/quarkus","slug":"path-is-outside-the-project-root-resolved","errorCode":null,"errorMessage":"Path is outside the project root: ${resolved}","messagePattern":"Path is outside the project root: (.+?)","errorType":"exception","errorClass":"SecurityException","httpStatus":null,"severity":"error","filePath":"extensions/devui/deployment/src/main/java/io/quarkus/devui/deployment/menu/WorkspaceProcessor.java","lineNumber":367,"sourceCode":"     * path (including ones using {@code ..} or symlinks) that resolves outside the root is rejected.\n     */\n    private static Path resolveWorkspacePath(Path rootPath, String uriString) {\n        if (uriString == null) {\n            throw new SecurityException(\"No workspace path provided\");\n        }\n\n        Path root;\n        Path resolved;\n        try {\n            root = toCanonicalPath(rootPath);\n            resolved = toCanonicalPath(Paths.get(URI.create(uriString)));\n        } catch (IllegalArgumentException | FileSystemNotFoundException | IOException e) {\n            // Malformed URI, a non-file scheme or a path we cannot safely canonicalize: reject it.\n            throw new SecurityException(\"Invalid workspace path: \" + uriString);\n        }\n\n        if (!resolved.startsWith(root)) {\n            throw new SecurityException(\"Path is outside the project root: \" + resolved);\n        }\n        return resolved;\n    }\n\n    /**\n     * Normalize a path to an absolute form with symlinks resolved. The file itself may not exist\n     * yet (e.g. when creating a new workspace item), so symlinks are only resolved on the nearest\n     * existing ancestor to prevent a symlinked directory from escaping the root. If the real path\n     * cannot be determined the {@link IOException} is propagated so the caller can fail closed\n     * rather than fall back to an unresolved (potentially escaping) path.\n     */\n    private static Path toCanonicalPath(Path path) throws IOException {\n        Path absolute = path.toAbsolutePath().normalize();\n        Path existing = absolute;\n        while (existing != null && !Files.exists(existing)) {\n            existing = existing.getParent();\n        }\n        if (existing == null) {","sourceCodeStart":349,"sourceCodeEnd":385,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/devui/deployment/src/main/java/io/quarkus/devui/deployment/menu/WorkspaceProcessor.java#L349-L385","documentation":"After successfully parsing and canonicalizing the client-supplied URI, resolveWorkspacePath verifies the resolved path stays inside the canonical project root using resolved.startsWith(root). If the path escapes the root (including via .. segments or symlinks that resolve elsewhere), a SecurityException is thrown. This prevents Dev UI workspace actions from touching files outside the user's project.","triggerScenarios":"Passing a file: URI that resolves outside the project directory — absolute paths elsewhere on disk, '..' traversal after resolution, or a symlink inside the project pointing to a location outside the root.","commonSituations":"Project checked out via a symlinked directory (IDE workspace path differs from canonical root); sending home-directory paths instead of project-relative ones; symlinks in src/ pointing to shared folders; attempting to edit config files outside the project.","solutions":["Use paths that physically reside under the project root directory dev mode was started in.","Start dev mode from the canonical project directory so rootPath matches where files actually live.","Replace symlinks that point outside the project with real files, or point the URI at the real in-project location.","Normalize the URI first (resolve .. segments against the project root) client-side before sending."],"exampleFix":"// before\nString uri = \"file:///etc/application.properties\"; // outside project\n\n// after\nString uri = \"file:///home/user/project/src/main/resources/application.properties\";","handlingStrategy":"validation","validationCode":"Path root = Paths.get(projectDir).toRealPath();\nPath resolved = Paths.get(URI.create(uriString)).toRealPath();\nif (!resolved.startsWith(root)) {\n    throw new IllegalArgumentException(\"Refusing: path escapes project root\");\n}","typeGuard":null,"tryCatchPattern":"try { Path p = resolveWorkspacePath(root, uriString); ... }\ncatch (SecurityException e) {\n    if (e.getMessage().startsWith(\"Path is outside the project root\")) {\n        ui.showError(\"Choose a file inside the project directory\");\n    } else throw e;\n}","preventionTips":["Start dev mode from the canonical (non-symlinked) project directory","Resolve '..' segments against the project root client-side","Avoid symlinks that point outside the project tree"],"tags":["devui","workspace","security","path-traversal"],"backgroundTag":"path-outside-project-root","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}