Skip to content

Commit

Permalink
Revert csp via headers (google#162)
Browse files Browse the repository at this point in the history
* Revert "Use CSP header instead of meta tag (google#115)"

This reverts commit c32358e.

* Fix test

* Make CSP optional
  • Loading branch information
cramforce authored Jan 7, 2023
1 parent ce3fc01 commit 20c7382
Show file tree
Hide file tree
Showing 7 changed files with 28 additions and 108 deletions.
19 changes: 1 addition & 18 deletions .eleventy.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ const markdownItAnchor = require("markdown-it-anchor");
const localImages = require("./third_party/eleventy-plugin-local-images/.eleventy.js");
const CleanCSS = require("clean-css");
const GA_ID = require("./_data/metadata.json").googleAnalyticsId;
const { cspDevMiddleware } = require("./_11ty/apply-csp.js");

module.exports = function (eleventyConfig) {
eleventyConfig.addPlugin(pluginRss);
Expand Down Expand Up @@ -180,6 +179,7 @@ module.exports = function (eleventyConfig) {
// We need to copy cached.js only if GA is used
eleventyConfig.addPassthroughCopy(GA_ID ? "js" : "js/*[!cached].*");
eleventyConfig.addPassthroughCopy("fonts");
eleventyConfig.addPassthroughCopy("_headers");

// We need to rebuild upon JS change to update the CSP.
eleventyConfig.addWatchTarget("./js/");
Expand All @@ -203,7 +203,6 @@ module.exports = function (eleventyConfig) {

// Browsersync Overrides
eleventyConfig.setBrowserSyncConfig({
middleware: cspDevMiddleware,
callbacks: {
ready: function (err, browserSync) {
const content_404 = fs.readFileSync("_site/404.html");
Expand All @@ -219,22 +218,6 @@ module.exports = function (eleventyConfig) {
ghostMode: false,
});

// Run me before the build starts
eleventyConfig.on("beforeBuild", () => {
// Copy _header to dist
// Don't use addPassthroughCopy to prevent apply-csp from running before the _header file has been copied
try {
const headers = fs.readFileSync("./_headers", { encoding: "utf-8" });
fs.mkdirSync("./_site", { recursive: true });
fs.writeFileSync("_site/_headers", headers);
} catch (error) {
console.log(
"[beforeBuild] Something went wrong with the _headers file\n",
error
);
}
});

// After the build touch any file in the test directory to do a test run.
eleventyConfig.on("afterBuild", async () => {
const files = await readdir("test");
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ npm run build

### Security

Generates a strong [Content-Security-Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) using HTTP headers.
Generates a strong [Content-Security-Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) for the base template.

- Default-src is self.
- Disallows plugins.
Expand Down
84 changes: 11 additions & 73 deletions _11ty/apply-csp.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
const { JSDOM } = require("jsdom");
const cspHashGen = require("csp-hash-generator");
const syncPackage = require("browser-sync/package.json");
const fs = require("fs");
const CSP = require("../_data/csp");

/**
* Substitute the magic `HASHES` string in the CSP with the actual values of the
Expand Down Expand Up @@ -65,51 +63,18 @@ const addCspHash = async (rawContent, outputPath) => {
hashes.push.apply(hashes, AUTO_RELOAD_SCRIPTS);
}

content = dom.serialize();

// write CSP Policy in headers file
const headersPath = "./_site/_headers";
const filePath = outputPath.replace("_site/", "/"); // _site/blog/index.html -> /blog/index.html
const filePathPrettyURL = filePath.slice(0, -10); // blog/index.html -> /blog/
try {
const headers = fs.readFileSync(headersPath, { encoding: "utf-8" });
const regExp = /(# \[csp headers\][\r\n]+)([\s\S]*)(# \[end csp headers\])/;
const match = headers.match(regExp);
if (!match) {
throw `Check your _headers file. I couldn't find the text block for the csp headers:
# [csp headers]
# this text will be replaced by apply-csp.js plugin
# [end csp headers]`;
}
const oldCustomHeaders = headers.match(regExp)[2].toString();
const CSPPolicy = `Content-Security-Policy: ${CSP.apply().regular.replace(
"HASHES",
hashes.join(" ")
)}`;
let newCustomHeaders = oldCustomHeaders.concat(
"\n",
filePath,
"\n ",
CSPPolicy
);
if (filePath != filePathPrettyURL) {
newCustomHeaders = newCustomHeaders.concat(
"\n",
filePathPrettyURL,
"\n ",
CSPPolicy
);
}
fs.writeFileSync(
headersPath,
headers.replace(regExp, `$1${newCustomHeaders}\n$3`)
);
} catch (error) {
console.log(
"[apply-csp] Something went wrong with the creation of the csp headers\n",
error
);
const csp = dom.window.document.querySelector(
"meta[http-equiv='Content-Security-Policy']"
);
if (!csp) {
return content;
}
csp.setAttribute(
"content",
csp.getAttribute("content").replace("HASHES", hashes.join(" "))
);

content = dom.serialize();
}

return content;
Expand Down Expand Up @@ -137,33 +102,6 @@ module.exports = {
configFunction: async (eleventyConfig, pluginOptions = {}) => {
eleventyConfig.addTransform("csp", addCspHash);
},
parseHeaders: parseHeaders,
cspDevMiddleware: function (req, res, next) {
const url = new URL(req.originalUrl, `http://${req.headers.host}/)`);
// add csp headers only for html pages (include pretty urls)
if (url.pathname.endsWith("/") || url.pathname.endsWith(".html")) {
let headers;
try {
headers = parseHeaders(
fs.readFileSync("_site/_headers", {
encoding: "utf-8",
})
)[url.pathname];
} catch (error) {
console.error(
"[setBrowserSyncConfig] Something went wrong with the creation of the csp headers\n",
error
);
}
if (headers) {
const csp = headers["Content-Security-Policy"];
if (csp) {
res.setHeader("Content-Security-Policy", csp);
}
}
}
next();
},
};

function isDevelopmentMode() {
Expand Down
2 changes: 1 addition & 1 deletion _data/csp.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const CSP = {
["object-src", quote("none")],
// Script from same-origin and inline-hashes.
// If you need to add an external host for scripts you need to add an item like 'https://code.jquery.com/jquery-3.6.0.slim.min.js' to this list.
["script-src", SELF, /* Replaced by apply-csp.js plugin */ "HASHES"],
["script-src", SELF, /* Replaced by csp.js plugin */ "HASHES"],
// Inline CSS is allowed.
["style-src", quote("unsafe-inline")],
// Images may also come from data-URIs.
Expand Down
4 changes: 0 additions & 4 deletions _headers
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,3 @@
cache-control: public,max-age=31536000,immutable
/favicon.svg
cache-control: public,max-age=3600

# [csp headers]
# this text will be replaced by apply-csp.js plugin
# [end csp headers]
4 changes: 4 additions & 0 deletions _includes/layouts/base.njk
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- See _data/csp.js for how to add to the CSP.
Update me: Remove comment if you want a CSP.
<meta http-equiv="Content-Security-Policy" content="{{ csp.regular | safe }}">
-->
{% if isdevelopment %}
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
{% else %}
Expand Down
21 changes: 10 additions & 11 deletions test/test-generic-post.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const readFileSync = require("fs").readFileSync;
const existsSync = require("fs").existsSync;
const metadata = require("../_data/metadata.json");
const GA_ID = require("../_data/googleanalytics.js")();
const { parseHeaders } = require("../_11ty/apply-csp");

/**
* These tests kind of suck and they are kind of useful.
Expand All @@ -16,10 +15,9 @@ const { parseHeaders } = require("../_11ty/apply-csp");

describe("check build output for a generic post", () => {
describe("sample post", () => {
const POST_PATH = "/posts/firstpost/";
const POST_FILENAME = `_site${POST_PATH}index.html`;
const POST_FILENAME = "_site/posts/firstpost/index.html";
const URL = metadata.url;
const POST_URL = URL + POST_PATH;
const POST_URL = URL + "/posts/firstpost/";

if (!existsSync(POST_FILENAME)) {
it("WARNING skipping tests because POST_FILENAME does not exist", () => {});
Expand Down Expand Up @@ -92,15 +90,16 @@ describe("check build output for a generic post", () => {
expect(count).to.equal(1);
});

/*
// Update me. Comment in if you turned on the CSP support.
it("should have a good CSP", () => {
assert(existsSync("./_site/_headers"), "_header exists");
const headers = parseHeaders(
readFileSync("./_site/_headers", { encoding: "utf-8" })
const csp = select(
"meta[http-equiv='Content-Security-Policy']",
"content"
);
POST_PATH;
expect(headers).to.have.key(POST_PATH);
expect(headers).to.have.key(`${POST_PATH}index.html`);
});
expect(csp).to.contain(";object-src 'none';");
expect(csp).to.match(/^default-src 'self';/);
});*/

it("should have accessible buttons", () => {
const buttons = doc.querySelectorAll("button");
Expand Down

0 comments on commit 20c7382

Please sign in to comment.