Skip to content

Security

What Signy does with untrusted data, and what is left to you.

Text is text

A value in an html template is set as text, never parsed as markup. A comment that contains <img onerror=...> shows those characters and runs nothing.

html`<p>${comment.body}</p>`;            // safe, whatever the body holds
html`<p title=${comment.author}>...</p>`;  // safe, set as an attribute value

Attributes that could run code

Bindings, spreads, the DOM helpers and router links refuse three kinds of value. The attribute is left out, and dev builds log view:unsafe-attribute when warnings are on.

  • An on attribute given a string, such as onclick=${'alert(1)'}. Handlers must be functions.
  • srcdoc, which holds a whole document.
  • A javascript: or vbscript: URL, or a data: URL that is not an image, in href, src, action, formaction, poster, data or xlink:href. Spaces and control characters hidden in the scheme do not get past the check.

Every other URL is allowed, including links to other sites. Check the host yourself when a link must stay on your site.

Raw HTML

Templates have no way to insert an HTML string. When you need one, such as rendered Markdown, you write it yourself through a ref, and that is where untrusted data must be cleaned.

html`<div ref=${(el) => (el.innerHTML = sanitizeHtml(post.body, ['p', 'b', 'i', 'ul', 'li']))}></div>`;
  • escapeHtml(value) escapes &, <, > and both quotes.
  • sanitizeHtml(html, tags) keeps only the listed tags, drops every attribute, and drops script and style with their contents. Without a list it escapes everything.
  • stripHtml(html) returns the visible text.

sanitizeHtml() removes all attributes, so links lose their href. For rich text with links and images, use a dedicated sanitizer such as DOMPurify. DOM.createTemplate(html) parses an HTML string too: pass it your own markup only.

Store and query strings

  • store.merge() and update() skip a __proto__ key in the data, so merging parsed JSON cannot change Object.prototype.
  • A set() path through __proto__, constructor or prototype, and an assignment to state.__proto__, throw reactivity:store-unsafe-key.
  • The router parses ?__proto__=x or ?toString=1 into plain keys of the query object, without touching prototypes.

Your part

  • Never pass user data to innerHTML, outerHTML, insertAdjacentHTML() or DOM.createTemplate() without a sanitizer.
  • Check URLs from users before you put them in href, beyond the scheme check above.
  • Use a Content Security Policy. Signy and the html template never call eval() or new Function(), so 'unsafe-eval' is not needed.