Skip to content

Commit 12c34fc

Browse files
committed
fix(security): bound trusted package URL prefixes
1 parent e366349 commit 12c34fc

2 files changed

Lines changed: 52 additions & 3 deletions

File tree

scripts/resolve-openclaw-package-candidate.mjs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,14 @@ function parseTrustedPort(value) {
890890
return Number.NaN;
891891
}
892892

893+
function pathnameMatchesTrustedPrefix(pathname, prefix) {
894+
if (prefix === "/") {
895+
return true;
896+
}
897+
const normalizedPrefix = prefix.endsWith("/") ? prefix : `${prefix}/`;
898+
return pathname === prefix || pathname.startsWith(normalizedPrefix);
899+
}
900+
893901
function toPathPrefixes(value, sourceId) {
894902
const prefixes = value === undefined ? ["/"] : value;
895903
if (!Array.isArray(prefixes) || prefixes.length === 0) {
@@ -981,7 +989,11 @@ function validateTrustedPackageDownloadUrl(parsed, trustedSource, options = {})
981989
`package_url port ${packageUrlPort(parsed)} is not allowed by trusted package source ${trustedSource.id}`,
982990
);
983991
}
984-
if (!trustedSource.pathPrefixes.some((prefix) => parsed.pathname.startsWith(prefix))) {
992+
if (
993+
!trustedSource.pathPrefixes.some((prefix) =>
994+
pathnameMatchesTrustedPrefix(parsed.pathname, prefix),
995+
)
996+
) {
985997
throw new Error(
986998
`package_url path is not allowed by trusted package source ${trustedSource.id}`,
987999
);

test/scripts/resolve-openclaw-package-candidate.test.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,7 @@ describe("resolve-openclaw-package-candidate", () => {
693693
status: 200,
694694
});
695695
},
696-
lookupHost: lookupAddresses([{ address: "10.0.0.8", family: 4 }]),
696+
lookupHost: lookupAddresses([{ address: "203.0.113.8", family: 4 }]),
697697
maxBytes: 3,
698698
trustedSource,
699699
});
@@ -713,7 +713,44 @@ describe("resolve-openclaw-package-candidate", () => {
713713
await expect(
714714
downloadUrl("https://packages.internal:8443/other/openclaw.tgz", target, {
715715
fetchImpl: unexpectedFetch,
716-
lookupHost: lookupAddresses([{ address: "10.0.0.8", family: 4 }]),
716+
lookupHost: lookupAddresses([{ address: "203.0.113.8", family: 4 }]),
717+
trustedSource,
718+
}),
719+
).rejects.toThrow("path is not allowed by trusted package source enterprise-artifactory");
720+
});
721+
722+
it("matches trusted package_url path prefixes on path segment boundaries", async () => {
723+
const dir = await mkdtemp(path.join(tmpdir(), "openclaw-package-download-"));
724+
tempDirs.push(dir);
725+
const target = path.join(dir, "openclaw.tgz");
726+
const trustedSource = {
727+
allowPrivateNetwork: true,
728+
hosts: ["packages.internal"],
729+
id: "enterprise-artifactory",
730+
pathPrefixes: ["/artifactory/openclaw"],
731+
ports: [8443],
732+
redirectHosts: ["packages.internal"],
733+
};
734+
const requestedUrls: string[] = [];
735+
736+
await downloadUrl("https://packages.internal:8443/artifactory/openclaw/pkg.tgz", target, {
737+
fetchImpl: async (url: URL) => {
738+
requestedUrls.push(url.toString());
739+
return new Response(new Uint8Array([1, 2, 3]), {
740+
headers: { "content-length": "3" },
741+
status: 200,
742+
});
743+
},
744+
lookupHost: lookupAddresses([{ address: "203.0.113.8", family: 4 }]),
745+
maxBytes: 3,
746+
trustedSource,
747+
});
748+
749+
expect(requestedUrls).toEqual(["https://packages.internal:8443/artifactory/openclaw/pkg.tgz"]);
750+
await expect(
751+
downloadUrl("https://packages.internal:8443/artifactory/openclaw-malicious/pkg.tgz", target, {
752+
fetchImpl: unexpectedFetch,
753+
lookupHost: lookupAddresses([{ address: "203.0.113.8", family: 4 }]),
717754
trustedSource,
718755
}),
719756
).rejects.toThrow("path is not allowed by trusted package source enterprise-artifactory");

0 commit comments

Comments
 (0)