@localnerve/csp-hashes
v5.0.3
Published
Flexible library to generate CSP hashes
Downloads
312
Readme
csp-hashes
Flexible build library to generate script and style hashes for CSP headers or meta tags
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 yourcallback
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
}
});
});