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";
			else

View on GitHub (pinned to d44925c47c)

Solutions

  1. 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
  2. If the request is from a CI job, use that project's own job token/secret rather than one from another project
  3. 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

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.

Related errors


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