diff --git a/ballerina/tests/client_endpoint_negative_test.bal b/ballerina/tests/client_endpoint_negative_test.bal index 8223838d1..ce1f12fc5 100644 --- a/ballerina/tests/client_endpoint_negative_test.bal +++ b/ballerina/tests/client_endpoint_negative_test.bal @@ -61,7 +61,7 @@ public function testConnectionWithInvalidConfiguration() returns error? { public function testReadNonExistingFile() returns error? { stream|Error str = (clientEp)->get("/home/in/nonexisting.txt"); if str is Error { - test:assertEquals(str.message(), "Failed to read file: ftp://wso2:wso2123@127.0.0.1:21212/home/in/nonexisting.txt not found", + test:assertEquals(str.message(), "Failed to read file: ftp://wso2:***@127.0.0.1:21212/home/in/nonexisting.txt not found", msg = "Unexpected error during the `get` operation of an non-existing file."); } else { test:assertFail(msg = "Found a non-error response while accessing a non-existing file path"); diff --git a/ballerina/tests/client_endpoint_test.bal b/ballerina/tests/client_endpoint_test.bal index 16a4a3615..2e8e48316 100644 --- a/ballerina/tests/client_endpoint_test.bal +++ b/ballerina/tests/client_endpoint_test.bal @@ -621,7 +621,7 @@ public function testDeleteFile() returns error? { } } else { test:assertEquals(str.message(), - "Failed to read file: ftp://wso2:wso2123@127.0.0.1:21212/home/in/test1.txt not found", + "Failed to read file: ftp://wso2:***@127.0.0.1:21212/home/in/test1.txt not found", msg = "Correct error is not given when the file is deleted." + str.message()); } } diff --git a/ballerina/tests/secure_client_endpoint_test.bal b/ballerina/tests/secure_client_endpoint_test.bal index 5f1e03df1..55005be7e 100644 --- a/ballerina/tests/secure_client_endpoint_test.bal +++ b/ballerina/tests/secure_client_endpoint_test.bal @@ -305,7 +305,7 @@ public function testSecureDeleteFileContent() returns error? { } } else { test:assertEquals(str.message(), - "Failed to read file: sftp://wso2:wso2123@127.0.0.1:21213/tempFile1.txt not found", + "Failed to read file: sftp://wso2:***@127.0.0.1:21213/tempFile1.txt not found", msg = "Correct error is not given when trying to get a non-existing file."); } } diff --git a/changelog.md b/changelog.md index 1952f2d65..f790f112a 100644 --- a/changelog.md +++ b/changelog.md @@ -10,7 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Fixed - [Fix the issue where the FTP URL is improperly formatted for the jail-break scnenarios](https://github.com/ballerina-platform/ballerina-library/issues/8267) - +- Fix exposed passwords in logs ## [2.14.0] - 2025-08-21 diff --git a/native/src/main/java/io/ballerina/stdlib/ftp/transport/client/connector/contractimpl/VfsClientConnectorImpl.java b/native/src/main/java/io/ballerina/stdlib/ftp/transport/client/connector/contractimpl/VfsClientConnectorImpl.java index d29148d06..6b83f0d72 100644 --- a/native/src/main/java/io/ballerina/stdlib/ftp/transport/client/connector/contractimpl/VfsClientConnectorImpl.java +++ b/native/src/main/java/io/ballerina/stdlib/ftp/transport/client/connector/contractimpl/VfsClientConnectorImpl.java @@ -44,6 +44,8 @@ import java.util.HashMap; import java.util.Map; +import static io.ballerina.stdlib.ftp.transport.server.util.FileTransportUtils.maskUrlPassword; + /** * Implementation for {@link VfsClientConnector} interface. */ @@ -68,8 +70,9 @@ public VfsClientConnectorImpl(Map config) fileURI = connectorConfig.get(FtpConstants.URI); path = fsManager.resolveFile(fileURI, opts); } catch (FileSystemException e) { + String safeUri = maskUrlPassword(fileURI); throw new RemoteFileSystemConnectorException("Error while connecting to the FTP server with URL: " - + (fileURI != null ? fileURI : ""), e.getCause()); + + (safeUri != null ? safeUri : ""), e.getCause()); } } @@ -94,7 +97,7 @@ public void send(RemoteFileSystemMessage message, FtpAction action, String fileP case MKDIR: if (fileObject.exists()) { throw new RemoteFileSystemConnectorException("Directory exists: " - + fileObject.getName().getURI()); + + maskUrlPassword(fileObject.getName().getURI())); } fileObject.createFolder(); break; @@ -130,7 +133,8 @@ public void send(RemoteFileSystemMessage message, FtpAction action, String fileP } } else { throw new RemoteFileSystemConnectorException( - "Failed to delete file: " + fileObject.getName().getURI() + " not found"); + "Failed to delete file: " + maskUrlPassword(fileObject.getName().getURI()) + + " not found"); } break; case RMDIR: @@ -141,7 +145,8 @@ public void send(RemoteFileSystemMessage message, FtpAction action, String fileP } } else { throw new RemoteFileSystemConnectorException( - "Failed to delete directory: " + fileObject.getName().getURI() + " not found"); + "Failed to delete directory: " + maskUrlPassword(fileObject.getName().getURI()) + + " not found"); } break; case RENAME: @@ -157,7 +162,7 @@ public void send(RemoteFileSystemMessage message, FtpAction action, String fileP fileObject.moveTo(finalPath); } else { throw new RemoteFileSystemConnectorException( - "The file at " + newPath.getURL().toString() + "The file at " + maskUrlPassword(newPath.getURL().toString()) + " already exists or it is a directory"); } } @@ -165,7 +170,8 @@ public void send(RemoteFileSystemMessage message, FtpAction action, String fileP } } else { throw new RemoteFileSystemConnectorException( - "Failed to rename file: " + fileObject.getName().getURI() + " not found"); + "Failed to rename file: " + maskUrlPassword(fileObject.getName().getURI()) + + " not found"); } break; case GET: @@ -180,7 +186,8 @@ public void send(RemoteFileSystemMessage message, FtpAction action, String fileP pathClose = false; } else { throw new RemoteFileSystemConnectorException( - "Failed to read file: " + fileObject.getName().getURI() + " not found"); + "Failed to read file: " + maskUrlPassword(fileObject.getName().getURI()) + + " not found"); } break; case SIZE: diff --git a/native/src/main/java/io/ballerina/stdlib/ftp/transport/server/util/FileTransportUtils.java b/native/src/main/java/io/ballerina/stdlib/ftp/transport/server/util/FileTransportUtils.java index f4f0a4181..84b2a755f 100644 --- a/native/src/main/java/io/ballerina/stdlib/ftp/transport/server/util/FileTransportUtils.java +++ b/native/src/main/java/io/ballerina/stdlib/ftp/transport/server/util/FileTransportUtils.java @@ -31,7 +31,6 @@ import java.time.Duration; import java.util.Locale; import java.util.Map; -import java.util.regex.Matcher; import java.util.regex.Pattern; import static io.ballerina.stdlib.ftp.util.FtpConstants.ENDPOINT_CONFIG_PREFERRED_METHODS; @@ -46,8 +45,8 @@ public final class FileTransportUtils { private FileTransportUtils() {} - private static final Pattern URL_PATTERN = Pattern.compile("[a-z]+://.*"); - private static final Pattern PASSWORD_PATTERN = Pattern.compile(":(?:[^/]+)@"); + private static final Pattern URL_PATTERN = Pattern.compile("^[a-z][a-z0-9+.-]*://", Pattern.CASE_INSENSITIVE); + private static final Pattern USERINFO_WITH_PASSWORD = Pattern.compile("://([^/@:]+):([^/@]*)@"); /** * A utility method for setting the relevant configurations for the file system in question. @@ -115,12 +114,12 @@ private static void setSftpOptions(Map options, FileSystemOption */ @ExcludeCoverageFromGeneratedReport public static String maskUrlPassword(String url) { - Matcher urlMatcher = URL_PATTERN.matcher(url); - if (urlMatcher.find()) { - Matcher pwdMatcher = PASSWORD_PATTERN.matcher(url); - return pwdMatcher.replaceFirst("\":***@\""); - } else { + if (url == null) { + return null; + } + if (!URL_PATTERN.matcher(url).find()) { return url; } + return USERINFO_WITH_PASSWORD.matcher(url).replaceFirst("://$1:***@"); } } diff --git a/native/src/main/java/io/ballerina/stdlib/ftp/util/FtpUtil.java b/native/src/main/java/io/ballerina/stdlib/ftp/util/FtpUtil.java index cd71e2e24..3ab86a1a9 100644 --- a/native/src/main/java/io/ballerina/stdlib/ftp/util/FtpUtil.java +++ b/native/src/main/java/io/ballerina/stdlib/ftp/util/FtpUtil.java @@ -52,6 +52,7 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; +import static io.ballerina.stdlib.ftp.transport.server.util.FileTransportUtils.maskUrlPassword; import static io.ballerina.stdlib.ftp.util.FtpConstants.ENDPOINT_CONFIG_PREFERRED_METHODS; import static io.ballerina.stdlib.ftp.util.FtpConstants.FTP_ANONYMOUS_PASSWORD; import static io.ballerina.stdlib.ftp.util.FtpConstants.FTP_ANONYMOUS_USERNAME; @@ -161,14 +162,17 @@ public static Map getAuthMap(BMap config) { * @return an error which will be propagated to ballerina user. */ public static BError createError(String message, String errorTypeName) { - return ErrorCreator.createError(ModuleUtils.getModule(), errorTypeName, StringUtils.fromString(message), + String safeMessage = maskUrlPassword(message); + return ErrorCreator.createError(ModuleUtils.getModule(), errorTypeName, StringUtils.fromString(safeMessage), null, null); } public static BError createError(String message, Throwable cause, String errorTypeName) { - return ErrorCreator.createError(ModuleUtils.getModule(), errorTypeName, StringUtils.fromString(message), + String safeMessage = maskUrlPassword(message); + return ErrorCreator.createError(ModuleUtils.getModule(), errorTypeName, StringUtils.fromString(safeMessage), cause == null ? null : cause instanceof BError ? - (BError) cause : ErrorCreator.createError(StringUtils.fromString(cause.getMessage())), null); + (BError) cause : ErrorCreator.createError(StringUtils + .fromString(maskUrlPassword(cause.getMessage()))), null); } public static Throwable findRootCause(Throwable throwable) {