{"record":{"id":"1221730a2e9e5fea","repo":"apache/dolphinscheduler","slug":"failed-to-download-to-filedownloadpathnormalized","errorCode":null,"errorMessage":"failed to download to ${fileDownloadPathNormalized}","messagePattern":"failed to download to (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"dolphinscheduler-storage-plugin/dolphinscheduler-storage-cos/src/main/java/org/apache/dolphinscheduler/plugin/storage/cos/CosStorageOperator.java","lineNumber":145,"sourceCode":"            throw new FileAlreadyExistsException(\"directory: \" + cosKey + \" already exists\");\n        }\n        ObjectMetadata metadata = new ObjectMetadata();\n        metadata.setContentLength(0L);\n        InputStream emptyContent = new ByteArrayInputStream(new byte[0]);\n        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, cosKey, emptyContent, metadata);\n        cosClient.putObject(putObjectRequest);\n    }\n\n    @SneakyThrows\n    @Override\n    public void download(String srcFilePath, String dstFilePath, boolean overwrite) {\n        String cosKey = transformAbsolutePathToCOSKey(srcFilePath);\n        Path dsTempFolder = Paths.get(FileUtils.DATA_BASEDIR).normalize().toAbsolutePath();\n        Path fileDownloadPathNormalized = dsTempFolder.resolve(dstFilePath).normalize().toAbsolutePath();\n        if (!fileDownloadPathNormalized.startsWith(dsTempFolder)) {\n            // if the destination file path is NOT in DS temp folder (e.g., '/tmp/dolphinscheduler'),\n            // an IllegalArgumentException should be thrown.\n            throw new IllegalArgumentException(\"failed to download to \" + fileDownloadPathNormalized);\n        }\n        File dstFile = fileDownloadPathNormalized.toFile();\n        if (dstFile.isDirectory()) {\n            Files.delete(dstFile.toPath());\n        } else {\n            FileUtils.createDirectoryWithPermission(dstFile.getParentFile().toPath(), FileUtils.PERMISSION_755);\n        }\n\n        GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, cosKey);\n        Download download = cosTransferManager.download(getObjectRequest, dstFile);\n        download.waitForCompletion();\n    }\n\n    @Override\n    public boolean exists(String fileName) {\n        String cosKey = transformAbsolutePathToCOSKey(fileName);\n        return cosClient.doesObjectExist(bucketName, cosKey);\n    }","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/apache/dolphinscheduler/blob/02eac45a1b6676e639fcbfb4be2243de5771b05d/dolphinscheduler-storage-plugin/dolphinscheduler-storage-cos/src/main/java/org/apache/dolphinscheduler/plugin/storage/cos/CosStorageOperator.java#L127-L163","documentation":"CosStorageOperator.download resolves the destination path under FileUtils.DATA_BASEDIR (the DS temp folder) and refuses to write anywhere else. If the normalized destination escapes that folder, it throws IllegalArgumentException to block path traversal outside the sandbox.","triggerScenarios":"Calling download(srcFilePath, dstFilePath) where dstFilePath resolves (after normalize().toAbsolutePath()) outside the dolphinscheduler temp base dir — e.g., an absolute dstFilePath like '/etc/foo' or a path containing enough '../' segments to escape.","commonSituations":"Passing an absolute destination path instead of a relative one; user-supplied resource paths containing '..'; misconfigured task output paths pointing to other system directories.","solutions":["Pass a path RELATIVE to the DS temp folder as dstFilePath","Strip/normalize '..' segments and any leading '/' from user-supplied destination paths before calling download","If the file truly must go elsewhere, download into the temp folder then move it with your own code"],"exampleFix":"// before\noperator.download(srcPath, \"/data/out/result.csv\");\n// after\noperator.download(srcPath, \"resources/out/result.csv\"); // stays inside DATA_BASEDIR","handlingStrategy":"validation","validationCode":"Path base = Paths.get(FileUtils.DATA_BASEDIR).normalize().toAbsolutePath();\nPath dst = base.resolve(userDst).normalize().toAbsolutePath();\nif (!dst.startsWith(base)) throw new IllegalArgumentException(\"dst must be inside DS temp folder\");","typeGuard":null,"tryCatchPattern":"try {\n    operator.download(src, dst);\n} catch (IllegalArgumentException e) {\n    log.error(\"Destination escaped DS temp folder: {}\", dst);\n}","preventionTips":["Always pass destinations relative to the DS temp folder","Sanitize '..' and leading '/' from user-supplied paths","Never concatenate raw user input into the destination path"],"tags":["storage","cos","path-traversal","security"],"backgroundTag":"path-traversal-blocked","analyzedSha":"02eac45a1b6676e639fcbfb4be2243de5771b05d","analyzedAt":"2026-09-06T17:43:00.555Z","contentChangedAt":"2026-09-06T17:43:00.555Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}