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.");
+    }
 }

Reply via email to