{"record":{"id":"5280e6cad23b965c","repo":"theonedev/onedev","slug":"invalid-request-path","errorCode":null,"errorMessage":"Invalid request path","messagePattern":"Invalid request path","errorType":"validation","errorClass":"ExplicitException","httpStatus":400,"severity":"error","filePath":"server-core/src/main/java/io/onedev/server/codequality/UnitTestReport.java","lineNumber":142,"sourceCode":"\t}\n\n\tprivate static boolean existsIn(File reportDir) {\n\t\treturn new File(reportDir, REPORT).isFile();\n\t}\n\n\t@Nullable\n\tpublic static UnitTestReport readFrom(Build build, String reportName) {\n\t\tcheckReportName(reportName);\n\t\tLong projectId = build.getProject().getId();\n\t\treturn OneDev.getInstance(ProjectService.class).runOnActiveServer(projectId,\n\t\t\t\tnew ReadReport(projectId, build.getNumber(), reportName));\n\t}\n\n\tpublic static void downloadArtifact(Long projectId, Long buildNumber, String reportName,\n\t\t\tString artifactPath, OutputStream os) {\n\t\tcheckReportName(reportName);\n\t\tif (artifactPath.contains(\"..\") || !artifactPath.startsWith(ARTIFACTS + \"/\"))\n\t\t\tthrow new ExplicitException(\"Invalid request path\");\n\n\t\tvar clusterService = OneDev.getInstance(ClusterService.class);\n\t\tvar activeServer = OneDev.getInstance(ProjectService.class).getActiveServer(projectId, true);\n\t\tif (activeServer.equals(clusterService.getLocalServerAddress())) {\n\t\t\tread(getReportLockName(projectId, buildNumber), () -> {\n\t\t\t\tFile reportDir = getReportDir(projectId, buildNumber, reportName);\n\t\t\t\tFile artifactFile = new File(reportDir, artifactPath).getCanonicalFile();\n\t\t\t\tif (!artifactFile.toPath().startsWith(reportDir.getCanonicalFile().toPath())\n\t\t\t\t\t\t|| !artifactFile.isFile()) {\n\t\t\t\t\tthrow new ExplicitException(\"Invalid request path\");\n\t\t\t\t}\n\t\t\t\ttry (var is = new FileInputStream(artifactFile)) {\n\t\t\t\t\tIOUtils.copy(is, os, BUFFER_SIZE);\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t\t});\n\t\t} else {\n\t\t\tClient client = ClientBuilder.newClient();","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-core/src/main/java/io/onedev/server/codequality/UnitTestReport.java#L124-L160","documentation":"UnitTestReport.downloadArtifact validates the requested artifact path before reading files from a build's unit test report directory. Any path that does not start with the 'artifacts/' prefix or that contains '..' (parent-directory traversal) is rejected with this ExplicitException. It is OneDev's first-line defense against path traversal when serving report artifacts over HTTP.","triggerScenarios":"Calling UnitTestReport.downloadArtifact(projectId, buildNumber, reportName, artifactPath, os) with artifactPath containing '..' anywhere, or with artifactPath not beginning with \"artifacts/\" (e.g. an absolute path, empty string, or a path rooted elsewhere).","commonSituations":"Client code or REST consumers passing a raw user-supplied path; constructing artifact paths by string concatenation that accidentally includes '../'; passing a path without the required 'artifacts/' prefix when resolving report attachments.","solutions":["Ensure artifactPath always starts with \"artifacts/\" before calling downloadArtifact.","Strip or normalize any '..' segments from the path (e.g. normalize relative references before invoking).","If the target lives outside the artifacts subtree, use the appropriate API instead of downloadArtifact.","Log the offending path to find which caller is producing malformed paths."],"exampleFix":"// before\ndownloadArtifact(projectId, buildNumber, reportName, \"../../etc/passwd\", os); // throws\n// after\nString safePath = \"artifacts/\" + artifactPath.replace(\"\\\\\", \"/\");\nif (!safePath.contains(\"..\"))\n    downloadArtifact(projectId, buildNumber, reportName, safePath, os);","handlingStrategy":"validation","validationCode":"if (artifactPath == null || artifactPath.contains(\"..\") || !artifactPath.startsWith(\"artifacts/\"))\n    throw new IllegalArgumentException(\"artifactPath must be a relative path under artifacts/\");","typeGuard":"boolean isSafeArtifactPath(String p) {\n    return p != null && !p.contains(\"..\") && p.startsWith(\"artifacts/\");\n}","tryCatchPattern":"try {\n    UnitTestReport.downloadArtifact(projectId, buildNumber, reportName, artifactPath, os);\n} catch (ExplicitException e) {\n    if (e.getMessage().equals(\"Invalid request path\"))\n    log.warn(\"Rejected artifact path: {}\", artifactPath);\n}","preventionTips":["Always build artifact paths from known-safe components, never raw user input.","Normalize paths and reject '..' before any file-serving API call.","Keep the mandatory 'artifacts/' prefix as part of a single path-building helper."],"tags":["path-traversal","validation","http-api"],"backgroundTag":"path-traversal-blocked","analyzedSha":"d44925c47c37992c828ea673a5f9620539bc3ff2","analyzedAt":"2026-09-06T07:18:27.995Z","contentChangedAt":"2026-09-06T07:18:27.995Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}