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.
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.
There are two problems
- Without nonces the code that generates CSP policy headers needs to be tightly integrated with the html/template that produces the HTML.
- If template authors have to sprinkle
nonce="..."
around their templates they make mistakes with security consequences.
For the Closure Templates language we got template authors out of the business of adding nonces to code.
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.
<img>
elements, and- media elements (
<audio>
,<track>
,<video>
) <link rel="favicon">
(modulo bug)
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.
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.
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.