{"record":{"id":"31c864597e5b4c70","repo":"theonedev/onedev","slug":"unauthorized-31c864","errorCode":null,"errorMessage":"Unauthorized","messagePattern":"Unauthorized","errorType":"http","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"server-core/src/main/java/io/onedev/server/web/resource/ArtifactResource.java","lineNumber":74,"sourceCode":"\t\tif (pathSegments.isEmpty())\n\t\t\tthrow new ExplicitException(\"Artifact path has to be specified\");\n\t\t\n\t\tString artifactPath = Joiner.on(\"/\").join(pathSegments);\n\t\t\n\t\tFileInfo fileInfo = null;\n\t\tif (!SecurityUtils.isSystem()) {\n\t\t\tProject project = OneDev.getInstance(ProjectService.class).load(projectId);\n\t\t\t\n\t\t\tBuild build = OneDev.getInstance(BuildService.class).find(project, buildNumber);\n\n\t\t\tif (build == null) {\n\t\t\t\tString message = String.format(\"Unable to find build (project: %s, build number: %d)\", \n\t\t\t\t\t\tproject.getPath(), buildNumber);\n\t\t\t\tthrow new EntityNotFoundException(message);\n\t\t\t}\n\t\t\t\n\t\t\tif (!SecurityUtils.canAccessProject(build.getProject()))\n\t\t\t\tthrow new UnauthorizedException();\n\t\t\t\n\t\t\tfileInfo = (FileInfo) getBuildService().getArtifactInfo(build, artifactPath);\n\t\t}\n\t\t\n\t\tResourceResponse response = new ResourceResponse();\n\t\tresponse.getHeaders().addHeader(\"X-Content-Type-Options\", \"nosniff\");\n\t\tresponse.disableCaching();\n\n\t\tString fileName = artifactPath;\n\t\tif (fileName.contains(\"/\"))\n\t\t\tfileName = StringUtils.substringAfterLast(fileName, \"/\");\n\t\ttry {\n\t\t\tresponse.setFileName(URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));\n\t\t} catch (UnsupportedEncodingException e) {\n\t\t\tthrow new RuntimeException(e);\n\t\t}\n\t\t\n\t\tif (fileInfo != null) {","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-core/src/main/java/io/onedev/server/web/resource/ArtifactResource.java#L56-L92","documentation":"After finding the build for an artifact request (only when the caller is not the internal system identity), ArtifactResource checks SecurityUtils.canAccessProject(build.getProject()) and throws Shiro UnauthorizedException when the current user cannot access that project. Access to artifacts is gated on project access, not just on knowing the URL.","triggerScenarios":"A user without project access requests artifacts of a build in a private/non-member project; a job or user token from another project fetches this project's artifacts.","commonSituations":"Sharing artifact links between teams; permission revoked after the URL was saved; CI job using another project's token; anonymous request against a private project.","solutions":["Request the project admin to grant you access (can access project) to the project owning the build","Authenticate as a user/agent with membership in the target project","For CI, use the job authorization token of the same project's build","Confirm the URL's project/build pair actually points to the project you have access to"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const perm = await onedevApi.get(`/projects/${projectId}/permissions`);\nif (!perm.canAccess) throw new Error('current user cannot access project ' + projectId + '; request membership or use a project token');","typeGuard":null,"tryCatchPattern":"try { await fetch(artifactUrl); } catch (e) { if (e.status === 403) console.error('Unauthorized: obtain project access or authenticate as a member of the build\\'s project'); }","preventionTips":["Use tokens from the same project as the build","Verify saved links after role/permission changes","Confirm URL's project id matches intended project"],"tags":["security","authorization","artifact"],"backgroundTag":"permission-denied","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"}