theonedev/onedev · error · UnauthorizedException
Unauthorized
Error message
Unauthorized
What it means
ArchiveResource performs an authorization check: unless the caller is the internal system/cluster identity (SecurityUtils.isSystem()), it loads the project and throws Shiro's UnauthorizedException when SecurityUtils.canReadCode(project) is false. It means the authenticated user is not allowed to read code in the target project.
Source
Thrown at server-core/src/main/java/io/onedev/server/web/resource/ArchiveResource.java:76
protected ResourceResponse newResourceResponse(Attributes attributes) {
PageParameters params = attributes.getParameters();
Long projectId = params.get(PARAM_PROJECT).toLong();
String revision = params.get(PARAM_REVISION).toString();
if (StringUtils.isBlank(revision))
throw new IllegalArgumentException("revision parameter has to be specified");
String format = params.get(PARAM_FORMAT).toString();
if (!FORMAT_ZIP.equals(format) && !FORMAT_TGZ.equals(format)) {
throw new IllegalArgumentException("format parameter should be specified either zip or tar.gz");
}
if (!SecurityUtils.isSystem()) {
// Perform database operations only if it is not a cluster access to avoid possible deadlocks
Project project = OneDev.getInstance(ProjectService.class).load(projectId);
if (!SecurityUtils.canReadCode(project))
throw new UnauthorizedException();
}
ResourceResponse response = new ResourceResponse();
response.setContentType(MimeTypes.OCTET_STREAM);
response.disableCaching();
try {
String fileName;
if (GitUtils.ref2branch(revision) != null)
fileName = GitUtils.ref2branch(revision);
else if (GitUtils.ref2tag(revision) != null)
fileName = GitUtils.ref2tag(revision);
else
fileName = revision;
if (FORMAT_ZIP.equals(format))
fileName += ".zip";
elseView on GitHub (pinned to d44925c47c)
Solutions
- Log in as a user with at least code read permission on the project, or request the project owner to grant 'Read code' via project/role permissions
- If the request is from a CI job, use that project's own job token/secret rather than one from another project
- Verify you are hitting the correct project id in the URL (wrong project id can point to a project you cannot read)
Defensive patterns
Strategy: validation
Validate before calling
const me = await onedevApi.get('/me'); // ensure user/token has code read on the project
const perm = await onedevApi.get(`/projects/${projectId}/permissions`);
if (!perm.canReadCode) throw new Error('user lacks code read permission on project ' + projectId); Try / catch
try { await fetch(archiveUrl); } catch (e) { if (e.status === 403 || /unauthorized/i.test(e.message)) console.error('Grant code read permission for this project or use a project member/token'); } Prevention
- Confirm project membership/role before hitting archive endpoints
- Use per-project CI job tokens, not cross-project secrets
- Re-test links after permission changes
When it happens
Trigger: An authenticated (or anonymous) user requests a project archive for a project where their account lacks code-read permission; a job token without code access calls the archive endpoint.
Common situations: Sharing archive URLs with users who are not project members; CI job secrets belonging to another project; permission changes after role removal; anonymous access when the project is private.
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/bb6bc14ef82160c8.
Report an issue: GitHub.