This is an automated email from the ASF dual-hosted git repository.
lhotari pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar.git
The following commit(s) were added to refs/heads/master by this push:
new 8ef825d9bc6 [fix][sec] Prevent path traversal in PackageName
toRestPath (#25628)
8ef825d9bc6 is described below
commit 8ef825d9bc6f08d7824ccb71749c798c7ea3617a
Author: Praveen Kumar <[email protected]>
AuthorDate: Sat May 2 23:25:14 2026 +0530
[fix][sec] Prevent path traversal in PackageName toRestPath (#25628)
---
.../management/core/common/PackageName.java | 23 +++++++++++++++++++++-
.../management/core/common/PackageNameTest.java | 23 ++++++++++++++++++++++
2 files changed, 45 insertions(+), 1 deletion(-)
diff --git
a/pulsar-package-management/core/src/main/java/org/apache/pulsar/packages/management/core/common/PackageName.java
b/pulsar-package-management/core/src/main/java/org/apache/pulsar/packages/management/core/common/PackageName.java
index 73cbe4186f6..668bb716548 100644
---
a/pulsar-package-management/core/src/main/java/org/apache/pulsar/packages/management/core/common/PackageName.java
+++
b/pulsar-package-management/core/src/main/java/org/apache/pulsar/packages/management/core/common/PackageName.java
@@ -18,11 +18,13 @@
*/
package org.apache.pulsar.packages.management.core.common;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
+import com.google.common.net.UrlEscapers;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
@@ -107,6 +109,18 @@ public class PackageName {
String.format("%s://%s/%s/%s@%s", type.toString(), tenant,
namespace, name, version);
}
+ @VisibleForTesting
+ PackageName(PackageType type, String tenant, String namespace, String
name, String version) {
+ this.type = type;
+ this.tenant = tenant;
+ this.namespace = namespace;
+ this.name = name;
+ this.version = version;
+ this.completeName = String.format("%s/%s/%s", tenant, namespace, name);
+ this.completePackageName =
+ String.format("%s://%s/%s/%s@%s", type.toString(), tenant,
namespace, name, version);
+ }
+
public PackageType getPkgType() {
return this.type;
}
@@ -136,7 +150,14 @@ public class PackageName {
}
public String toRestPath() {
- return String.format("%s/%s/%s/%s/%s", type, tenant, namespace, name,
version);
+ // Use Guava's URL path segment escaper to safely encode each segment
and prevent path traversal (CWE-22).
+ var escaper = UrlEscapers.urlPathSegmentEscaper();
+ return String.format("%s/%s/%s/%s/%s",
+ type.toString(),
+ escaper.escape(tenant),
+ escaper.escape(namespace),
+ escaper.escape(name),
+ escaper.escape(version));
}
@Override
diff --git
a/pulsar-package-management/core/src/test/java/org/apache/pulsar/packages/management/core/common/PackageNameTest.java
b/pulsar-package-management/core/src/test/java/org/apache/pulsar/packages/management/core/common/PackageNameTest.java
index cece1093f8d..b1e82222e78 100644
---
a/pulsar-package-management/core/src/test/java/org/apache/pulsar/packages/management/core/common/PackageNameTest.java
+++
b/pulsar-package-management/core/src/test/java/org/apache/pulsar/packages/management/core/common/PackageNameTest.java
@@ -116,4 +116,27 @@ public class PackageNameTest {
PackageName name = PackageName.get("function://public/default/test");
Assert.assertEquals("function://public/default/test@latest",
name.toString());
}
+
+ @Test
+ public void testPathTraversalBypassConstructor() throws Exception {
+ // Use the package-private constructor annotated with
@VisibleForTesting
+ // to inject a traversal payload directly, bypassing normal Splitter
validation.
+ PackageName packageName = new PackageName(
+ PackageType.FUNCTION,
+ "tenant-a/../../system-tenant",
+ "ns",
+ "name",
+ "v1"
+ );
+
+ // Verify that path separators in package components are
percent-encoded in the generated REST path.
+ String expectedSafePath =
"function/tenant-a%2F..%2F..%2Fsystem-tenant/ns/name/v1";
+
+ // Invoke the method under test
+ String actualPath = packageName.toRestPath();
+
+ // This assertion verifies that traversal characters are encoded
rather than emitted as raw slashes.
+ Assert.assertEquals(actualPath, expectedSafePath,
+ "The toRestPath method must encode path traversal characters
in package components.");
+ }
}