fix: harden evidence URI validation
This commit is contained in:
@@ -276,6 +276,7 @@ function isSafeEvidencePattern(value: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function parsePublicHttpUri(value: string): URL | undefined {
|
function parsePublicHttpUri(value: string): URL | undefined {
|
||||||
|
if (value.trim() !== value || /[\u0000-\u001f\u007f\\]/u.test(value)) return undefined;
|
||||||
try {
|
try {
|
||||||
const parsed = new URL(value);
|
const parsed = new URL(value);
|
||||||
if (
|
if (
|
||||||
@@ -297,14 +298,20 @@ function canonicalPublicHttpUri(value: string): string | undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isSafeS3Uri(value: string): boolean {
|
function isSafeS3Uri(value: string): boolean {
|
||||||
|
if (value.trim() !== value || /[\u0000-\u001f\u007f\\]/u.test(value)) return false;
|
||||||
try {
|
try {
|
||||||
const parsed = new URL(value);
|
const parsed = new URL(value);
|
||||||
|
const bucket = parsed.hostname;
|
||||||
|
const validBucket = /^(?=.{3,63}$)(?!-)(?!.*\.\.)(?!.*\.-)(?!.*-\.)[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/.test(bucket)
|
||||||
|
&& !/^\d{1,3}(?:\.\d{1,3}){3}$/.test(bucket);
|
||||||
return parsed.protocol === "s3:"
|
return parsed.protocol === "s3:"
|
||||||
&& parsed.hostname.length > 0
|
&& validBucket
|
||||||
|
&& parsed.port === ""
|
||||||
&& parsed.username === ""
|
&& parsed.username === ""
|
||||||
&& parsed.password === ""
|
&& parsed.password === ""
|
||||||
&& parsed.search === ""
|
&& parsed.search === ""
|
||||||
&& parsed.hash === "";
|
&& parsed.hash === ""
|
||||||
|
&& parsed.href === value;
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -380,7 +387,8 @@ const s3EvidenceSourceSchema = z.object({
|
|||||||
message: "custom S3 endpoints must be explicitly trusted",
|
message: "custom S3 endpoints must be explicitly trusted",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (source.endpoint_url.startsWith("http:") && !source.allow_insecure_endpoint) {
|
const endpoint = parsePublicHttpUri(source.endpoint_url);
|
||||||
|
if (endpoint?.protocol === "http:" && !source.allow_insecure_endpoint) {
|
||||||
context.addIssue({
|
context.addIssue({
|
||||||
code: "custom", path: ["allow_insecure_endpoint"],
|
code: "custom", path: ["allow_insecure_endpoint"],
|
||||||
message: "HTTP S3 endpoints require an explicit insecure opt-in",
|
message: "HTTP S3 endpoints require an explicit insecure opt-in",
|
||||||
|
|||||||
@@ -476,6 +476,7 @@ const invalidHttpUris = [
|
|||||||
"https://example.com/manifest?token=CANARY-HTTP",
|
"https://example.com/manifest?token=CANARY-HTTP",
|
||||||
"https://example.com/manifest#CANARY-HTTP",
|
"https://example.com/manifest#CANARY-HTTP",
|
||||||
"ftp://example.com/manifest/CANARY-HTTP",
|
"ftp://example.com/manifest/CANARY-HTTP",
|
||||||
|
"\nhttps://example.com/CANARY-HTTP",
|
||||||
];
|
];
|
||||||
|
|
||||||
test.each(invalidHttpUris)("rejects unsafe HTTP descriptor URI %# without leaking it", (uri) => {
|
test.each(invalidHttpUris)("rejects unsafe HTTP descriptor URI %# without leaking it", (uri) => {
|
||||||
@@ -506,6 +507,8 @@ test.each(invalidHttpBounds)("rejects invalid HTTP bound %s=%s", (field, value)
|
|||||||
const invalidS3Uris = [
|
const invalidS3Uris = [
|
||||||
"https://bucket/prefix", "s3:///prefix", "s3://user:CANARY-S3@bucket/prefix",
|
"https://bucket/prefix", "s3:///prefix", "s3://user:CANARY-S3@bucket/prefix",
|
||||||
"s3://bucket/prefix?token=CANARY-S3", "s3://bucket/prefix#CANARY-S3",
|
"s3://bucket/prefix?token=CANARY-S3", "s3://bucket/prefix#CANARY-S3",
|
||||||
|
"s3://bucket:123/CANARY-S3", "s3://bucket/%2e%2e/CANARY-S3",
|
||||||
|
"s3://127.0.0.1/CANARY-S3", "s3://UPPERCASE/CANARY-S3",
|
||||||
];
|
];
|
||||||
|
|
||||||
test.each(invalidS3Uris)("rejects invalid S3 URI %# without leaking it", (uri) => {
|
test.each(invalidS3Uris)("rejects invalid S3 URI %# without leaking it", (uri) => {
|
||||||
@@ -520,6 +523,7 @@ const invalidS3Endpoints = [
|
|||||||
{ endpoint_url: "https://objects.example#CANARY-S3", trusted_endpoint: true },
|
{ endpoint_url: "https://objects.example#CANARY-S3", trusted_endpoint: true },
|
||||||
{ endpoint_url: "https://objects.example", trusted_endpoint: false },
|
{ endpoint_url: "https://objects.example", trusted_endpoint: false },
|
||||||
{ endpoint_url: "http://objects.example", trusted_endpoint: true, allow_insecure_endpoint: false },
|
{ endpoint_url: "http://objects.example", trusted_endpoint: true, allow_insecure_endpoint: false },
|
||||||
|
{ endpoint_url: "HTTP://objects.example", trusted_endpoint: true, allow_insecure_endpoint: false },
|
||||||
{ trusted_endpoint: true },
|
{ trusted_endpoint: true },
|
||||||
{ allow_private_endpoint: true },
|
{ allow_private_endpoint: true },
|
||||||
{ allow_insecure_endpoint: true },
|
{ allow_insecure_endpoint: true },
|
||||||
|
|||||||
Reference in New Issue
Block a user