npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

pug-plugin-trusted-types

v2.0.2

Published

XSS prevention for Pug templates with typesafe exceptions

Downloads

88

Readme

Safe Pug Templates (Pug Trusted Types Plugin)

Build Status Dependencies Status npm Coverage Status Install Size Known Vulnerabilities

Hooks into Pug to add Trusted Types checks to key attributes to reduce the risk of XSS.

This plugin focuses on checking URLs, to prevent, e.g. arbitrary strings from reaching <script src> or javascript: URLs from reaching <a href>.

Without this plugin, the below can lead to XSS.

// Attacker controls x
const x = 'javascript:alert(document.domain)';

// Declare a template
const pug = require('pug');
const template = pug.compile('a(href=x) Link', {});

// Use the template
const html = template({ x });

console.log(html);
//! <a href="javascript:alert(document.domain)">Link</a>

This plugin cannot, by itself, prevent XSS due to intentionally unsafe features but when it finds a use of unsafe features, it warns on them and refuses to output TrustedHTML.

Usage

There are several ways to use safe Pug templates.

The Trusted Types plugin adds require calls which only work with code loaded in a CommonJS module context.

Pug compiles templates to JavaScript which it loads by calling new Function() so does not load in a module context.

Webpack integration via pug-loader

Pug-loader makes it easy to compile templates when webpacking.

You do need to configure pug-loader to use this plugin though.

If you're using pug-loader, your webpack.config.js should probably have something like:

({
  rules: [
    // When loading Pug, run pug-loader.
    {
      test: /\.pug$/,
      use: [
        {
          loader: path.resolve('node_modules/pug-loader/index.js'),
          options: {
            plugins: [
              // Any other plugins you use should ideally go first.
              require('pug-plugin-trusted-types'),
            ],
          },
          // Optionally, configure the plugin.  You probably won't need to do this.
          filterOptions: {
            trustedTypes: {
              // See "Plugin Configuration" below.
            },
          },
        },
      ],
    },
    // This runs the module-keys babel processor on all JavaScript sources.
    {
      test: /\.js$/,
      use: [
        {
          loader: path.resolve('node_modules/babel-loader/lib/index.js'),
          options: {
            plugins: ['module-keys/babel'],
          },
        },
      ],
      exclude: /node_modules\/(webpack\/buildin|module-keys|path-browserify|process)/,
    },
  ],
})

Requiring Templates

First you need a dependency:

npm install --save pug-require

Then you can load Pug templates by calling require.

// Adds hooks so that requiring a .pug file loads it as a template.
// Even if you use the default config, you still need to require
// this module before you require the first .pug file.
const { configurePug } = require('pug-require');

configurePug({ /* pug options */ });

// Load a simple template `a(href=x) Link`.
const myTemplate = require('./templates/link.pug');

console.log(myTemplate({ x: 'https://example.com/' }));
//! <a href="https://example.com/">Link</a>

console.log(myTemplate({ x: 'javascript:evil()' }));
//! <a href="about:invalid#TrustedURL">Link</a>

See pug-require for more details.

Inline Templates

First you need a dependency:

npm install --save pug-template-tag

Then you can declare Pug templates inline in JS or TS code.

const pug = require('pug-template-tag');

const myTemplate = pug`a(href=x) Link`;

console.log(myTemplate({ x: 'https://example.com/' }));
//! <a href="https://example.com/">Link</a>

console.log(myTemplate({ x: 'javascript:evil()' }));
//! <a href="about:invalid#TrustedURL">Link</a>

See pug-template-tag for more details including how to configure templates.

Pre-compiled or manually compiled Templates

First you need to install Pug and the Trusted Types Plugin.

npm install --save pug pug-plugin-trusted-types

Then add the plugin to the plugins field of your Pug options object.

Before

const pug = require('pug');

const myTemplate = pug.compile(
    templateCode,
    {
      // Options
    });

After

const pug = require('pug');
const pugPluginTT = require('pug-plugin-trusted-types/plugin');

const myTemplate = pug.compile(
    templateCode,
    {
      plugins: [ pugPluginTT ],
      // Options
    });

Since the Trusted Types Plugin provides security checks, it should ideally run after plugins that do not aim to provide security guarantees. Putting it at the end of any existing plugins array should suffice.

postCodeGen stage plugins could undo security guarantees even if the trusted types plugin runs late.

Double checking expressions

Expressions in Pug templates, whether for attribute values or for text nodes, are double-checked as described below.

| Pug Example | Value of X | Policy | | ------------------ | ------------------------------- | ------------------------------ | | div(title=x) | Ordinary attribute value | | | | Any value | No change | | a(href=x) | External URL attribute | TrustedURL.sanitize | | | Constant expression | No change | | | http: ... | No change | | | https: ... | No change | | | mailto: ... | No change | | | TrustedURL | No change | | | TrustedResourceURL | No change | | | Other | Replaced with about:invalid | | script(src=x) | URL loaded into same origin | | | | Constant expression | No change | | | TrustedResourceURL | No change | | | Other | Replaced with about:invalid | | p =x | Text in a normal element | | | | Constant expression | Auto-escaped unless != | | | TrustedHTML | No change | | | Other | Auto-escaped | | script =x | Text in <script> element | | | | Constant expression | No change | | | TrustedScript | No change | | | Other | Replaced with space | | iframe(srcdoc=x) | HTML in attribute | | | | Constant expression | No change | | | TrustedHTML | Escaped once to embed as value | | | Other | Escaped twice to embed in HTML |

It doesn't matter whether an attribute value appears via assignment as in element(attribute=expression) or in an attribute block like element()&attributes({ attribute: expression }).

Automagic

CSRF (Cross-Site Request Forgery) Protection

CSRF Protection works by putting enough information in <form>s so that the server can double check that it served the form.

Configure the plugin with options like

{
  "csrfInputName":            "csrf",
  "csrfInputValueExpression": "csrfTokenValue"
}

When rendering HTML, pass a value to the template for the CSRF input value expression:

let templateInput = {
  "csrfTokenValue": "r4Nd0M_NuM83R"
};

Any form in your PUG template like:

form(action='delete' method='POST')
  button(type='submit') Delete

will have a hidden input added:

<form action="delete" method="POST">
  <input name="csrf" type="hidden" value="r4Nd0M_NuM83R"/>
  <button type="submit">Delete</button>
</form>

Configuring with csrf-crypto

If you use csrf-crypto and you're plugging in via pug-require, then the pieces fit together like:

// Configure pug-require to thread
const pugRequire = require('pug-require');
pugRequire.configurePug({
  filterOptions: {
    trustedTypes: {
      csrfInputName: '_csrf',
      // You could use 'res.getFormToken()' as the value expression
      // if you pass res to Pug.
      csrfInputValueExpression: 'csrfToken',
    },
  },
});

// Load a pug template after configuring pug-require
const template = require('./path/to/template.pug');

// Setup csrf-crypto to define res.getFormToken().
const csrfCrypt = require('csrf-crypto');
app.use(csrfCrypto({ key: applicationLevelSecret, /* ... */ }));
app.use(csrfCrypto.enforcer());

// When rendering HTML output using pug, provide access to the form token.
function handle(req, res) {
  // ...
  res.end(
    template({
      get csrfToken() {
        // Lazily generate a form token.
        delete this.csrfToken;
        this.csrfToken = res.getFormToken();
        return this.csrfToken;
      },
    }));
}

Content-Security-Policy

Strict CSP explains how to use the Content-Security-Policy header to protect against XSS:

To enable a strict CSP policy, most applications will need to make the following changes:

  • Add a nonce attribute to all <script> elements. Some template systems can do this automatically.
  • Refactor any markup with inline event handlers (onclick, etc.) and javascript: URIs (details).
  • For every page load, generate a new nonce, pass it the to the template system, and use the same value in the policy.

To automatically add nonce attributes, configure the plugin with options like

{
  "nonceValueExpression": "sessionScopedRandomString"
}

And then generate a strong nonce for each HTTP response, and pass it to your template:

let templateInput = {
  // https://csp.withgoogle.com/docs/faq.html#generating-nonces says > 128b = 16B
  sessionScopedRandomString: require('uid-safe').sync(18),
};

Caveat: Do not use npmjs.com/package/nonce. It does not provide strong nonces, nor does it claim to.

Pug that loads CSS or JavaScript will have nonces automatically added.

head
  link(rel='stylesheet' src='/styles.css')
  script(src='/script.js')
  script main()

The output HTML will look like:

<head>
  <link rel="stylesheet" src="/styles.css" nonce="7QgTXZjEaat5wrC8JAn0FsBq"/>
  <script src="/script.js" nonce="7QgTXZjEaat5wrC8JAn0FsBq"></script>
  <script nonce="7QgTXZjEaat5wrC8JAn0FsBq">main()</script>
</head>

If your HTTP response has a header like the below then those CSS and JavaScript will load, but ones lacking the nonce attribute will not.

Content-Security-Policy: default-src 'nonce-7QgTXZjEaat5wrC8JAn0FsBq'

Plugin Configuration

Pug doesn't provide a way to directly configure plugins, but this plugin takes into account

({
  filtersOptions: {
    trustedTypes: {
      report() {
        // ...
      }
    }
  }
})

csrfInputName

A value for an <input name> attribute that is automatically added to <form> elements to protect against Cross-Site Request Forgery (CSRF).

Defaults to csrfToken.

See also CSRF (Cross-Site Request Forgery) Protection.

csrfInputValueExpression

A string containing a JavaScript expression for the value corresponding to the csrfInputName.

Defaults to null. If null, then <form>s have no hidden input added.

See also CSRF (Cross-Site Request Forgery) Protection.

nonceValueExpression

A string containing a JavaScript expression for the value of nonce attribute automatically added to <script> and <style> elements.

Defaults to null. If null, then nonce attributes are not added.

See also Content-Security-Policy.

report(message)

Called if the plugin finds a problem with the template.

By default, this is console.warn.