Extensions & Integrations

Security Headers

This module sends the modern HTTP security headers that are expected by various security scanners.
> npm i @apostrophecms/security-headers

ApostropheCMS logo

Security Headers

GitHub Workflow Status (branch)

This module sends the modern HTTP security headers that are expected by various security scanners. The default settings are strict in most regards, so see below for adjustments you may wish to make.

Warning

Some third-party services, including Google Analytics, Google Fonts, YouTube and Vimeo, are included as allowed sources for HTML, CSS and scripts in the standard configuration. However even with these permissive settings not all third-party services compatible with Apostrophe will be permitted out of the box. For instance, because they are used relatively rarely, no special testing has been done for Wufoo or Infogram. You should test your site and configure custom policies accordingly.

Installation

To install the module, use the command line to run this command in an Apostrophe project's root directory:

npm install @apostrophecms/security-headers

Usage

Activate the @apostrophecms/security-headers module in the project's app.js file:

require('apostrophe')({
  shortName: 'my-project',
  modules: {
    '@apostrophecms/security-headers': {}
  }
});

The headers to be sent can be overriden by setting them as options to the module in the project-level modules/@apostrophecms/security-headers/index.js file:

// in modules/@apostrophecms/security-headers/index.js
module.exports = {
  options: {
    'X-Frame-Options': 'DENY'
  }
};

You can also disable a header entirely by setting the option for it to false.

Guide to configuring headers

Here are the headers that are sent by default, with their default values:

module.exports = {
  // in modules/@apostrophecms/security-headers/index.js
  options: {
    // 1 year. Do not include subdomains as they could be unrelated sites
    'Strict-Transport-Security': 'max-age=31536000',
    // You may also set to DENY, however future Apostrophe modules may use
    // iframes to present previews etc.
    'X-Frame-Options': 'SAMEORIGIN',
    // If you have issues with broken images etc., make sure content type
    // configuration is correct for your production server
    'X-Content-Type-Options': 'nosniff',
    // Very new. Used to entirely disable browser features like geolocation per host.
    // Since we don't know what your site may need, we don't try to set this
    // header by default (false means "don't send the header")
    'Permissions-Policy': false,
    // Don't send a "Referer" (sp) header unless the new URL shares the same
    // origin. You can set this to `false` if you prefer cross-origin "Referer"
    // headers be sent. Apostrophe does not rely on them
    'Referrer-Policy': 'same-origin',
    // `true` means it should be computed according to the rules below.
    // You may also pass your own string, or `false` to not send this header.
    // The `policies` option and all of its sub-options are ignored unless
    // `Content-Security-Policy` is `true`.
    'Content-Security-Policy': true
  }
};

For more information about these security headers see the MDN documentation.

Configuring the Content-Security-Policy header

The Content-Security-Policy header is more complex than the others. As described in MDN, this header is used to determine which hosts are permitted as a source for stylesheets, scripts, images and more. While you can certainly set a fixed value for it, the default response for it is the result of merging together options for individual use cases ("policies") as shown below. This makes it easier to think about what you need to allow for a particular purpose. The actual header sent contains all of the permissions required to satisfy all of the policies.

The default configuration is shown below. You do not have to copy and paste the entire default configuration, only the policies you wish to change. Any policy you specify explicitly at project level overrides all of the default settings shown below for that policy. You may set one to false to completely disable it. You may also introduce entirely new policies.

Policies of the same type from different sub-options are merged, with the largest set of keywords and hosts enabled. This is done because browsers do not support more than one style-src policy, for example, but do support specifying several hosts.

Note the HOSTS wildcard which matches all expected hosts that Apostrophe is aware of, including baseUrl settings, CDN hosts and locale-specific hostnames.

module.exports = {
  // in modules/@apostrophecms/security-headers/index.js
  options: {
    policies: {
      general: {
        'default-src': 'HOSTS',
        // Because it is necessary for some of the output of our rich text editor
        'style-src': "HOSTS 'unsafe-inline'",
        'script-src': 'HOSTS',
        'font-src': 'HOSTS',
        'frame-src': "'self'"
      },

      // Set this sub-option to false if you wish to forbid google fonts
      googleFonts: {
        'style-src': 'fonts.googleapis.com',
        'font-src': 'fonts.gstatic.com'
      },

      // Set this sub-option to false if you do not use the video widget
      oembed: {
        'frame-src': '*.youtube.com *.vimeo.com',
        'img-src': '*.ytimg.com'
      },

      // Set this sub-option to false if you do not wish to permit Google Analytics and
      // Google Adsense
      analytics: {
        'default-src': '*.google-analytics.com *.doubleclick.net',
        // Note that use of google tag manager by definition brings in scripts from
        // more third party sites and you will need to add policies for them
        'script-src': '*.google-analytics.com *.doubleclick.net *.googletagmanager.com',
      }
    }
  }
};

Inline style attributes are still allowed

Note that style-src is set by default to permit inline style attributes. This is currently necessary because the output of the tiptap rich text editor used in Apostrophe involves inline styles in some cases.

Inline script tags are not allowed

Inline script tags (those without a src) are not allowed by the default policies shown above, as this is one of the primary benefits of using the Content-Security-Policy header. If you do choose to output an inline script tag, you may do so if you use the "nonce" template argument provided by this module, like this:

<script nonce="{{ nonce }}">
  // inline script code here
</script>

The nonce template variable is always available in Nunjucks templates when using this module. It is generated uniquely for each new page request.

The nonce mechanism ensures that the script tag was the intention of the developer and is not an XSS attack. However please note that you will lose the security benefits of this if you output other user-entered data inside the script tag without properly escaping it, for instance using the | json nunjucks filter.

Setting the nonce attribute on a DOM element has no ill effects when this module is not in use, so it is OK to set it in inline script tags output by npm modules intended for use with or without this module.

Custom policies

You may add any number of custom policies. Any sub-option nested in your policies option is treated just like the standard cases above and merged into the final Content-Security-Policy header.

Disabling standard policies

You may set any of the standard policy sub-options above to false to disable them.

Hosts wildcard

Note that the HOSTS wildcard is automatically replaced with a list of hosts including any baseUrl host, localized hostnames for specific locales, CDN hosts from your uploadfs configuration, and self. Use of this wildcard is recommended as Apostrophe pushes assets to Amazon S3, CDNs, etc. when configured to do so, including scripts and stylesheets.

You may override the normal list of hosts for HOSTS by setting the legitimateHosts option to an array of strings. You can also extend or override the legitimateHosts method of this module at project level.

For example:

module.exports = {
  // in modules/@apostrophecms/security-headers/index.js
  options: {
    legitimateHosts: [ 'mysite.com', 'www.mysite.com', 'surprise.mysite.com' ]
  }
};

Updated

Over 1 year ago

Version

1.0.1

Report a bug