@localnerve/csp-hashes

4.0.8 • Public • Published

csp-hashes

Flexible build library to generate script and style hashes for CSP headers or meta tags

npm version Verify Coverage Status

Contents

Overview

This Nodejs library generates script and style inline element and attribute hashes. It is for use in the generation of HTTP content security policy (CSP) headers or to replace/update Meta tags as a website build step. Ready for use with Gulp.

Breaking Changes

  • As of Version 2+, this is an ES Module. See Non-Esm Usage for how to use outside of ESM.

Prerequisites

  • NodeJS 18+

API

hashstream (also the default export)

This library exports a function that takes options and returns a transform stream in object mode. The transform stream operates on Vinyl objects or a compatible file object with path and contents properties. The only required option is a callback function.

Stream hashstream ({
  callback,
  replace = false,
  algo = 'sha256'
})

See hashstream options for a detailed explanation of the input options.

createCspHash

This library exports the helper method it uses to make CSP formatted hashes. This is useful if you have a picece of code you need to hash and place into your hash list outside the scope of the page as rendered.

String createCSPHash(inputString, algo = 'sha256')
  • {String} inputString - Required - The input content to hash.
  • {String} [algo] - Optional - Defaults to 'sha256', can be one of 'sha256', 'sha384' or 'sha512'.

Returns a ready to use csp hash string (with quotes) in the form of 'sha256-d3ii1Pel57UO62xosCMNgTaZJhJa87Gd/X6e7UdlEU8='.

removeCspMeta

This library also exports a convenience helper method, removeCspMeta that is useful for some types of development builds. This method takes no options and returns a stream that operates on Vinyl objects and removes any Content-Security-Policy content found in the files.

Stream removeCspMeta ()

Hashstream Options

  • {Function} callback - Required - A function to process the hashes. Receives file contents and must return new file contents if replace option is true.
  • {Boolean} [replace] - Optional - Defaults to false, set to true to indicate your callback function returns new file contents to replace the original.
  • {String} [algo] - Optional - Defaults to 'sha256', can be one of 'sha256', 'sha384' or 'sha512'.

Callback Function

A callback function is required to process the CSP hashes collected by this library for your build.

callback(path, hashes[, contents])
  • {String} path - The local filesystem path to the original file. Use to create your own rules and/or a path to the web resource for writing header rules.
  • {Object} hashes - The script and style inline element and attribute hashes for the current file. See object format for details.
  • {String} [contents] - The original file contents. Only sent if the replace option is true, in which case you must return new file contents.
Callback Hashes Object

The callback hashes object contains all of the inline element and attribute hashes for scripts and styles in the current file being processed. The object has the following format:

// `hashes` object:
{
  script: {
    elements: [],
    attributes: [],
    get all () {
      return this.elements.concat(this.attributes);
    }
  },
  style: {
    elements: [],
    attributes: [],
    get all () {
      return this.elements.concat(this.attributes);
    }
  }
}

The object structure allows you to direct the hashes to any CSP header directive layout you might use. You can use script-src or style-src alone and concatenate the element and attribute hashes together into one list using the all getter property, or you can use script-src-attr and style-src-attr separately, whatever is a more secure/optimal policy for your situation.
NOTE
The hashes object structure is always the same. If there are no elements or attributes of script or style in the current file, the arrays are just empty (not null).

Example Usage

Build Step to Maintain CSP Headers

In this example, a build step gets the hashes for every html file under the dist directory, then for each html file, updates the header rules for the host service being deployed to.

import gulp from 'gulp';
import path from 'path';
import hashstream from '@localnerve/csp-hashes';
import { cspHeaderRules } from './host-header-rules';

export function cspHeaders (settings) {
  const { dist } = settings;

  return gulp.src(`${dist}/**/*.html`)
    .pipe(hashstream({
      callback: (p, hashes) => {
        const webPath = p.replace(path.resolve(dist), '');
        cspHeaderRules.updateHashes(webPath, 'script-src', hashes.script.elements.join(' '));
        cspHeaderRules.updateHashes(webPath, 'script-src-attr', hashes.script.attributes.join(' '));
        cspHeaderRules.updateHashes(webPath, 'style-src', hashes.style.elements.join(' '));
        cspHeaderRules.updateHashes(webPath, 'style-src-attr', hashes.style.attributes.join(' '));
      }
    }))
}

Build Step to Maintain CSP Meta Tags

In this example, a build step gets the hashes for every html file under the dist directory, then updates each html file's meta tags to include the hashes after 'self', preserving any other rules before it. This example uses the all property to get the combined element and attribute hashes together.

import gulp from 'gulp';
import hashstream from '@localnerve/csp-hashes';

export function cspMetaTags (settings) {
  const { dist } = settings;

  return gulp.src(`${dist}/**/*.html`)
    .pipe(hashstream({
      replace: true,
      callback: (p /* not used */, hashes, contents) => {
        return contents
          .replace(/script-src (.+) 'self'/, `script-src $1 'self' ${hashes.script.all.join(' ')}`)
          .replace(/style-src (.+) 'self'/, `style-src $1 'self' ${hashes.style.all.join(' ')}`);
      }
    }))
    .pipe(gulp.dest(dist));
}

Build Step to Remove CSP Meta Tag Content

In this example, a build step removes any content from a Content-Security-Policy in a development build that wishes to ignore it.

import gulp from 'gulp';
import { removeCspMeta } from '@localnerve/csp-hashes';

export function stripCspMetaContents (settings) {
  const { dist } = settings;

  return gulp.src(`${dist}/**/*.html`)
    .pipe(removeCspMeta())
    .pipe(gulp.dest(dist));
}

Non-ESM usage

As of Version 2, this package is an ES Module, making it incompatible with require. To use outside of ESM, you can use this with a dynamic import as in the following example:

import('@localnerve/csp-hashes').then(({ hashstream }) => {
  hashstream({
    callback: (p, hashes, contents) => {
      // do stuff
    }
  });
});

LICENSE

Readme

Keywords

Package Sidebar

Install

npm i @localnerve/csp-hashes

Weekly Downloads

24

Version

4.0.8

License

MIT

Unpacked Size

16.4 kB

Total Files

7

Last publish

Collaborators

  • localnerve