theonedev/onedev · error · UnauthorizedException
Unauthorized
Error message
Unauthorized
What it means
For non-system callers lacking a valid encrypted 'authorization' parameter, AttachmentResource authorizes the attachment group by entity type: pull request/code comment groups require canReadCode(project), issue groups require canAccessIssue(issue), build groups require canAccessProject(build.getProject()), and unknown groups fall back to canAccessProject(project). Any failed check throws Shiro UnauthorizedException.
Source
Thrown at server-core/src/main/java/io/onedev/server/web/resource/AttachmentResource.java:81
String attachmentGroup = params.get(PARAM_ATTACHMENT_GROUP).toString();
if (StringUtils.isBlank(attachmentGroup))
throw new IllegalArgumentException("Parameter 'attachment-group' has to be specified");
else if (attachmentGroup.contains(".."))
throw new IllegalArgumentException("Invalid parameter 'attachment-group'");
if (!SecurityUtils.isSystem()) {
Project project = OneDev.getInstance(ProjectService.class).load(projectId);
String authorization = params.get(PARAM_AUTHORIZATION).toOptionalString();
if (authorization == null
|| !new String(CryptoUtils.decrypt(Base64.decodeBase64(authorization)), UTF_8).equals(attachmentGroup)) {
Issue issue;
Build build;
if (OneDev.getInstance(PullRequestService.class).find(attachmentGroup) != null
|| OneDev.getInstance(CodeCommentService.class).findByUUID(attachmentGroup) != null) {
if (!SecurityUtils.canReadCode(project))
throw new UnauthorizedException();
} else if ((issue = OneDev.getInstance(IssueService.class).find(attachmentGroup)) != null) {
if (!SecurityUtils.canAccessIssue(issue))
throw new UnauthorizedException();
} else if ((build = OneDev.getInstance(BuildService.class).find(attachmentGroup)) != null) {
if (!SecurityUtils.canAccessProject(build.getProject()))
throw new UnauthorizedException();
} else if (!SecurityUtils.canAccessProject(project)) {
throw new UnauthorizedException();
}
}
}
String attachment = params.get(PARAM_ATTACHMENT).toString();
if (StringUtils.isBlank(attachment))
throw new IllegalArgumentException("attachment parameter has to be specified");
else if (attachment.contains(".."))
throw new IllegalArgumentException("Invalid attachment parameter");
View on GitHub (pinned to d44925c47c)
Solutions
- Request access: ask an admin to grant code-read (for PR/comment attachments), issue access, or project access as appropriate to the attachment's origin
- For CI, use the same project's job token or pass a valid 'authorization' parameter (the encrypted, Base64 token OneDev generates for the group)
- Verify the attachment-group value actually refers to the entity you intend; an unknown group falls back to plain project access check
- Re-obtain the link from the OneDev UI as the current user to confirm the entity and your access
Defensive patterns
Strategy: validation
Validate before calling
const canRead = await onedevApi.get(`/projects/${projectId}/permissions`);
if (!canRead.canReadCode && !canRead.canAccess) throw new Error('insufficient permissions for this attachment group; request code read / issue access / project access as appropriate'); Try / catch
try { await fetch(attachmentUrl); } catch (e) { if (e.status === 403) console.error('Unauthorized for attachment group: acquire the proper entity/project access or a valid encrypted authorization token'); } Prevention
- Ensure users have access to the entity backing the attachment group (issue, PR, comment, build)
- Use OneDev-generated authorization tokens for programmatic group access
- Re-verify links after permission tightening; use project-scoped tokens in CI
When it happens
Trigger: An authenticated user without the needed permission requests an attachment: e.g. an issue attachment the user cannot access, a build attachment in a project they cannot access, or a group the user cannot resolve at all.
Common situations: Sharing attachment links with users lacking issue/project access; job attachments fetched with a foreign project's token; code comment attachments fetched by users without code read; stale links after permissions were tightened.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of theonedev/onedev@d44925c47c (2026-09-06).
Data as JSON: /api/errors/f06a6cb3f476edc6.
Report an issue: GitHub.