ractivejs / ractive

Next-generation DOM manipulation
http://ractive.js.org
MIT License
5.94k stars 396 forks source link

Partials and templates with expressions are not CSP compliant in root instance #3306

Closed smoldaner closed 5 years ago

smoldaner commented 5 years ago

Description:

Pre-parsed partials and templates cannot be used on environments that enforce a strict Content Security Policy (CSP).

It turns out that only expressions in partials/templates used in components are successfully pre-converted to functions. In the root ractive instance the the pre-parsed expression functions are ignored.

See #3285 for a similar issue

Versions affected:

1.3.6

Platforms affected:

All pages that enforce a strict CSP policy

Reproduction:

Using a pre-parsed templates/partials with expression functions and csp: true and allowExpressions: false options. See JSFiddle Testcase

The fiddle uses pre-compiled templates/partials (with expressions functions) generated via

const serialize = require('serialize-javascript')
const Ractive = require('ractive')
const output = serialize(Ractive.parse(source), {space: 2})

from the following templates:

template:

ROOT:
<div id="root">
    <div class="template">
        template value:<span class="testvalue">{{test()}}</span>
    </div>
    {{>partial1}}
</div>
COMPONENT:
<div id="component">
    <test></test>
</div>

template2:

<div class="template">
    template value:<span class="testvalue">{{test()}}</span>
</div>
{{>partial2}}

partial:

<div class="partial">
partial value:<span class="testvalue">{{test()}}</span>
</div>

Testcode:

var fixture = document.getElementById( 'qunit-fixture' );

test( 'CSP tests', function ( t ) {
    var partial = {
  "v": 4,
  "t": [
    {
      "t": 7,
      "e": "div",
      "m": [
        {
          "t": 13,
          "n": "class",
          "f": "partial",
          "g": 1
        }
      ],
      "f": [
        "partial value:",
        {
          "t": 7,
          "e": "span",
          "m": [
            {
              "t": 13,
              "n": "class",
              "f": "testvalue",
              "g": 1
            }
          ],
          "f": [
            {
              "t": 2,
              "x": {
                "r": [
                  "test"
                ],
                "s": "_0()"
              }
            }
          ]
        }
      ]
    }
  ],
  "e": {
    "_0()": function (_0){return(_0());}
  }
}
    var template = {
  "v": 4,
  "t": [
    "ROOT: ",
    {
      "t": 7,
      "e": "div",
      "m": [
        {
          "n": "id",
          "f": "root",
          "t": 13,
          "g": 1
        }
      ],
      "f": [
        {
          "t": 7,
          "e": "div",
          "m": [
            {
              "t": 13,
              "n": "class",
              "f": "template",
              "g": 1
            }
          ],
          "f": [
            "template value:",
            {
              "t": 7,
              "e": "span",
              "m": [
                {
                  "t": 13,
                  "n": "class",
                  "f": "testvalue",
                  "g": 1
                }
              ],
              "f": [
                {
                  "t": 2,
                  "x": {
                    "r": [
                      "test"
                    ],
                    "s": "_0()"
                  }
                }
              ]
            }
          ]
        },
        " ",
        {
          "t": 8,
          "r": "partial1"
        }
      ]
    },
    " COMPONENT: ",
    {
      "t": 7,
      "e": "div",
      "m": [
        {
          "n": "id",
          "f": "component",
          "t": 13,
          "g": 1
        }
      ],
      "f": [
        {
          "t": 7,
          "e": "test"
        }
      ]
    }
  ],
  "e": {
    "_0()": function (_0){return(_0());}
  }
}
    var template2 = {
  "v": 4,
  "t": [
    {
      "t": 7,
      "e": "div",
      "m": [
        {
          "t": 13,
          "n": "class",
          "f": "template",
          "g": 1
        }
      ],
      "f": [
        "template value:",
        {
          "t": 7,
          "e": "span",
          "m": [
            {
              "t": 13,
              "n": "class",
              "f": "testvalue",
              "g": 1
            }
          ],
          "f": [
            {
              "t": 2,
              "x": {
                "r": [
                  "test"
                ],
                "s": "_0()"
              }
            }
          ]
        }
      ]
    },
    " ",
    {
      "t": 8,
      "r": "partial2"
    }
  ],
  "e": {
    "_0()": function (_0){return(_0());}
  }
}

  var component = Ractive.extend({
    isolated: false,
    template: template2,
    partials: {
      partial2: partial
    }
  })

  Ractive({
    el: fixture,
    csp: true,
    allowExpressions: false,
    template: template,
    components: {
      test: component
    },
    partials: {
      partial1: partial
    },
    data: {
      test: function () {
        return 'testValue'
      }
    }
  })

  t.equal( fixture.querySelector('#component .template .testvalue').innerHTML, 'testValue' )
  t.equal( fixture.querySelector('#component .partial .testvalue').innerHTML, 'testValue' )
  t.equal( fixture.querySelector('#root .template .testvalue').innerHTML, 'testValue' )
  t.equal( fixture.querySelector('#root .partial .testvalue').innerHTML, 'testValue' )
})
evs-chris commented 5 years ago

I think there are two separate issues here: a bug and a docs bug.

allowExpressions is meant to completely disallow evaluating any expressions, including pre-parsed expressions in a template, in case that you don't trust your templates at all. The docs don't make that particularly clear.

The bug is that partials loaded from a template object don't currently preload their expression payload to make them available without re-parsing.

I have a fix for the bug ready, and I'll get the docs bug in a bit.

evs-chris commented 5 years ago

The fix is now published on edge, and will be cherry-picked over to 1.3 and 1.2 branches when the next releases are cut.