Skip to content

Commit 34efe28

Browse files
Praveenkumar76poorbarcode
authored andcommitted
[fix][sec] Prevent path traversal in PackageName toRestPath (apache#25628)
1 parent 59be0af commit 34efe28

2 files changed

Lines changed: 45 additions & 1 deletion

File tree

  • pulsar-package-management/core/src

pulsar-package-management/core/src/main/java/org/apache/pulsar/packages/management/core/common/PackageName.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818
*/
1919
package org.apache.pulsar.packages.management.core.common;
2020

21+
import com.google.common.annotations.VisibleForTesting;
2122
import com.google.common.base.Splitter;
2223
import com.google.common.base.Strings;
2324
import com.google.common.cache.CacheBuilder;
2425
import com.google.common.cache.CacheLoader;
2526
import com.google.common.cache.LoadingCache;
27+
import com.google.common.net.UrlEscapers;
2628
import java.util.List;
2729
import java.util.Objects;
2830
import java.util.concurrent.ExecutionException;
@@ -107,6 +109,18 @@ private PackageName(String packageName) {
107109
String.format("%s://%s/%s/%s@%s", type.toString(), tenant, namespace, name, version);
108110
}
109111

112+
@VisibleForTesting
113+
PackageName(PackageType type, String tenant, String namespace, String name, String version) {
114+
this.type = type;
115+
this.tenant = tenant;
116+
this.namespace = namespace;
117+
this.name = name;
118+
this.version = version;
119+
this.completeName = String.format("%s/%s/%s", tenant, namespace, name);
120+
this.completePackageName =
121+
String.format("%s://%s/%s/%s@%s", type.toString(), tenant, namespace, name, version);
122+
}
123+
110124
public PackageType getPkgType() {
111125
return this.type;
112126
}
@@ -136,7 +150,14 @@ public String toString() {
136150
}
137151

138152
public String toRestPath() {
139-
return String.format("%s/%s/%s/%s/%s", type, tenant, namespace, name, version);
153+
// Use Guava's URL path segment escaper to safely encode each segment and prevent path traversal (CWE-22).
154+
var escaper = UrlEscapers.urlPathSegmentEscaper();
155+
return String.format("%s/%s/%s/%s/%s",
156+
type.toString(),
157+
escaper.escape(tenant),
158+
escaper.escape(namespace),
159+
escaper.escape(name),
160+
escaper.escape(version));
140161
}
141162

142163
@Override

pulsar-package-management/core/src/test/java/org/apache/pulsar/packages/management/core/common/PackageNameTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,27 @@ public void testPackageNameErrors() {
116116
PackageName name = PackageName.get("function://public/default/test");
117117
Assert.assertEquals("function://public/default/test@latest", name.toString());
118118
}
119+
120+
@Test
121+
public void testPathTraversalBypassConstructor() throws Exception {
122+
// Use the package-private constructor annotated with @VisibleForTesting
123+
// to inject a traversal payload directly, bypassing normal Splitter validation.
124+
PackageName packageName = new PackageName(
125+
PackageType.FUNCTION,
126+
"tenant-a/../../system-tenant",
127+
"ns",
128+
"name",
129+
"v1"
130+
);
131+
132+
// Verify that path separators in package components are percent-encoded in the generated REST path.
133+
String expectedSafePath = "function/tenant-a%2F..%2F..%2Fsystem-tenant/ns/name/v1";
134+
135+
// Invoke the method under test
136+
String actualPath = packageName.toRestPath();
137+
138+
// This assertion verifies that traversal characters are encoded rather than emitted as raw slashes.
139+
Assert.assertEquals(actualPath, expectedSafePath,
140+
"The toRestPath method must encode path traversal characters in package components.");
141+
}
119142
}

0 commit comments

Comments
 (0)