Skip to content

Commit

Permalink
Validate meta CSP directives (#107)
Browse files Browse the repository at this point in the history
  • Loading branch information
rviscomi authored Apr 23, 2024
1 parent 459c902 commit c9c6706
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 15 deletions.
6 changes: 3 additions & 3 deletions crx/capo.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion crx/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "Capo: get your ﹤𝚑𝚎𝚊𝚍﹥ in order",
"description": "Visualize the optimal ordering of ﹤𝚑𝚎𝚊𝚍﹥ elements on any web page",
"version": "1.5.0",
"version": "1.5.1",
"permissions": [
"scripting",
"activeTab",
Expand Down
8 changes: 8 additions & 0 deletions docs/src/content/docs/user/validation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ In the example above, there is a `<meta>` CSP element, so capo.js warns that "CS

This validation warning is an example of capo.js being more opinionated than simply following the specification. The warning includes a recommendation to use the CSP header instead, which avoids the preload scanner issue all together. Also note that the `Content-Security-Policy-Report-Only` directive is only valid as an HTTP header and not as a `<meta http-equiv>` element.

Additionally, some CSP directives are not allowed in the `<meta>` declaration:

- `frame-ancestors`
- `sandbox`
- `report-uri`

If capo.js detects any of these directives in a `<meta>` CSP element, it will log a validation warning.

### No invalid origin trials

Sites can register for [origin trials](https://developer.chrome.com/en/docs/web-platform/origin-trials/) to enable individual experimental web platform features. To enable them on a given site, a token must be included as either an `Origin-Trial` HTTP header or `<meta http-equiv>` element.
Expand Down
33 changes: 29 additions & 4 deletions docs/src/lib/capo.js
Original file line number Diff line number Diff line change
Expand Up @@ -540,11 +540,36 @@ function $c322f9a5057eaf5c$export$6c93e2175c028eeb(element) {
}
function $c322f9a5057eaf5c$var$validateCSP(element) {
const warnings = [];
if (element.matches('meta[http-equiv="Content-Security-Policy-Report-Only" i]')) //https://w3c.github.io/webappsec-csp/#meta-element
warnings.push("CSP Report-Only is forbidden in meta tags");
else if (element.matches('meta[http-equiv="Content-Security-Policy" i]')) warnings.push("meta CSP discouraged. See https://crbug.com/1458493.");
let payload = null;
if (element.matches('meta[http-equiv="Content-Security-Policy-Report-Only" i]')) {
//https://w3c.github.io/webappsec-csp/#meta-element
warnings.push("CSP Report-Only is forbidden in meta tags");
return warnings;
}
if (element.matches('meta[http-equiv="Content-Security-Policy" i]')) warnings.push("meta CSP discouraged. See https://crbug.com/1458493.");
const content = element.getAttribute("content");
if (!content) {
warnings.push("Invalid CSP. The content attribute must be set.");
return {
warnings: warnings,
payload: payload
};
}
const directives = Object.fromEntries(content.split(/\s*;\s*/).map((directive)=>{
const [key, ...value] = directive.split(" ");
return [
key,
value.join(" ")
];
}));
payload = payload ?? {};
payload.directives = directives;
if ("report-uri" in directives) warnings.push("The report-uri directive is not supported. Use the Content-Security-Policy-Report-Only HTTP header instead.");
if ("frame-ancestors" in directives) warnings.push("The frame-ancestors directive is not supported. Use the Content-Security-Policy HTTP header instead.");
if ("sandbox" in directives) warnings.push("The sandbox directive is not supported. Use the Content-Security-Policy HTTP header instead.");
return {
warnings: warnings
warnings: warnings,
payload: payload
};
}
function $c322f9a5057eaf5c$var$isInvalidOriginTrial(element) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rviscomi/capo.js",
"version": "1.5.0",
"version": "1.5.1",
"description": "Get your ﹤𝚑𝚎𝚊𝚍﹥ in order",
"author": "Rick Viscomi",
"license": "Apache-2.0",
Expand Down
33 changes: 29 additions & 4 deletions snippet/capo.js
Original file line number Diff line number Diff line change
Expand Up @@ -541,11 +541,36 @@ function $580f7ed6bc170ae8$export$6c93e2175c028eeb(element) {
}
function $580f7ed6bc170ae8$var$validateCSP(element) {
const warnings = [];
if (element.matches('meta[http-equiv="Content-Security-Policy-Report-Only" i]')) //https://w3c.github.io/webappsec-csp/#meta-element
warnings.push("CSP Report-Only is forbidden in meta tags");
else if (element.matches('meta[http-equiv="Content-Security-Policy" i]')) warnings.push("meta CSP discouraged. See https://crbug.com/1458493.");
let payload = null;
if (element.matches('meta[http-equiv="Content-Security-Policy-Report-Only" i]')) {
//https://w3c.github.io/webappsec-csp/#meta-element
warnings.push("CSP Report-Only is forbidden in meta tags");
return warnings;
}
if (element.matches('meta[http-equiv="Content-Security-Policy" i]')) warnings.push("meta CSP discouraged. See https://crbug.com/1458493.");
const content = element.getAttribute("content");
if (!content) {
warnings.push("Invalid CSP. The content attribute must be set.");
return {
warnings: warnings,
payload: payload
};
}
const directives = Object.fromEntries(content.split(/\s*;\s*/).map((directive)=>{
const [key, ...value] = directive.split(" ");
return [
key,
value.join(" ")
];
}));
payload = payload ?? {};
payload.directives = directives;
if ("report-uri" in directives) warnings.push("The report-uri directive is not supported. Use the Content-Security-Policy-Report-Only HTTP header instead.");
if ("frame-ancestors" in directives) warnings.push("The frame-ancestors directive is not supported. Use the Content-Security-Policy HTTP header instead.");
if ("sandbox" in directives) warnings.push("The sandbox directive is not supported. Use the Content-Security-Policy HTTP header instead.");
return {
warnings: warnings
warnings: warnings,
payload: payload
};
}
function $580f7ed6bc170ae8$var$isInvalidOriginTrial(element) {
Expand Down
36 changes: 34 additions & 2 deletions src/lib/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,18 +185,50 @@ export function getCustomValidations(element) {

function validateCSP(element) {
const warnings = [];
let payload = null;

if (element.matches('meta[http-equiv="Content-Security-Policy-Report-Only" i]')) {
//https://w3c.github.io/webappsec-csp/#meta-element
warnings.push("CSP Report-Only is forbidden in meta tags");
} else if (element.matches('meta[http-equiv="Content-Security-Policy" i]')) {
return warnings;
}

if (element.matches('meta[http-equiv="Content-Security-Policy" i]')) {
warnings.push("meta CSP discouraged. See https://crbug.com/1458493.");
}

const content = element.getAttribute("content");
if (!content) {
warnings.push("Invalid CSP. The content attribute must be set.");
return { warnings, payload };
}

const directives = Object.fromEntries(
content.split(/\s*;\s*/).map((directive) => {
const [key, ...value] = directive.split(" ");
return [key, value.join(" ")];
})
);
payload = payload ?? {};
payload.directives = directives;

// TODO: Validate that CSP doesn't include `report-uri`, `frame-ancestors`, or `sandbox` directives.
if ("report-uri" in directives) {
warnings.push(
"The report-uri directive is not supported. Use the Content-Security-Policy-Report-Only HTTP header instead."
);
}
if ("frame-ancestors" in directives) {
warnings.push(
"The frame-ancestors directive is not supported. Use the Content-Security-Policy HTTP header instead."
);
}
if ("sandbox" in directives) {
warnings.push("The sandbox directive is not supported. Use the Content-Security-Policy HTTP header instead.");
}

return {
warnings,
payload,
};
}

Expand Down

0 comments on commit c9c6706

Please sign in to comment.