Skip to content

html/template: provide JS variable population helper, disable arbitrary template actions inside of <script> contexts #77031

@rolandshoemaker

Description

@rolandshoemaker

The html/template package allows the usage of template actions within <script> contexts, and attempts to apply escaping to these actions depending on the JS context the action appears in.

In order to support this behavior the template parser needs to have a relatively complex understanding of JS semantics in order to properly determine how to safely escape the input to actions. In order to satisfy this property, we have a partial JS parser that attempts to track the contexts actions appear in. The ECMAScript specification, and the grammar it defines, are non-trivial, and our parser is not able, for various reasons, to effectively (and safely) handle complex constructions (in part this is due to how the template parser and the JS parser interact, which forces the JS parser to consume partial JS snippets which prevents it from maintaining proper state).

Due to the limitations of our JS parser, we've had a number of security vulnerabilities in html/template that are specifically related to making incorrect escaping decisions inside <script> contexts, even if the template author has carefully constructed their template (thus violating a described property of the security module: "A developer (or code reviewer) familiar with HTML, CSS, and JavaScript, who knows that contextual autoescaping happens should be able to look at a {{.}} and correctly infer what sanitization happens.")

In the vast majority of cases actions within <script> contexts are used to populate JS variables.

We can vastly improve the safety of html/template by providing a helper that allows either injecting a block which contains variables to the beginning of a <script> block, or injecting a <script> block which just contains variable definitions.

One way to accomplish this would be to add a new html/template builtin, along the lines of urlescaper, such as jsvarblock which would require a map or struct as input.

e.g. the following template would produce the following output when passed the map map[string]any{"a": "str", "b": 1}:

<html>
<head>
<script>
{{. | jsvarblock}}
</script>
</head>
</html>
<html>
<head>
<script>
    let a = "str";
    let b = 1;
</script>
</head>
</html>

The template parser would only need to track if the jsvarblock invocation happened at the beginning of a <script> block. Any other usage of jsvarblock would result in an error.

Additionally, if we add this helper, we should consider disabling the usage of actions elsewhere inside of <script> blocks.

This could be done by default, with a GODEBUG to disable the behavior (arbitraryjsactions), as well as adding a new option to Template.Options, arbitraryjsactions, with the following behavior:

"arbitraryjsactions=default"
    The default behavior: Do not allow the usage of template actions inside of <script> blocks
    unless they use `jsvarblock` and appear as the first token after the opening <script> tag.
    If GODEBUG=arbitraryjsactions=1 is set, the option "arbitraryjsactions=default" is the same
    as "arbitraryjsactions=enabled".
"arbitraryjsactions=enabled"
    Allow the usage of arbitrary template actions anywhere inside of <script> blocks.

These changes wouldn't affect the usafe of html/template.JS or html/template.JSStr inside of <script> blocks, as these entirely bypass escaping behaviors and as such don't require complex context tracking.

cc @golang/security @empijei

Metadata

Metadata

Assignees

No one assigned

    Labels

    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.SecurityThinking

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions