Don’t let a Content Security Policy slay your extension’s images 🔪

Carlos Alexander Vializ
Keep It Up
Published in
4 min readMay 18, 2016

Developing a rich browser extension with on-page content poses many challenges. Some obstacles pit extension developers against website devs. Problems such as CSS rule conflicts, overlapping page elements, and race conditions add hours of work to extension projects. But overcoming these issues pays off with detailed user interaction, valuable integrations, and high utility for users, especially for users of Kifi.

Extensions and Content Security Policies

Content Security Policies in particular present an interesting challenge. Security-conscious software engineers implement CSPs to lock-down the resources on their webpages. Browsers receive special headers and will only load content from trusted sources. Scripts, styles and images are all subject to the host page’s policy.

Extensions’ content scripts and stylesheets are allowed because they are injected, not sent down the wire. Images, however, tend to get blocked. Your extension may want to add images, and not all images can be pre-loaded into the extension package. Some must be hosted from your CDN, which is probably not trusted by any CSP. What to do?

Bummer! I’d love to see profile pictures and content previews but, alas, GitHub implements a CSP.

Content scripts and the background page

To investigate a workaround, let’s discuss how extensions work generally. Extension code runs in two places: the background page and in content scripts. The background page runs scripts that interface with the browser’s Extension API. Content scripts are injected into each tab by code from the background page. They share some context with the host page’s resources, they are able to interact with individual pages’ DOM, and they communicate with the background page through message-passing.

Since content scripts share a context with the host page, the same security restrictions apply to their resource requests. If a content script runs on a site with a CSP (like GitHub or Twitter), it is unable to access unapproved resources and images from an external CDN.

The background page, however, plays by different rules. Content Security Policies do not apply to its requests. If our content script passes a URL to the background page, it can proxy the request and message-pass the image data no matter what any content page says. Combine this with the fact that most sites’ CSPs allow images to load from data-URIs and we’re in business.💥

Code on the background page can load resources and pass a data-URI back to the content scripts.

When to run the workaround

But it isn’t enough to load images through the background page. The extension needs to know when to request images through an alternative method. Typically this will be when the all of the following conditions are met:

  1. After a user interacts with the extension UI: You don’t want to consume more bandwidth than is necessary, so don’t use the workaround if the user isn’t looking at your images yet.
  2. After an image from the CDN fails to load: Loading through the background page is somewhat slower than loading normally, so only proxy the image requests if a CSP actually blocks images.
  3. After a UI element with an image is added to the DOM: Images are added and removed often, so the code may need to check and load images more than once.

For the first condition, the CSP check can be called after the user opens the extension UI using an event listener or other initialization code.

Second, to check for a CSP the browser can request a small image from your CDN. If the image triggers an error event, a CSP is probably blocking resources.

If the error event is triggered, we know the image was blocked by a CSP (or the tiny.png is missing 😛)

Third, now that we know a CSP is definitely blocking images, elements will need their image URLs replaced with data-URIs. A capturing event listener on the document will find img tags that fail to load. Elements with background-images do not trigger an event on failure like img tags do, so a MutationObserver is needed to find them.

Event listener and mutation observer working together to fix broken images.

Fixing blocked images

When a CSP blocks an img tag from loading, we can simply listen for the error event. Then the URL from the src attribute is asynchronously passed to the background page, which returns a data-URI. Finally, the img tag’s original src is replaced by the data-URI and a flag is applied to prevent infinite loops should another error occur.

Simple enough

Now, let’s tackle background-images. A MutationObserver observes the document for changes. Once changes occur, we only check descendents of our extension and look for non-data-URI background-images to replace.

You might say that observing the document object seems like overkill, but we have not yet noticed any performance issues. The Kifi extension needs to observe the full document because it adds multiple root elements at the document-level at different times. Managing and observing them separately would not be worth the effort, given the minor effect on performance.

A little more complicated and slow, but it still follows the same basic premise as img tags.

And here is the final product after all the pieces are working together:

Correct and performant, as if there was no CSP at all.

Currently, Chrome does not enforce content security policies on DOM elements created by content scripts, while Firefox and Safari do. If you currently only support Chrome users, this may not be a solution for a problem you have.

However, for a complex cross-browser extension like Kifi, providing support for all cases is necessary, challenging, and fun!

We wrote this post while working on Kifi — Connecting People with Knowledge, available for Chrome, Firefox and Safari. Learn more about our Slack integration.

--

--

Carlos Alexander Vializ
Keep It Up

Pilot, serial unicyclist, software engineer @kifi. Let’s go!