Sunday, September 15, 2013

Changing the Dart URI Policy for HTML Sanitizing


I was not prepared to like the HTML sanitization in Dart, but there it is.

I believe that it is fantastic that a web language—not a web framework, but a web language—is putting so much thought into security. It bodes well for the future of the web. No doubt other languages will follow suit and if they don't, well developers will not have any business using those languages.

The “why” of language security is a no-brainer. The “how” is what concerns me here. The default HTML sanitization is pretty reasonable. It will strip out anything that is known to be risky. The developer (that's me!) has to decide whether to work within the confines of the sanitized code or to change the sanitization strategy.

Last night, I tried the latter, which led to:
    list_el.setInnerHtml(
      graphic_novels_template(list),
      validator: new NodeValidatorBuilder()
        ..allowHtml5()
        ..allowElement('a', attributes: ['href'])
    );
Given an HTML template that is comprised of comic books:
graphic_novel_template(graphic_novel) {
  return '''
    <li id="${graphic_novel['id']}">
      ${graphic_novel['title']}
      <a href="#" class="delete">[delete]</a>
    </li>''';
}
The list_el.setInnerHtml() operation does pretty much what you might expect: it sets the HTML contents inside the list_el element.

This is not the usual Dart setter for inner HTML because of the need for the optional validator. If I did not need a specialized validator, I could have done something more like:
list_el.innerHtml = graphic_novels_template(list);
The default sanitization scheme lets through just about everything I need in my template. In fact, under normal conditions, it lets through everything that I need. But as I found last night, under file:// context test, the href attributes are stripped off with console warnings:
Removing disallowed attribute <A href="#">
I can probably live with that under test, but it got me wondering how to handle this situation. In the end, I found that I could take the base HtmlValidator and allow typical HTML5 elements along with any href attributes:
    list_el.setInnerHtml(
      graphic_novels_template(list),
      validator: new NodeValidatorBuilder()
        ..allowHtml5()
        ..allowElement('a', attributes: ['href'])
    );
(the double dot operator is a method cascade which calls the method on the object, but returns the object instead of the method's return value)

That worked, but is not quite what I wanted. I do not want to allow all href attributes—just “#” in addition to the usual values that are allowed. The reason that “#” is not allowed from file:// is that it is running afoul of the default URI policy. Actually, I am not sure why “#” runs afoul of the default URL policy when run from a file:// URL. That seems like it ought to work regardless of the context. But it is what it is and I would like to allow it.

To do so, I create my own URI policy class that allows the URI if it equals “#”:
class MyUriPolicy implements UriPolicy {
  bool allowsUri(uri) => uri == '#';
}
I can then replace the blanket allow href for <A> tags with my URI policy for navigation:
    list_el.setInnerHtml(
      graphic_novels_template(list),
      validator: new NodeValidatorBuilder()
        ..allowHtml5()
        ..allowNavigation(new MyUriPolicy())
    );
And that does the trick. I have my tests passing without any sanitization warnings. In the end I will probably switch back to the regular innerHtml setter—at least in this case. Still, I am glad that I figured that out.


Day #875

1 comment:

  1. Thanks for the post! Helped me understand some things about the topic.
    I have an X3DOM frame, which I load only if an event occurs. For this I needed to allow all the non-standard HTML elements like x3d, shape, box, etc.
    Is there a way of allowing a list of elements? I couldn't find it in the DOC. An iterable is only used for the attributes.
    Also, per Dart documentation, the HttpRequest is asynchronous. I tried await for the function, but even then sometimes the content does not load. Any ideas? I know use Future API with .then, but even so sometimes it does not load.Code
    getX3D(String $file) {
    return HttpRequest.getString($file);
    }
    insertX3DHtml() {
    var div = querySelector('#insert-x3d');
    // Here I need a validator to accept x3d tags.
    var frameHtml = getX3D('x3d-frame.html');
    frameHtml.then((resp) {
    div.setInnerHtml(resp,
    validator: new NodeValidatorBuilder()
    ..allowHtml5()
    ..allowElement('x3d'), ..others);});
    }
    main() {
    insertX3DHtml();
    }
    Thanks!

    ReplyDelete