{"record":{"id":"5a060ee1d6e4e422","repo":"theonedev/onedev","slug":"invalid-attachment-parameter","errorCode":null,"errorMessage":"Invalid attachment parameter","messagePattern":"Invalid attachment parameter","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"server-core/src/main/java/io/onedev/server/web/resource/AttachmentResource.java","lineNumber":98,"sourceCode":"\t\t\t\t\tif (!SecurityUtils.canReadCode(project))\n\t\t\t\t\t\tthrow new UnauthorizedException();\n\t\t\t\t} else if ((issue = OneDev.getInstance(IssueService.class).find(attachmentGroup)) != null) {\n\t\t\t\t\tif (!SecurityUtils.canAccessIssue(issue))\n\t\t\t\t\t\tthrow new UnauthorizedException();\n\t\t\t\t} else if ((build = OneDev.getInstance(BuildService.class).find(attachmentGroup)) != null) {\n\t\t\t\t\tif (!SecurityUtils.canAccessProject(build.getProject()))\n\t\t\t\t\t\tthrow new UnauthorizedException();\n\t\t\t\t} else if (!SecurityUtils.canAccessProject(project)) {\n\t\t\t\t\tthrow new UnauthorizedException();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tString attachment = params.get(PARAM_ATTACHMENT).toString();\n\t\tif (StringUtils.isBlank(attachment))\n\t\t\tthrow new IllegalArgumentException(\"attachment parameter has to be specified\");\n\t\telse if (attachment.contains(\"..\"))\n\t\t\tthrow new IllegalArgumentException(\"Invalid attachment parameter\");\n\n\t\tResourceResponse response = new ResourceResponse();\n\t\tresponse.setContentLength(getAttachmentService().getAttachmentInfo(projectId, attachmentGroup, attachment).getLength());\n\t\t\n\t\tresponse.getHeaders().addHeader(\"X-Content-Type-Options\", \"nosniff\");\n\t\tresponse.setContentType(MimeTypes.OCTET_STREAM);\n\n\t\tresponse.setFileName(URLEncoder.encode(attachment, UTF_8));\n\n\t\tresponse.setWriteCallback(new WriteCallback() {\n\n\t\t\t@Override\n\t\t\tpublic void writeData(Attributes attributes) throws IOException {\n\t\t\t\tString activeServer = getProjectService().getActiveServer(projectId, true);\n\t\t\t\tClusterService clusterService = OneDev.getInstance(ClusterService.class);\n\t\t\t\tif (activeServer.equals(clusterService.getLocalServerAddress())) {\n\t\t\t\t\tread(getAttachmentService().getAttachmentLockName(projectId, attachmentGroup), () -> {\n\t\t\t\t\t\tFile attachmentFile = new File(getAttachmentService().getAttachmentGroupDir(projectId, attachmentGroup), attachment);","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-core/src/main/java/io/onedev/server/web/resource/AttachmentResource.java#L80-L116","documentation":"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.","triggerScenarios":"GET with ?attachment=../../etc/passwd or any value containing the \"..\" substring — including legitimate-looking names like \"my..file.txt\".","commonSituations":"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.","solutions":["Pass only the bare attachment file name as returned when it was uploaded (no slashes, no \"..\").","If a file name genuinely contains \"..\", rename it on upload and use the new name.","If this appears in logs as probing, treat it as an attack attempt; block or rate-limit the source."],"exampleFix":"// before\nGET /~resource/attachments/1/issue-42?attachment=../../secrets.txt -> 400 Invalid attachment parameter\n// after\nGET /~resource/attachments/1/issue-42?attachment=secrets.txt","handlingStrategy":"validation","validationCode":"if (attachmentName.includes('..') || attachmentName.includes('/') || attachmentName.includes('\\\\')) {\n  throw new Error('attachment must be a bare file name without path segments');\n}","typeGuard":"function isSafeAttachmentName(name) {\n  return typeof name === 'string' && /^[^/\\\\]+$/.test(name) && !name.includes('..');\n}","tryCatchPattern":"try { await fetch(urlWith(attachment)); } catch (e) { if (String(e.message).includes('Invalid attachment parameter')) sanitizeAndRetry(attachment); else throw e; }","preventionTips":["Never pass user-controlled paths directly into the attachment parameter","Store and echo back the exact name returned at upload time","Sanitize names at upload (strip path separators and '..')"],"tags":["onedev","http","security","path-traversal"],"backgroundTag":"path-traversal-blocked","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"}