theonedev/onedev · error · IllegalArgumentException

Invalid attachment parameter

Error message

Invalid attachment parameter

What it means

AttachmentResource rejects an 'attachment' parameter containing ".." with IllegalArgumentException("Invalid attachment parameter"). This is a path-traversal guard: the attachment name must be a plain file name, never a relative path escaping the attachment storage directory.

Source

Thrown at server-core/src/main/java/io/onedev/server/web/resource/AttachmentResource.java:98

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

		ResourceResponse response = new ResourceResponse();
		response.setContentLength(getAttachmentService().getAttachmentInfo(projectId, attachmentGroup, attachment).getLength());
		
		response.getHeaders().addHeader("X-Content-Type-Options", "nosniff");
		response.setContentType(MimeTypes.OCTET_STREAM);

		response.setFileName(URLEncoder.encode(attachment, UTF_8));

		response.setWriteCallback(new WriteCallback() {

			@Override
			public void writeData(Attributes attributes) throws IOException {
				String activeServer = getProjectService().getActiveServer(projectId, true);
				ClusterService clusterService = OneDev.getInstance(ClusterService.class);
				if (activeServer.equals(clusterService.getLocalServerAddress())) {
					read(getAttachmentService().getAttachmentLockName(projectId, attachmentGroup), () -> {
						File attachmentFile = new File(getAttachmentService().getAttachmentGroupDir(projectId, attachmentGroup), attachment);

View on GitHub (pinned to d44925c47c)

Solutions

  1. Pass only the bare attachment file name as returned when it was uploaded (no slashes, no "..").
  2. If a file name genuinely contains "..", rename it on upload and use the new name.
  3. If this appears in logs as probing, treat it as an attack attempt; block or rate-limit the source.

Example fix

// before
GET /~resource/attachments/1/issue-42?attachment=../../secrets.txt -> 400 Invalid attachment parameter
// after
GET /~resource/attachments/1/issue-42?attachment=secrets.txt
Defensive patterns

Strategy: validation

Validate before calling

if (attachmentName.includes('..') || attachmentName.includes('/') || attachmentName.includes('\\')) {
  throw new Error('attachment must be a bare file name without path segments');
}

Type guard

function isSafeAttachmentName(name) {
  return typeof name === 'string' && /^[^/\\]+$/.test(name) && !name.includes('..');
}

Try / catch

try { await fetch(urlWith(attachment)); } catch (e) { if (String(e.message).includes('Invalid attachment parameter')) sanitizeAndRetry(attachment); else throw e; }

Prevention

When it happens

Trigger: GET with ?attachment=../../etc/passwd or any value containing the ".." substring — including legitimate-looking names like "my..file.txt".

Common situations: Malicious/scanner probes against the endpoint; scripts that pass full paths (e.g. "dir/file.txt") or names containing ".." instead of the bare file name returned by the attachment listing.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


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