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
72 changes: 72 additions & 0 deletions src/analyzer/tests/cross-origin-embedder-policy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { CROSS_ORIGIN_EMBEDDER_POLICY } from "../../headers.js";
import { BaseOutput, Requests } from "../../types.js";
import { Expectation } from "../../types.js";
import { getFirstHttpHeader } from "../utils.js";

export class CrossOriginEmbedderPolicyOutput extends BaseOutput {
/** @type {string | null} */
data = null;
http = false;
static name = "cross-origin-embedder-policy";
static title = "Cross Origin Embedder Policy";
static possibleResults = [
Expectation.CoepNotImplemented,
Expectation.CoepImplementedWithRequireCorp,
Expectation.CoepImplementedWithCredentialless,
Expectation.CoepImplementedWithUnsafeNone,
Expectation.CoepHeaderInvalid,
];

/**
* @param {Expectation} expectation
*/
constructor(expectation) {
super(expectation);
}
}

/**
* @param {Requests} requests
* @param {Expectation} expectation
* @returns {CrossOriginEmbedderPolicyOutput}
*/
export function crossOriginEmbedderPolicyTest(
requests,
expectation = Expectation.CoepNotImplemented
) {
const output = new CrossOriginEmbedderPolicyOutput(expectation);
output.result = Expectation.CoepNotImplemented;

const resp = requests.responses.auto;
if (!resp) {
return output;
}

const httpHeader = getFirstHttpHeader(resp, CROSS_ORIGIN_EMBEDDER_POLICY);
output.http = !!httpHeader;

if (httpHeader) {
const headerValue = httpHeader.slice(0, 256).trim().toLowerCase();
output.data = headerValue;

if (headerValue === "require-corp") {
output.result = Expectation.CoepImplementedWithRequireCorp;
} else if (headerValue === "credentialless") {
output.result = Expectation.CoepImplementedWithCredentialless;
} else if (headerValue === "unsafe-none") {
output.result = Expectation.CoepImplementedWithUnsafeNone;
} else {
output.result = Expectation.CoepHeaderInvalid;
}
}

output.pass = [
expectation,
Expectation.CoepNotImplemented,
Expectation.CoepImplementedWithRequireCorp,
Expectation.CoepImplementedWithCredentialless,
Expectation.CoepImplementedWithUnsafeNone,
].includes(output.result ?? "");

return output;
}
76 changes: 76 additions & 0 deletions src/analyzer/tests/cross-origin-opener-policy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { CROSS_ORIGIN_OPENER_POLICY } from "../../headers.js";
import { BaseOutput, Requests } from "../../types.js";
import { Expectation } from "../../types.js";
import { getFirstHttpHeader } from "../utils.js";

export class CrossOriginOpenerPolicyOutput extends BaseOutput {
/** @type {string | null} */
data = null;
http = false;
static name = "cross-origin-opener-policy";
static title = "Cross Origin Opener Policy";
static possibleResults = [
Expectation.CoopNotImplemented,
Expectation.CoopImplementedWithSameOrigin,
Expectation.CoopImplementedWithSameOriginAllowPopups,
Expectation.CoopImplementedWithNoopenerAllowPopups,
Expectation.CoopImplementedWithUnsafeNone,
Expectation.CoopHeaderInvalid,
];

/**
* @param {Expectation} expectation
*/
constructor(expectation) {
super(expectation);
}
}

/**
* @param {Requests} requests
* @param {Expectation} expectation
* @returns {CrossOriginOpenerPolicyOutput}
*/
export function crossOriginOpenerPolicyTest(
requests,
expectation = Expectation.CoopNotImplemented
) {
const output = new CrossOriginOpenerPolicyOutput(expectation);
output.result = Expectation.CoopNotImplemented;

const resp = requests.responses.auto;
if (!resp) {
return output;
}

const httpHeader = getFirstHttpHeader(resp, CROSS_ORIGIN_OPENER_POLICY);
output.http = !!httpHeader;

if (httpHeader) {
const headerValue = httpHeader.slice(0, 256).trim().toLowerCase();
output.data = headerValue;

if (headerValue === "same-origin") {
output.result = Expectation.CoopImplementedWithSameOrigin;
} else if (headerValue === "same-origin-allow-popups") {
output.result = Expectation.CoopImplementedWithSameOriginAllowPopups;
} else if (headerValue === "noopener-allow-popups") {
output.result = Expectation.CoopImplementedWithNoopenerAllowPopups;
} else if (headerValue === "unsafe-none") {
output.result = Expectation.CoopImplementedWithUnsafeNone;
} else {
output.result = Expectation.CoopHeaderInvalid;
}
}

output.pass = [
expectation,
Expectation.CoopNotImplemented,
Expectation.CoopImplementedWithSameOrigin,
Expectation.CoopImplementedWithSameOriginAllowPopups,
Expectation.CoopImplementedWithNoopenerAllowPopups,
Expectation.CoopImplementedWithUnsafeNone,
].includes(output.result ?? "");

return output;
}
14 changes: 13 additions & 1 deletion src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ import {
CorsOutput,
crossOriginResourceSharingTest,
} from "./analyzer/tests/cors.js";
import {
CrossOriginEmbedderPolicyOutput,
crossOriginEmbedderPolicyTest,
} from "./analyzer/tests/cross-origin-embedder-policy.js";
import {
CrossOriginOpenerPolicyOutput,
crossOriginOpenerPolicyTest,
} from "./analyzer/tests/cross-origin-opener-policy.js";
import {
CrossOriginResourcePolicyOutput,
crossOriginResourcePolicyTest,
Expand Down Expand Up @@ -36,6 +44,8 @@ import {
export const ALL_TESTS = [
contentSecurityPolicyTest,
cookiesTest,
crossOriginEmbedderPolicyTest,
crossOriginOpenerPolicyTest,
crossOriginResourceSharingTest,
redirectionTest,
referrerPolicyTest,
Expand All @@ -49,6 +59,8 @@ export const ALL_TESTS = [
export const ALL_RESULTS = [
CspOutput,
CookiesOutput,
CrossOriginEmbedderPolicyOutput,
CrossOriginOpenerPolicyOutput,
CorsOutput,
RedirectionOutput,
ReferrerOutput,
Expand All @@ -61,4 +73,4 @@ export const ALL_RESULTS = [

export const NUM_TESTS = ALL_TESTS.length;

export const ALGORITHM_VERSION = 5;
export const ALGORITHM_VERSION = 6;
138 changes: 138 additions & 0 deletions src/grader/charts.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export const GRADES = new Set(GRADE_CHART.values());
/** @type {import("../types.js").StringMap} */
export const TEST_TITLES = {
"cross-origin-resource-sharing": "Cross Origin Resource Sharing (CORS)",
"cross-origin-embedder-policy": "Cross Origin Embedder Policy",
"cross-origin-opener-policy": "Cross Origin Opener Policy",
"cross-origin-resource-policy": "Cross Origin Resource Policy",
"content-security-policy": "Content Security Policy (CSP)",
redirection: "Redirection",
Expand All @@ -57,6 +59,14 @@ export const TEST_TOPIC_LINKS = new Map([
"cookies",
"/en-US/docs/Web/Security/Practical_implementation_guides/Cookies",
],
[
"cross-origin-embedder-policy",
"/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Embedder-Policy",
],
[
"cross-origin-opener-policy",
"/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Opener-Policy",
],
[
"cross-origin-resource-policy",
"/en-US/docs/Web/Security/Practical_implementation_guides/CORP",
Expand Down Expand Up @@ -849,6 +859,134 @@ export const SCORE_TABLE = new Map([
},
],

// Cross Origin Embedder Policy

[
Expectation.CoepImplementedWithRequireCorp,
{
description: `<p>
<code>Cross-Origin-Embedder-Policy</code> header set to <code>require-corp</code>, restricting loading cross-origin resources.
</p>`,
modifier: 10,
recommendation: ``,
},
],
[
Expectation.CoepImplementedWithCredentialless,
{
description: `<p>
<code>Cross-Origin-Embedder-Policy</code> header set to <code>credentialless</code>, allowing loading cross-origin resources but without sending credentials in the requests.
</p>`,
modifier: 10,
recommendation: ``,
},
],
[
Expectation.CoepImplementedWithUnsafeNone,
{
description: `<p>
<code>Cross-Origin-Embedder-Policy</code> header set to <code>unsafe-none</code>, allowing loading cross-origin resources, which is the default value.
</p>`,
modifier: 0,
recommendation: `<p>
Set to <code>require-corp</code> or <code>credentialless</code>.
</p>`,
},
],
[
Expectation.CoepNotImplemented,
{
description: `<p>
<code>Cross-Origin-Embedder-Policy</code> header not implemented.
</p>`,
modifier: 0,
recommendation: `<p>
Set to <code>require-corp</code> or <code>credentialless</code>.
</p>`,
},
],
[
Expectation.CoepHeaderInvalid,
{
description: `<p>
<code>Cross-Origin-Embedder-Policy</code> header cannot be recognized.
</p>`,
modifier: -5,
recommendation: `<p>
Set to <code>require-corp</code>, <code>credentialless</code>, or <code>unsafe-none</code>.
</p>`,
},
],

// Cross Origin Opener Policy

[
Expectation.CoopImplementedWithSameOrigin,
{
description: `<p>
<code>Cross-Origin-Opener-Policy</code> header set to <code>same-origin</code>, enforcing site isolation from the opener.
</p>`,
modifier: 10,
recommendation: ``,
},
],
[
Expectation.CoopImplementedWithSameOriginAllowPopups,
{
description: `<p>
<code>Cross-Origin-Opener-Policy</code> header set to <code>same-origin-allow-popups</code>, enforcing site isolation from the opener but allowing trusted resources such as for OAuth or payments.
</p>`,
modifier: 10,
recommendation: ``,
},
],
[
Expectation.CoopImplementedWithNoopenerAllowPopups,
{
description: `<p>
<code>Cross-Origin-Opener-Policy</code> header set to <code>noopener-allow-popups</code>, enforcing site isolation from the opener even if they are same origin.
</p>`,
modifier: 10,
recommendation: ``,
},
],
[
Expectation.CoopImplementedWithUnsafeNone,
{
description: `<p>
<code>Cross-Origin-Opener-Policy</code> header set to <code>unsafe-none</code>, the document permits sharing its browsing context group with any other document, and may therefore be unsafe.
</p>`,
modifier: 0,
recommendation: `<p>
Set to <code>same-origin</code> or <code>same-origin-allow-popups</code>.
</p>`,
},
],
[
Expectation.CoopNotImplemented,
{
description: `<p>
<code>Cross-Origin-Opener-Policy</code> header not implemented.
</p>`,
modifier: 0,
recommendation: `<p>
Set to <code>same-origin</code> or <code>same-origin-allow-popups</code>.
</p>`,
},
],
[
Expectation.CoopHeaderInvalid,
{
description: `<p>
<code>Cross-Origin-Opener-Policy</code> header cannot be recognized.
</p>`,
modifier: -5,
recommendation: `<p>
Set to <code>same-origin</code>, <code>same-origin-allow-popups</code>, <code>noopener-allow-popups</code>, or <code>unsafe-none</code>.
</p>`,
},
],

// GENERIC
[
Expectation.HtmlNotParseable,
Expand Down
2 changes: 2 additions & 0 deletions src/headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export const ORIGIN = "origin";
export const ACCESS_CONTROL_ALLOW_CREDENTIALS =
"access-control-allow-credentials";
export const CROSS_ORIGIN_RESOURCE_POLICY = "cross-origin-resource-policy";
export const CROSS_ORIGIN_OPENER_POLICY = "cross-origin-opener-policy";
export const CROSS_ORIGIN_EMBEDDER_POLICY = "cross-origin-embedder-policy";
export const STRICT_TRANSPORT_SECURITY = "strict-transport-security";
export const CONTENT_TYPE = "content-type";
export const X_CONTENT_TYPE_OPTIONS = "x-content-type-options";
Expand Down
21 changes: 21 additions & 0 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,25 @@ export const Expectation = {
CrossOriginResourcePolicyImplementedWithCrossOrigin:
"corp-implemented-with-cross-origin",
CrossOriginResourcePolicyHeaderInvalid: "corp-header-invalid",

// CROSS ORIGIN OPENER POLICY

CoopNotImplemented: "coop-not-implemented",
CoopImplementedWithSameOrigin: "coop-implemented-with-same-origin",
CoopImplementedWithSameOriginAllowPopups:
"coop-implemented-with-same-origin-allow-popups",
CoopImplementedWithNoopenerAllowPopups:
"coop-implemented-with-noopener-allow-popups",
CoopImplementedWithUnsafeNone: "coop-implemented-with-unsafe-none",
CoopHeaderInvalid: "coop-header-invalid",

// CROSS ORIGIN EMBEDDER POLICY

CoepNotImplemented: "coep-not-implemented",
CoepImplementedWithRequireCorp: "coep-implemented-with-require-corp",
CoepImplementedWithCredentialless: "coep-implemented-with-credentialless",
CoepImplementedWithUnsafeNone: "coep-implemented-with-unsafe-none",
CoepHeaderInvalid: "coep-header-invalid",
};

/**
Expand Down Expand Up @@ -265,6 +284,8 @@ export class Policy {
* @typedef {import("./analyzer/tests/cookies.js").CookiesOutput
* | import("./analyzer/tests/cors.js").CorsOutput
* | import("./analyzer/tests/csp.js").CspOutput
* | import("./analyzer/tests/cross-origin-embedder-policy.js").CrossOriginEmbedderPolicyOutput
* | import("./analyzer/tests/cross-origin-opener-policy.js").CrossOriginOpenerPolicyOutput
* | import("./analyzer/tests/redirection.js").RedirectionOutput
* | import("./analyzer/tests/referrer-policy.js").ReferrerOutput
* | import("./analyzer/tests/strict-transport-security.js").StrictTransportSecurityOutput
Expand Down
Loading
Loading