{"record":{"id":"ccb6afc4f858650d","repo":"theonedev/onedev","slug":"invalid-old-path-oldpath","errorCode":null,"errorMessage":"Invalid old path: <oldPath>","messagePattern":"Invalid old path: <oldPath>","errorType":"validation","errorClass":"BlobEditException","httpStatus":null,"severity":"error","filePath":"server-core/src/main/java/io/onedev/server/git/BlobEdits.java","lineNumber":43,"sourceCode":"\t\n\tprivate static final long serialVersionUID = 1L;\n\n\tprivate final Set<String> oldPaths;\n\t\n\tprivate final Map<String, BlobContent> newBlobs;\n\t\n\tpublic BlobEdits() {\n\t\tthis(new HashSet<>(), new HashMap<>());\n\t}\n\t\n\tpublic BlobEdits(Set<String> oldPaths, Map<String, BlobContent> newBlobs) {\n\t\tthis.oldPaths = new HashSet<>();\n\t\tfor (String oldPath: oldPaths) {\n\t\t\tString normalizedPath = GitUtils.normalizePath(oldPath);\n\t\t\tif (normalizedPath != null)\n\t\t\t\tthis.oldPaths.add(normalizedPath);\n\t\t\telse\n\t\t\t\tthrow new BlobEditException(\"Invalid old path: \" + oldPath);\n\t\t}\n\t\tthis.newBlobs = new HashMap<>();\n\t\tfor (Map.Entry<String, BlobContent> entry: newBlobs.entrySet()) { \n\t\t\tString normalizedPath = GitUtils.normalizePath(entry.getKey());\n\t\t\tif (normalizedPath != null)\n\t\t\t\tthis.newBlobs.put(normalizedPath, entry.getValue());\n\t\t\telse\n\t\t\t\tthrow new BlobEditException(\"Invalid new path: \" + entry.getKey());\n\t\t}\n\t}\n\n\tpublic Set<String> getOldPaths() {\n\t\treturn oldPaths;\n\t}\n\n\tpublic Map<String, BlobContent> getNewBlobs() {\n\t\treturn newBlobs;\n\t}","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-core/src/main/java/io/onedev/server/git/BlobEdits.java#L25-L61","documentation":"The BlobEdits constructor normalizes every path in the oldPaths set via GitUtils.normalizePath, which returns null for paths that are invalid in git terms (empty, absolute, containing '..' or otherwise unnormalizable). If any old path fails normalization, a BlobEditException is thrown at construction time so a malformed edit set is never built.","triggerScenarios":"new BlobEdits(oldPaths, newBlobs) where oldPaths contains an empty string, a path like \"/abs/path\", \"../escape\", or any string GitUtils.normalizePath cannot reduce to a valid relative path.","commonSituations":"REST/API callers deleting or moving files with absolute or '..'-containing paths; UI/API input not sanitized before constructing edit sets; paths with redundant or malformed segments that normalize to null.","solutions":["Pass only normalized relative paths (no leading '/', no '..' segments, non-empty) in oldPaths.","Run GitUtils.normalizePath on each path yourself before constructing BlobEdits and skip/reject null results.","Catch BlobEditException and report which path was invalid to the caller.","Sanitize user-supplied file paths in the API/UI layer before building edit sets."],"exampleFix":"// before\nnew BlobEdits(Set.of(\"/src/../../etc/passwd\"), Map.of()); // throws\n// after\nString p = GitUtils.normalizePath(\"/src/../../etc/passwd\"); // null -> reject earlier\nif (p != null) new BlobEdits(Set.of(p), Map.of());","handlingStrategy":"validation","validationCode":"Set<String> safe = new HashSet<>();\nfor (String p : oldPaths) {\n    String n = GitUtils.normalizePath(p);\n    if (n == null) throw new IllegalArgumentException(\"Invalid old path: \" + p);\n    safe.add(n);\n}","typeGuard":"boolean isValidGitPath(String p) {\n    return p != null && GitUtils.normalizePath(p) != null;\n}","tryCatchPattern":"try {\n    BlobEdits edits = new BlobEdits(oldPaths, newBlobs);\n} catch (BlobEditException e) {\n    // report invalid path to caller\n}","preventionTips":["Always use relative, '/'-separated paths without '..' segments.","Normalize all paths with GitUtils.normalizePath before constructing BlobEdits.","Reject absolute paths and empty strings at the input boundary.","Treat any user-supplied path as untrusted and sanitize it."],"tags":["git","path-validation","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"}