Reject relative package URIs (#1683)

This commit is contained in:
Islon Scherer
2026-06-17 15:39:03 +02:00
committed by GitHub
parent fc8fe86e5a
commit bfac0d66ed
6 changed files with 95 additions and 4 deletions
@@ -45,6 +45,7 @@ import java.util.stream.StreamSupport;
import java.util.zip.ZipInputStream;
import org.graalvm.collections.EconomicMap;
import org.jspecify.annotations.Nullable;
import org.pkl.core.PklBugException;
import org.pkl.core.SecurityManager;
import org.pkl.core.SecurityManagerException;
import org.pkl.core.http.HttpClient;
@@ -456,10 +457,17 @@ final class PackageResolvers {
}
private Path getRelativePath(PackageUri uri) {
return Path.of(
CACHE_DIR_PREFIX,
IoUtils.encodePath(uri.getUri().getAuthority()),
getEffectivePackageUriPath(uri));
var relativePath =
Path.of(
CACHE_DIR_PREFIX,
IoUtils.encodePath(uri.getUri().getAuthority()),
getEffectivePackageUriPath(uri));
// ensure the derived path cannot escape the cache directory
var resolved = cacheDir.resolve(relativePath).normalize();
if (!resolved.startsWith(cacheDir.normalize())) {
throw new PklBugException("Package URI escapes the cache directory");
}
return relativePath;
}
private String getLastSegmentName(PackageUri packageUri) {
@@ -61,6 +61,13 @@ public final class PackageUri {
throw new URISyntaxException(
uri.toString(), ErrorMessages.create("missingPathInPackageUri", uri));
}
// reject `..` segments, percent-encoded or not
for (var segment : path.split("/", -1)) {
if (segment.equals("..")) {
throw new URISyntaxException(
uri.toString(), ErrorMessages.create("invalidRelativePathInPackageUri"));
}
}
var versionIdx = path.lastIndexOf('@');
if (versionIdx == -1) {
throw new URISyntaxException(
@@ -861,6 +861,9 @@ Package URIs must have an authority component.\n\
\n\
For example, `example.com` in URI `project://example.com/my/package@1.0.0`.
invalidRelativePathInPackageUri=\
Package URIs cannot contain `..` segments in the URI's "path" component.
unexpectedChecksumInPackageUri=\
Did not expect to find a checksum component in this package URI.
@@ -0,0 +1,2 @@
// relative path in package URI
res = import("package://localhost:0/%2e%2e/birds@0.5.0#/catalog/Bird.pkl")
@@ -0,0 +1,16 @@
–– Pkl Error ––
Module URI `package://localhost:0/%2e%2e/birds@0.5.0#/catalog/Bird.pkl` has invalid syntax.
x | res = import("package://localhost:0/%2e%2e/birds@0.5.0#/catalog/Bird.pkl")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at badImport12.error#res (file:///$snippetsDir/input/packages/badImport12.error.pkl)
Package URIs cannot contain `..` segments in the URI's "path" component.
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,55 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.core.packages
import java.net.URISyntaxException
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatCode
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
class PackageUriTest {
@Test
fun `rejects percent-encoded dot-dot path segments`() {
val err =
assertThrows<URISyntaxException> {
PackageUri("package://attacker.com/%2e%2e/legit.example.com/legit@1.2.3")
}
assertThat(err).hasMessageContaining("..")
}
@Test
fun `rejects literal dot-dot path segments`() {
assertThrows<URISyntaxException> { PackageUri("package://attacker.com/../legit@1.2.3") }
}
@Test
fun `rejects trailing dot-dot segment`() {
assertThrows<URISyntaxException> { PackageUri("package://attacker.com/foo@1.2.3/%2e%2e") }
}
@Test
fun `accepts a valid package URI`() {
assertThatCode { PackageUri("package://example.com/my/package@1.0.0") }
.doesNotThrowAnyException()
}
@Test
fun `does not reject path segments that merely contain dots`() {
assertThatCode { PackageUri("package://example.com/my..pkg/..foo/bar..@1.0.0") }
.doesNotThrowAnyException()
}
}