theonedev/onedev · error · ExplicitException

Invalid artifact request path

Error message

Invalid artifact request path

What it means

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.

Source

Thrown at server-plugin/server-plugin-report-unittest/src/main/java/io/onedev/server/plugin/report/unittest/TestArtifactResource.java:74

				throw new EntityNotFoundException(String.format(
						"Unable to find build (project: %s, build number: %d)",
						project.getPath(), buildNumber));
			}
			if (!SecurityUtils.canAccessReport(build, reportName))
				throw new UnauthorizedException();
		}

		List<String> pathSegments = new ArrayList<>();
		for (int i = 0; i < params.getIndexedCount(); i++) {
			String pathSegment = params.get(i).toString();
			if (pathSegment.contains(".."))
				throw new ExplicitException("Invalid request path");
			if (pathSegment.length() != 0)
				pathSegments.add(pathSegment);
		}
		String artifactPath = Joiner.on("/").join(pathSegments);
		if (!artifactPath.startsWith(ARTIFACTS + "/"))
			throw new ExplicitException("Invalid artifact request path");

		String fileName = StringUtils.substringAfterLast(artifactPath, "/");
		ResourceResponse response = new ResourceResponse();
		response.getHeaders().addHeader("X-Content-Type-Options", "nosniff");
		response.setContentDisposition(ContentDisposition.ATTACHMENT);
		try {
			response.setContentType(MimeUtils.sanitize(Files.probeContentType(Paths.get(artifactPath))));
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		response.disableCaching();
		response.setFileName(URLEncoder.encode(fileName, UTF_8));
		response.setWriteCallback(new WriteCallback() {

			@Override
			public void writeData(Attributes attributes) throws IOException {
				UnitTestReport.downloadArtifact(projectId, buildNumber, reportName, artifactPath,
						attributes.getResponse().getOutputStream());

View on GitHub (pinned to d44925c47c)

Solutions

  1. Ensure the artifact URL path starts with 'artifacts/' followed by the file path (e.g. .../artifacts/build/log.txt).
  2. Copy the artifact link from the OneDev UI (test report page) rather than constructing it manually.
  3. Check for truncated or URL-encoded characters in the path when building links in automation scripts.
  4. Verify the report actually contains artifacts; if no artifacts were published the link may be malformed.

Example fix

// before (bad link)
GET /~downloads/unittest/1/build/log.txt
// after
GET /~downloads/unittest/1/artifacts/build/log.txt
Defensive patterns

Strategy: validation

Validate before calling

String artifactPath = Joiner.on("/").join(pathSegments);
if (artifactPath == null || !artifactPath.startsWith("artifacts/")) {
    // don't call the resource; fix the URL first
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of theonedev/onedev@d44925c47c (2026-09-06). Data as JSON: /api/errors/2f264f23cfb83884. Report an issue: GitHub.