So you want to allow your power users to extend your website, what could go wrong?
Free for all
Extension frameworks inherently need to balance how much flexibility they provide to apps, and the obvious amount of flexibility to provide is “as much as we‘ve got”.
- make ajax requests to your server as the logged-in user
- make ajax requests to external services via CORS, potentially sharing data available on your page
- manipulate your DOM
- interact with global variables
- modify the prototype of core objects
While you and your users may be ok with this, it becomes troublesome to change your application when you have to constantly consider whether third-party code might be relying on internal code or behaviour which wasn’t explicitly exposed.
How we tried fixing it
When we started looking at developing a sandbox for third-party code our main goal was to encourage developers to write their code in an isolated fashion, relying only on public, documented APIs. We reviewed every app submitted to our marketplace and only allowed the ones that adhered to our guidelines. This caused some frustration for app developers, who felt the process was too restrictive and slow.
However, we didn’t want to get in the way of developers if they needed to do something we didn’t officially support, at their own risk, on their own account. That way we could offer an easy migration path for Zendesk widgets (embedded script tags with third-party code), which relied on the ability to manipulate the DOM.
Where we are now
Here’s how we did it:
In order to benefit from the security restrictions imposed by same-origin policy we host every third-party asset from a CDN on a different domain. This ensures that third-party scripts:
- only have very limited access to Window and Location objects from a different origin
- can only read from or write to their own cookie, localStorage, and IndexedDB
- cannot make HTTP requests to a different origin, unless it supports CORS
For more information see Same-origin policy at MDN.
While scripts from different origins are not allowed to access each other, the window.postMessage API provides a controlled way for cross-origin communication by using message event handlers. Normally, this involves adding an event listener on one origin so you can post a message from the other. If you need two-way communication you need an event listener on both origins so they can post messages to each other. Additionally, for security purposes it is highly recommended to validate the origin of the event from your message handler.
Zendesk apps can include the ZAF SDK library on their website to interact with the Zendesk App Framework from within an iframe. If you’re interested to see how it works the source code is available on GitHub and is documented here.
Another benefit of using iframes is that it encourages third-party developers to host their own code. This may or may not be desirable depending on your use case, but the main advantage is that it gives developers the ability to use whichever technology they want on the client and server side.
When including an external page within an iframe it is important to provide a way for the external server to validate that the request comes from your application. This allows the third-party code to lookup data associated with the user and also prevents leakage of privileged information to unauthenticated requests.
Zendesk apps can opt in to the “signed urls” authentication feature. When this feature is on apps receive a POST request with a JSON Web Token (JWT), which can be validated against a public key that is specific to the app. This allows an app developer to validate that a request originates from a legitimate Zendesk instance. It also allows apps to lookup values associated with a Zendesk account or a particular Zendesk agent within their internal database, while preventing information leakage to unauthenticated third-parties. For more information on how we do this see our public documentation for Using Signed URLs.
Google Caja was initially evaluated as a potential solution for us, but its future seemed uncertain. Also, Caja would have required more fundamental changes than we felt necessary, as well as complicating backwards-compatibility. Caja however, does remain a source of design inspiration for high-level APIs.
Web Components, once standardised, may provide a great alternative to iframes for sandboxing untrusted code. The current proposal includes many of the benefits of iframes, such as DOM and global scope isolation with added benefits, such as not requiring a separate origin and simplified integration with the host page. For more information about Web Components see Web Components at MDN.
Moving to an iframe based architecture for sandboxing apps was a major step forward for us to protect our users and also provide more flexibility for app developers. Although it adds some overhead and complexity to how your developers interact with your application, iframes come with extra security features provided by the browser, improved stability and flexibility for developers.