@netlify/plugin-csp-nonce 1.2.0 → 1.2.2

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 CHANGED
@@ -97,10 +97,10 @@ If your HTML does not contain the `nonce` attribute on the `<script>` tags that
97
97
 
98
98
  You may want to gradually rollout the effects of this plugin while you monitor violation reports, without modifying code.
99
99
 
100
- You can ramp up or ramp down the inclusion of the headers this plugin enforces by setting the `CSP_NONCE_DISTRIBUTION` environment variable to a value between `0` and `1`.
100
+ You can ramp up or ramp down the inclusion of the `Content-Security-Policy` header by setting the `CSP_NONCE_DISTRIBUTION` environment variable to a value between `0` and `1`.
101
101
 
102
102
  - If `0`, the plugin is completely skipped at build time, and no extra functions or edge functions get deployed. Functionally, this acts the same as if the plugin isn't installed at all.
103
103
  - If `1`, 100% of traffic for all matching paths will include the nonce. Functionally, this acts the same as if the `CSP_NONCE_DISTRIBUTION` environment variable was not defined.
104
- - Any value in between `0` and `1` will include the nonce in randomly distributed traffic. For example, a value of `0.25` will include the nonce 25% of the time for matching paths.
104
+ - Any value in between `0` and `1` will include the nonce in randomly distributed traffic. For example, a value of `0.25` will put the nonce in the `Content-Security-Policy` header 25% of requests for matching paths. The other 75% of matching requests will have the nonce in the `Content-Security-Policy-Report-Only` header.
105
105
 
106
106
  The `CSP_NONCE_DISTRIBUTION` environment variable needs to be scoped to both `Builds` and `Functions`.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@netlify/plugin-csp-nonce",
4
- "version": "1.2.0",
4
+ "version": "1.2.2",
5
5
  "description": "Use a nonce for the script-src and style-src directives of your Content Security Policy.",
6
6
  "main": "index.js",
7
7
  "repository": {
@@ -3,6 +3,8 @@
3
3
  import type { Config, Context } from "netlify:edge";
4
4
  // @ts-expect-error
5
5
  import { randomBytes } from "node:crypto";
6
+ // @ts-expect-error
7
+ import { HTMLRewriter } from "https://ghuc.cc/worker-tools/html-rewriter@0.1.0-pre.17/index.ts";
6
8
 
7
9
  import inputs from "./__csp-nonce-inputs.json" assert { type: "json" };
8
10
 
@@ -15,12 +17,12 @@ type Params = {
15
17
  };
16
18
  const params = inputs as Params;
17
19
 
18
- const header = params.reportOnly
19
- ? "content-security-policy-report-only"
20
- : "content-security-policy";
21
-
22
20
  const handler = async (request: Request, context: Context) => {
23
- const response = await context.next();
21
+ const response = await context.next(request);
22
+
23
+ let header = params.reportOnly
24
+ ? "content-security-policy-report-only"
25
+ : "content-security-policy";
24
26
 
25
27
  // for debugging which routes use this edge function
26
28
  response.headers.set("x-debug-csp-nonce", "invoked");
@@ -48,10 +50,16 @@ const handler = async (request: Request, context: Context) => {
48
50
  distribution.endsWith("%") || parseFloat(distribution) > 1
49
51
  ? Math.max(parseFloat(distribution) / 100, 0)
50
52
  : Math.max(parseFloat(distribution), 0);
51
- // if a roll of the dice is greater than our threshold, skip
52
53
  const random = Math.random();
54
+ // if a roll of the dice is greater than our threshold...
53
55
  if (random > threshold && threshold <= 1) {
54
- return response;
56
+ if (header === "content-security-policy") {
57
+ // if the real CSP is set, then change to report only
58
+ header = "content-security-policy-report-only";
59
+ } else {
60
+ // if the CSP is set to report-only, return unadulterated response
61
+ return response;
62
+ }
55
63
  }
56
64
  }
57
65
 
@@ -103,13 +111,13 @@ const handler = async (request: Request, context: Context) => {
103
111
  response.headers.set(header, value);
104
112
  }
105
113
 
106
- // time to do some regex magic
107
- const page = await response.text();
108
- const rewrittenPage = page.replace(
109
- /<script([^>]*)>/gi,
110
- `<script$1 nonce="${nonce}">`
111
- );
112
- return new Response(rewrittenPage, response);
114
+ return new HTMLRewriter()
115
+ .on("script", {
116
+ element(element) {
117
+ element.setAttribute("nonce", nonce);
118
+ },
119
+ })
120
+ .transform(response);
113
121
  };
114
122
 
115
123
  // Top 50 most common extensions (minus .html and .htm) according to Humio
@@ -169,7 +177,7 @@ export const config: Config = {
169
177
  excludedPath: [
170
178
  ...params.excludedPath,
171
179
  "/.netlify/*",
172
- ...excludedExtensions.map((ext) => `**/*.${ext}`),
180
+ `**/*.(${excludedExtensions.join("|")})`,
173
181
  ],
174
182
  handler,
175
183
  onError: "bypass",