@netlify/plugin-csp-nonce 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/index.js +7 -1
- package/manifest.yml +3 -0
- package/package.json +1 -1
- package/src/__csp-nonce.ts +26 -13
package/README.md
CHANGED
|
@@ -7,5 +7,6 @@ This package deploys an edge function to add a header and transform the HTML res
|
|
|
7
7
|
## Configuration options
|
|
8
8
|
|
|
9
9
|
- `reportOnly`: When true, uses the Content-Security-Policy-Report-Only header instead of the Content-Security-Policy header.
|
|
10
|
+
- `unsafeEval`: When true, adds 'unsafe-eval' to CSP for easier adoption. Set to false to have a safer policy if your code and code dependencies does not use eval().
|
|
10
11
|
- `path`: The glob expressions of path(s) that should invoke the CSP nonce edge function. Can be a string or array of strings.
|
|
11
12
|
- `excludedPath`: The glob expressions of path(s) that _should not_ invoke the CSP nonce edge function. Must be an array of strings. This value gets spread with common non-html filetype extensions (_.css, _.js, \*.svg, etc)
|
package/index.js
CHANGED
|
@@ -4,9 +4,15 @@ import fs, { copyFileSync } from "fs";
|
|
|
4
4
|
const SITE_ID = "321a7119-6008-49a8-9d2f-e20602b1b349";
|
|
5
5
|
|
|
6
6
|
export const onPreBuild = async ({ inputs, netlifyConfig, utils }) => {
|
|
7
|
-
console.log(` Current working directory: ${process.cwd()}`);
|
|
8
7
|
const config = JSON.stringify(inputs, null, 2);
|
|
9
8
|
const { build } = netlifyConfig;
|
|
9
|
+
|
|
10
|
+
if (build.environment.DISABLE_CSP_NONCE === "true") {
|
|
11
|
+
console.log(` DISABLE_CSP_NONCE environment variable is true, skipping.`);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
console.log(` Current working directory: ${process.cwd()}`);
|
|
10
16
|
const basePath =
|
|
11
17
|
build.environment.SITE_ID === SITE_ID
|
|
12
18
|
? "./src"
|
package/manifest.yml
CHANGED
|
@@ -3,6 +3,9 @@ inputs:
|
|
|
3
3
|
- name: reportOnly
|
|
4
4
|
description: When true, uses the Content-Security-Policy-Report-Only header instead of the Content-Security-Policy header.
|
|
5
5
|
default: true
|
|
6
|
+
- name: unsafeEval
|
|
7
|
+
description: When true, adds 'unsafe-eval' to CSP for easier adoption. Set to false to have a safer policy if your code and code dependencies does not use eval().
|
|
8
|
+
default: true
|
|
6
9
|
- name: path
|
|
7
10
|
description: The glob expressions of path(s) that should invoke the CSP nonce edge function. Can be a string or array of strings.
|
|
8
11
|
default: "/*"
|
package/package.json
CHANGED
package/src/__csp-nonce.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/* eslint-disable */
|
|
2
2
|
// @ts-expect-error
|
|
3
|
-
import { cryptoRandomString } from "https://deno.land/x/crypto_random_string@1.0.0/mod.ts";
|
|
4
|
-
// @ts-expect-error
|
|
5
3
|
import type { Config, Context } from "netlify:edge";
|
|
4
|
+
// @ts-expect-error
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
6
|
|
|
7
7
|
import inputs from "./__csp-nonce-inputs.json" assert { type: "json" };
|
|
8
8
|
|
|
9
9
|
type Params = {
|
|
10
10
|
reportOnly: boolean;
|
|
11
|
+
unsafeEval: boolean;
|
|
11
12
|
path: string | string[];
|
|
12
13
|
excludedPath: string[];
|
|
13
14
|
};
|
|
@@ -23,23 +24,34 @@ const handler = async (request: Request, context: Context) => {
|
|
|
23
24
|
// for debugging which routes use this edge function
|
|
24
25
|
response.headers.set("x-debug-csp-nonce", "invoked");
|
|
25
26
|
|
|
26
|
-
// html only
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
// html/curl GETs only
|
|
28
|
+
const isGET = request.method?.toUpperCase() === "GET";
|
|
29
|
+
const isCurl = request.headers.get("user-agent")?.startsWith("curl/");
|
|
30
|
+
const isHTMLRequest =
|
|
31
|
+
request.headers.get("accept")?.startsWith("text/html") || isCurl;
|
|
32
|
+
const isHTMLResponse = response.headers
|
|
33
|
+
.get("content-type")
|
|
34
|
+
.startsWith("text/html");
|
|
35
|
+
const shouldTransformResponse = isGET && isHTMLRequest && isHTMLResponse;
|
|
36
|
+
if (!shouldTransformResponse) {
|
|
33
37
|
return response;
|
|
34
38
|
}
|
|
35
39
|
|
|
36
|
-
const nonce =
|
|
40
|
+
const nonce = randomBytes(24).toString("base64");
|
|
37
41
|
// `'strict-dynamic'` allows scripts to be loaded from trusted scripts
|
|
38
42
|
// when `'strict-dynamic'` is present, `'unsafe-inline' 'self' https: http:` is ignored by browsers
|
|
39
43
|
// `'unsafe-inline' 'self' https: http:` is a compat check for browsers that don't support `strict-dynamic`
|
|
40
44
|
// https://content-security-policy.com/strict-dynamic/
|
|
41
|
-
const rules =
|
|
42
|
-
|
|
45
|
+
const rules = [
|
|
46
|
+
`'nonce-${nonce}'`,
|
|
47
|
+
`'strict-dynamic'`,
|
|
48
|
+
`'unsafe-inline'`,
|
|
49
|
+
params.unsafeEval && `'unsafe-eval'`,
|
|
50
|
+
`'self'`,
|
|
51
|
+
`https:`,
|
|
52
|
+
`http:`,
|
|
53
|
+
].filter(Boolean);
|
|
54
|
+
const scriptSrc = `script-src ${rules.join(" ")}`;
|
|
43
55
|
const reportUri = `report-uri /.netlify/functions/__csp-violations`;
|
|
44
56
|
|
|
45
57
|
const csp = response.headers.get(header);
|
|
@@ -75,7 +87,7 @@ const handler = async (request: Request, context: Context) => {
|
|
|
75
87
|
const page = await response.text();
|
|
76
88
|
const rewrittenPage = page.replace(
|
|
77
89
|
/<script([^>]*)>/gi,
|
|
78
|
-
|
|
90
|
+
`<script$1 nonce="${nonce}">`
|
|
79
91
|
);
|
|
80
92
|
return new Response(rewrittenPage, response);
|
|
81
93
|
};
|
|
@@ -136,6 +148,7 @@ export const config: Config = {
|
|
|
136
148
|
path: params.path,
|
|
137
149
|
excludedPath: [
|
|
138
150
|
...params.excludedPath,
|
|
151
|
+
"/.netlify/*",
|
|
139
152
|
...excludedExtensions.map((ext) => `**/*.${ext}`),
|
|
140
153
|
],
|
|
141
154
|
handler,
|