Skip to content

Instantly share code, notes, and snippets.

@mikesamuel
Last active August 12, 2022 15:09
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikesamuel/c816d975ce22d2802992203c140ff34f to your computer and use it in GitHub Desktop.
Save mikesamuel/c816d975ce22d2802992203c140ff34f to your computer and use it in GitHub Desktop.
CSP Auto-noncing in Go html/template

Auto-noncing in Go html/template

Background

CSP mitigates many client-side security vulnerabilities. A policy is a whitelist of locations from which JavaScript, Styles, and other content can be loaded. CSP allows nonces & hashes to make it easy for a policy to allow some inline content without allowing all inline content.

html/template makes it easy to produce HTML that preserves the template author's intent in the face of untrusted inputs.

The upcoming safehtml/template builds on html/template to, among other things, distinguish between URLs that load code into the documents origin (TrustedResourceUrls) and those that do not (SafeUrl), like link target URLs and media src URLs.

Using CSP

Right now, a server can pass a nonce into a template.

<script nonce="{{.CSPNonce}}">
...
</script>

and this is fine since the autoescaper ensures that any third-party content interpolated into the script tag is side-effect-free at evaluation time.

There is a risk though in some other constructs

<link rel="script" href="{{.ScriptSrc}}" />

In this case, the same script would have to end up in the CSP policy. Alternatively, the template maintainer could add a nonce

<link rel="script" href="{{.ScriptSrc}}" nonce="{{.CSPNonce}} />

adding a nonce is implicitly saying that whatever {{.}} evaluates to is a safe script source. This avoids the type-safety-based security around SafeURL in Hugo and in the upcoming safehtml/template.

There is also a maintenance risk when something like

<link rel="script" href="{{.BaseUrl}}/script/foo.js" nonce="{{.CSPNonce}}" />

is edited to make the href more general.

Problem

There are two problems

  1. Without nonces the code that generates CSP policy headers needs to be tightly integrated with the html/template that produces the HTML.
  2. If template authors have to sprinkle nonce="..." around their templates they make mistakes with security consequences.

Related Work

For the Closure Templates language we got template authors out of the business of adding nonces to code.

Proposal

Augment html/template to inject {{if $.CSPNonce}} nonce="{{$.CSPNonce}}"{{end}} in the following contexts:

A script element with no src attribute.

<script HERE>...</script>

A style element.

<style HERE>...</style>

These are the main use cases for CSP nonces but there are some other contexts we could consider.

load content, but not into the same origin.

Others are riskier

  • <iframe> can affect the current page in many ways if not strictly sandboxed,
  • A <link> with a rel attribute with value in (import, manifest, script, stylesheet) can load code directly into the same origin.

It is unsafe to inject the nonce if the src/href is dynamic and not a TrustedResourceUrl. safehtml/template ensures this property but html/template does not.

Whence nonces?

Generating a strongly unpredictable, properly scoped nonce will be left to frameworks.

When one template calls another though, it can pass a portion of the input, so the callee may not receive the caller's nonce.

We use the implicitly defined $ variable to reach a nonce at the top level. This means that every top-level template input that produces CSP-compatible output needs to have a nonce.

We can ease this by providing

struct {
    CSPNonce Nonce
}

that can be mixed into an input struct via an anonymous field.

Alternative Solutions

To avoid tight coupling between the code that generates CSP policy headers, and the html/template, a template could use a custom function.

<script src="{{.scriptSrc | addToCspWhitelist}}">...</script>

The function could callback to CSP policy generating code while returning its value unchanged.

This requires the response body be rendered before headers are written and could suffer from the same lack of type-safety.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment