{"record":{"id":"2f264f23cfb83884","repo":"theonedev/onedev","slug":"invalid-artifact-request-path-2f264f","errorCode":null,"errorMessage":"Invalid artifact request path","messagePattern":"Invalid artifact request path","errorType":"exception","errorClass":"ExplicitException","httpStatus":null,"severity":"error","filePath":"server-plugin/server-plugin-report-unittest/src/main/java/io/onedev/server/plugin/report/unittest/TestArtifactResource.java","lineNumber":74,"sourceCode":"\t\t\t\tthrow new EntityNotFoundException(String.format(\n\t\t\t\t\t\t\"Unable to find build (project: %s, build number: %d)\",\n\t\t\t\t\t\tproject.getPath(), buildNumber));\n\t\t\t}\n\t\t\tif (!SecurityUtils.canAccessReport(build, reportName))\n\t\t\t\tthrow new UnauthorizedException();\n\t\t}\n\n\t\tList<String> pathSegments = new ArrayList<>();\n\t\tfor (int i = 0; i < params.getIndexedCount(); i++) {\n\t\t\tString pathSegment = params.get(i).toString();\n\t\t\tif (pathSegment.contains(\"..\"))\n\t\t\t\tthrow new ExplicitException(\"Invalid request path\");\n\t\t\tif (pathSegment.length() != 0)\n\t\t\t\tpathSegments.add(pathSegment);\n\t\t}\n\t\tString artifactPath = Joiner.on(\"/\").join(pathSegments);\n\t\tif (!artifactPath.startsWith(ARTIFACTS + \"/\"))\n\t\t\tthrow new ExplicitException(\"Invalid artifact request path\");\n\n\t\tString fileName = StringUtils.substringAfterLast(artifactPath, \"/\");\n\t\tResourceResponse response = new ResourceResponse();\n\t\tresponse.getHeaders().addHeader(\"X-Content-Type-Options\", \"nosniff\");\n\t\tresponse.setContentDisposition(ContentDisposition.ATTACHMENT);\n\t\ttry {\n\t\t\tresponse.setContentType(MimeUtils.sanitize(Files.probeContentType(Paths.get(artifactPath))));\n\t\t} catch (IOException e) {\n\t\t\tthrow new RuntimeException(e);\n\t\t}\n\t\tresponse.disableCaching();\n\t\tresponse.setFileName(URLEncoder.encode(fileName, UTF_8));\n\t\tresponse.setWriteCallback(new WriteCallback() {\n\n\t\t\t@Override\n\t\t\tpublic void writeData(Attributes attributes) throws IOException {\n\t\t\t\tUnitTestReport.downloadArtifact(projectId, buildNumber, reportName, artifactPath,\n\t\t\t\t\t\tattributes.getResponse().getOutputStream());","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-plugin/server-plugin-report-unittest/src/main/java/io/onedev/server/plugin/report/unittest/TestArtifactResource.java#L56-L92","documentation":"Thrown by TestArtifactResource.newResourceResponse when the requested artifact path from a unit test report download URL does not start with the required \"artifacts/\" prefix after being normalized into slash-joined path segments. The resource only serves files stored under the artifacts directory of a test report, so any other path is rejected as invalid to prevent serving arbitrary or malformed paths.","triggerScenarios":"A GET request to the unit-test artifact resource whose URL path segments, after stripping empty segments and joining with '/', do not begin with 'artifacts/' (e.g. missing the prefix, a mistyped URL, or a manually crafted link pointing outside the artifacts tree).","commonSituations":"Users hand-editing or copying truncated artifact download links; CI scripts constructing artifact URLs programmatically without the artifacts/ prefix; stale bookmarks after URL scheme changes; attempts to probe other report files via the artifact endpoint.","solutions":["Ensure the artifact URL path starts with 'artifacts/' followed by the file path (e.g. .../artifacts/build/log.txt).","Copy the artifact link from the OneDev UI (test report page) rather than constructing it manually.","Check for truncated or URL-encoded characters in the path when building links in automation scripts.","Verify the report actually contains artifacts; if no artifacts were published the link may be malformed."],"exampleFix":"// before (bad link)\nGET /~downloads/unittest/1/build/log.txt\n// after\nGET /~downloads/unittest/1/artifacts/build/log.txt","handlingStrategy":"validation","validationCode":"String artifactPath = Joiner.on(\"/\").join(pathSegments);\nif (artifactPath == null || !artifactPath.startsWith(\"artifacts/\")) {\n    // don't call the resource; fix the URL first\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always build artifact URLs from the paths shown in the OneDev test report UI.","Keep the 'artifacts/' prefix constant when generating links in CI scripts.","URL-encode each path segment but keep '/' as separator."],"tags":["url-path","rest-api","artifacts"],"backgroundTag":"invalid-url","analyzedSha":"d44925c47c37992c828ea673a5f9620539bc3ff2","analyzedAt":"2026-09-06T07:18:27.995Z","contentChangedAt":"2026-09-06T07:18:27.995Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}