Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions src/analyzer/tests/subresource-integrity.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ export function subresourceIntegrityTest(
}
// Track to see if any scripts were on foreign TLDs.
let scriptsOnForeignOrigin = false;

// Protocol-relative URLs (//cdn.example.com/…) inherit the page's scheme.
// They are only safe when the site ensures HTTP is never served: either
// there is no HTTP server at all, or HTTP always redirects to HTTPS.
const httpRedirects = requests.responses.httpRedirects;
const httpEnforcesHttps =
!requests.responses.http ||
(httpRedirects.length > 1 &&
httpRedirects.at(-1)?.url.protocol === "https:");

for (const script of scripts) {
const scriptSrc = getAttribute(script, "src");
if (scriptSrc) {
Expand All @@ -91,7 +101,9 @@ export function subresourceIntegrityTest(
if (relativeProtocolRegex.test(scriptSrc)) {
// relative protocol(src="//example.com/script.js")
relativeProtocol = true;
sameSecondLevelDomain = true;
sameSecondLevelDomain =
parse("https:" + scriptSrc).domain ===
parse(requests.site.hostname).domain;
} else if (fullUrlRegex.test(scriptSrc)) {
// full URL (src="https://example.com/script.js")
sameSecondLevelDomain =
Expand Down Expand Up @@ -119,7 +131,8 @@ export function subresourceIntegrityTest(
let secureScheme = false;
if (
scheme === "https:" ||
(relativeOrigin && requests.session?.url.protocol === "https:")
(relativeOrigin && requests.session?.url.protocol === "https:") ||
(relativeProtocol && httpEnforcesHttps)
) {
secureScheme = true;
}
Expand Down
45 changes: 44 additions & 1 deletion test/subresource-integrity.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,22 @@ describe("Subresource Integrity", () => {
);
assert.isTrue(result.pass);

// On the same second-level domain, but without a protocol
// On the same second-level domain, but without a protocol — when HTTP
// redirects to HTTPS the protocol-relative URL is safe, so only -5 (issue #464).
reqs = emptyRequests("test_content_sri_sameorigin3.html");
result = subresourceIntegrityTest(reqs);
assert.equal(
result.result,
Expectation.SriNotImplementedButExternalScriptsLoadedSecurely
);
assert.isFalse(result.pass);

// Without HTTP→HTTPS enforcement the protocol-relative URL is still penalised at -50.
reqs = emptyRequests("test_content_sri_sameorigin3.html");
reqs.responses.httpRedirects = [
{ url: new URL("http://mozilla.org/"), status: 200 },
];
result = subresourceIntegrityTest(reqs);
assert.equal(
result.result,
Expectation.SriNotImplementedAndExternalScriptsNotLoadedSecurely
Expand Down Expand Up @@ -115,8 +128,23 @@ describe("Subresource Integrity", () => {
});

it("checks if implemented with external scripts and no protocol", function () {
// When HTTP redirects to HTTPS, //cdn.example.com/script.js always resolves to https://,
// so protocol-relative URLs should be treated the same as https:// (issue #464).
reqs = emptyRequests("test_content_sri_impl_external_noproto.html");
let result = subresourceIntegrityTest(reqs);
assert.equal(
result.result,
Expectation.SriImplementedAndExternalScriptsLoadedSecurely
);
assert.isTrue(result.pass);

// When HTTP does NOT redirect to HTTPS, //cdn.example.com/script.js can resolve to http://
// on an HTTP visit, so it must still be penalised.
reqs = emptyRequests("test_content_sri_impl_external_noproto.html");
reqs.responses.httpRedirects = [
{ url: new URL("http://mozilla.org/"), status: 200 },
];
result = subresourceIntegrityTest(reqs);
assert.equal(
result.result,
Expectation.SriImplementedButExternalScriptsNotLoadedSecurely
Expand All @@ -135,8 +163,23 @@ describe("Subresource Integrity", () => {
});

it("checks if not implemented with external scripts and no protocol", function () {
// When HTTP redirects to HTTPS, //cdn.example.com/script.js always resolves to https://,
// so it should score like https:// (-5), not like http:// (-50) (issue #464).
reqs = emptyRequests("test_content_sri_notimpl_external_noproto.html");
let result = subresourceIntegrityTest(reqs);
assert.equal(
result.result,
Expectation.SriNotImplementedButExternalScriptsLoadedSecurely
);
assert.isFalse(result.pass);

// When HTTP does NOT redirect to HTTPS, //cdn.example.com/script.js can resolve to http://
// on an HTTP visit, so it must still be penalised at -50.
reqs = emptyRequests("test_content_sri_notimpl_external_noproto.html");
reqs.responses.httpRedirects = [
{ url: new URL("http://mozilla.org/"), status: 200 },
];
result = subresourceIntegrityTest(reqs);
assert.equal(
result.result,
Expectation.SriNotImplementedAndExternalScriptsNotLoadedSecurely
Expand Down
Loading