Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ballerina/tests/client_endpoint_negative_test.bal
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public function testConnectionWithInvalidConfiguration() returns error? {
public function testReadNonExistingFile() returns error? {
stream<byte[] & readonly, io:Error?>|Error str = (<Client>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");
Expand Down
2 changes: 1 addition & 1 deletion ballerina/tests/client_endpoint_test.bal
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
Expand Down
2 changes: 1 addition & 1 deletion ballerina/tests/secure_client_endpoint_test.bal
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}
}
Expand Down
2 changes: 1 addition & 1 deletion changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -68,8 +70,9 @@ public VfsClientConnectorImpl(Map<String, String> 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());
}
}

Expand All @@ -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;
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -157,15 +162,16 @@ 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");
}
}
}
}
} 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:
Expand All @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -115,12 +114,12 @@ private static void setSftpOptions(Map<String, String> 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:***@");
}
}
10 changes: 7 additions & 3 deletions native/src/main/java/io/ballerina/stdlib/ftp/util/FtpUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -161,14 +162,17 @@ public static Map<String, String> 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) {
Expand Down
Loading