{"record":{"id":"8c6e455d5a10291c","repo":"theonedev/onedev","slug":"invalid-report-name","errorCode":null,"errorMessage":"Invalid report name","messagePattern":"Invalid report name","errorType":"validation","errorClass":"ExplicitException","httpStatus":400,"severity":"error","filePath":"server-core/src/main/java/io/onedev/server/codequality/CoverageStats.java","lineNumber":96,"sourceCode":"\t\t\tcheckReportName(reportName);\n\t\tLong projectId = build.getProject().getId();\n\t\tMap<String, Map<Integer, CoverageStatus>> coveragesMap = OneDev.getInstance(ProjectService.class)\n\t\t\t\t.runOnActiveServer(projectId, new GetLineCoverages(projectId, build.getNumber(),\n\t\t\t\t\t\tblobPath, reportName));\n\t\tMap<Integer, CoverageStatus> coverages = new HashMap<>();\n\t\tfor (var entry: coveragesMap.entrySet()) {\n\t\t\tif (SecurityUtils.canAccessReport(build, entry.getKey())) {\n\t\t\t\tentry.getValue().forEach((key, value) -> {\n\t\t\t\t\tcoverages.merge(key, value, CoverageStatus::mergeWith);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\treturn coverages;\n\t}\n\n\tprivate static void checkReportName(String reportName) {\n\t\tif (reportName.contains(\"..\"))\n\t\t\tthrow new ExplicitException(\"Invalid report name\");\n\t}\n\t\n\tpublic void writeTo(File reportDir) {\n\t\tFile reportFile = new File(reportDir, REPORT);\n\t\ttry (var os = new BufferedOutputStream(new FileOutputStream(reportFile), BUFFER_SIZE)) {\n\t\t\tSerializationUtils.serialize(this, os);\n\t\t} catch (IOException e) {\n\t\t\tthrow new RuntimeException(e);\n\t\t}\n\t}\n\n\tpublic static String getReportLockName(Build build) {\n\t\treturn getReportLockName(build.getProject().getId(), build.getNumber());\n\t}\n\t\n\tpublic static String getReportLockName(Long projectId, Long buildNumber) {\n\t\treturn CoverageStats.class.getName() + \":\"\t+ projectId + \":\" + buildNumber;\n\t}","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-core/src/main/java/io/onedev/server/codequality/CoverageStats.java#L78-L114","documentation":"CoverageStats.checkReportName rejects any coverage report name containing \"..\" because the name is used to build file paths under a report directory; \"..\" would allow escaping that directory (path traversal). OneDev surfaces this as an ExplicitException with the message \"Invalid report name\".","triggerScenarios":"Calling CoverageStats.getLineCoverages(reportName) or readFrom(reportDir, reportName) with a reportName containing \"..\", e.g. \"../secrets\" or \"a..b\" patterns used as directory components.","commonSituations":"Untrusted report names piped from CI config, job step parameters, or REST payloads into the code quality report APIs; malicious or buggy build scripts passing relative paths as report names.","solutions":["Remove any \"..\" from the report name before calling the API","Use a plain report name (single path segment, no traversal) and resolve parent directories outside the report name","Sanitize/validate external input at the job or plugin boundary before passing it as a report name"],"exampleFix":"// before\nvar coverages = coverage.getLineCoverages(\"../other-project/report\");\n// after\nString reportName = \"unit-tests\"; // no path separators or \"..\"\nvar coverages = coverage.getLineCoverages(reportName);","handlingStrategy":"validation","validationCode":"// validate before calling CoverageStats APIs\nif (reportName == null || reportName.contains(\"..\"))\n    throw new IllegalArgumentException(\"report name must not contain '..'\");\nif (reportName.contains(\"/\") || reportName.contains(\"\\\\\"))\n    throw new IllegalArgumentException(\"report name must be a single path segment\");","typeGuard":"function isValidReportName(name) { return typeof name === 'string' && name.length > 0 && !name.includes('..') && !/[/\\\\]/.test(name); }","tryCatchPattern":"try {\n  coverages = CoverageStats.readFrom(reportDir, reportName);\n} catch (ExplicitException e) {\n  if (\"Invalid report name\".equals(e.getMessage())) {\n    reportName = sanitizeReportName(reportName); // strip '..' and separators\n    coverages = CoverageStats.readFrom(reportDir, reportName);\n  } else throw e;\n}","preventionTips":["Treat report names from CI configs as untrusted input and sanitize them","Use fixed, well-known report name constants where possible","Add a pre-submit validator that rejects names containing '..'","Never build report paths by concatenating user input"],"tags":["security","path-traversal","validation","code-quality"],"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-14T05:17:10.506Z"}