Skip to content

Commit

Permalink
feat: support controlled rollouts by environment variable (#41)
Browse files Browse the repository at this point in the history
* fix: support CSP_NONCE_DISTRIBUTION env var

* fix: more comments

* fix: udpated readme

* fix: correct scope in readme

* fix: missing second argument of Math.max

* fix: more logging

* fix: replace Netlify.env with Deno.env

* fix: more logging

* fix: actually use the correct syntax

* fix: disable logging
  • Loading branch information
Jason Barry authored Jun 14, 2023
1 parent fbda4fb commit 64b285b
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 3 deletions.
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,14 @@ If your HTML does not contain the `nonce` attribute on the `<script>` tags that
- The `content-type` response header starts with `text/html`
- The path of the request is satisfied by the `path` config option, and not included in the `excludedPath` config option

### Quickly enabling and disabling
### Controlling rollout

You may want to quickly enable/disable the plugin while monitoring violation reports. You can do so without modifying code.
You may want to gradually rollout the effects of this plugin while you monitor violation reports, without modifying code.

Simply set the `DISABLE_CSP_NONCE` environment variable to `true`, and your next deploy will skip running the plugin. Setting to `false` will re-enable the plugin. The environment variable needs to be scoped to `Builds`.
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`.

- 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.
- 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.
- 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.

The `CSP_NONCE_DISTRIBUTION` environment variable needs to be scoped to both `Builds` and `Functions`.
17 changes: 17 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,28 @@ export const onPreBuild = async ({ inputs, netlifyConfig, utils }) => {
const config = JSON.stringify(inputs, null, 2);
const { build } = netlifyConfig;

// DISABLE_CSP_NONCE is undocumented (deprecated), but still supported
// -> superseded by CSP_NONCE_DISTRIBUTION
if (build.environment.DISABLE_CSP_NONCE === "true") {
console.log(` DISABLE_CSP_NONCE environment variable is true, skipping.`);
return;
}

// CSP_NONCE_DISTRIBUTION is a number from 0 to 1,
// but 0 to 100 is also supported, along with a trailing %
const distribution = build.environment.CSP_NONCE_DISTRIBUTION;
if (!!distribution) {
const threshold =
distribution.endsWith("%") || parseFloat(distribution) > 1
? Math.max(parseFloat(distribution) / 100, 0)
: Math.max(parseFloat(distribution), 0);
console.log(` CSP_NONCE_DISTRIBUTION is set to ${threshold * 100}%`);
if (threshold === 0) {
console.log(` Skipping.`);
return;
}
}

console.log(` Current working directory: ${process.cwd()}`);
const basePath =
build.environment.SITE_ID === SITE_ID
Expand Down
16 changes: 16 additions & 0 deletions src/__csp-nonce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ const handler = async (request: Request, context: Context) => {
return response;
}

// CSP_NONCE_DISTRIBUTION is a number from 0 to 1,
// but 0 to 100 is also supported, along with a trailing %
// @ts-expect-error
const distribution = Netlify.env.get("CSP_NONCE_DISTRIBUTION");
if (!!distribution) {
const threshold =
distribution.endsWith("%") || parseFloat(distribution) > 1
? Math.max(parseFloat(distribution) / 100, 0)
: Math.max(parseFloat(distribution), 0);
// if a roll of the dice is greater than our threshold, skip
const random = Math.random();
if (random > threshold && threshold <= 1) {
return response;
}
}

const nonce = randomBytes(24).toString("base64");
// `'strict-dynamic'` allows scripts to be loaded from trusted scripts
// when `'strict-dynamic'` is present, `'unsafe-inline' 'self' https: http:` is ignored by browsers
Expand Down

0 comments on commit 64b285b

Please sign in to comment.