skip to Main Content

In my application, a user can load custom content such as a third-party hosted image. I have specific goals (for security):

  1. Load the image in a sandboxed iframe and "transfer" the image to the "host". This allows the "host" to avoid needing to define a CSP with image-src for each possible user-uploaded image. It also allows the browser to handle things like CSP validation for the image source and then the host can trust a data URL of the image.
  2. Keep the "host" CSP restricted to only allow iframes from our "sandbox" iframe.
  3. Keep the "sandbox" CSP restricted for user-defined content. E.g. using img-src to only allow images from that image’s host source.

In order to "transfer" the image to the host, I need a blob/data URL/object URL. I’ve tried the following solutions:

  1. Use fetch() in the "sandbox" to make an HTTP request for the image URL and get the blob. This is treated as connect-src rather than img-src and "opens up" security problems.
  2. Create an <img> with the source of the image, dynamically create a <canvas>, use canvas.getContext('2d').drawImage(img, 0, 0) (and/or related APIs), and then finally use canvas.toBlob(). This works great for JPEGs and PNGs, but only grabs the first frame for GIFs and doesn’t include the raw source for an SVG.

Ideally, I’m looking for an image-oriented API (so that img-src is respected). For example:

  • Telling fetch() to expect an image?
  • Using an API like createImageBitmap() from the <img> but with all the data in the loaded image.
  • Using an API directly on <img> to "transfer" it to a different context (e.g. "sandbox" iframe to "host").

Is this possible?

2

Answers


  1. I think you are looking for post message.

    https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage

    this is the way you can send data between an iframe an host.
    send a "retrieve image" message to the iframe. have the iframe fetch() the image blob then send the response.blob() back to the host

    // send a message to the iframe to request the image
    function sendMessage() {
        const message = 'retrieve image'
        const iframe = document.querySelector("iframe");
        iframe.contentWindow.postMessage(message, "*");
    }
    
    // in the iframe receive the message
    window.addEventListener('message', receiveMessage, false);
    
    // in the iframe handle featching the image and sending a message back
    function receiveMessage(event) {
      if (event.origin === 'https://allowedoroigin.com') {
        const imageBlob = '[imageData]' // fetch the image and get the blob
        window.parent.postMessage(message, imageBlob);
      }
    }
    
    // back in the parent frame recieve and handle the message 
    window.addEventListener('message', receiveMessage, false);
    

    disclaimer: this is a gist not full code, you need to put the peices in the right places and handle fetching the image, use a standard fetch and run the blob() method on the result.

    Login or Signup to reply.
  2. I might be missing something, but here are some thoughts of mine. It feels like a situation where a backend for frontend can assist the browser-based app and enable an optimal solution that reduces security compromises.

    EXAMPLE

    If your app runs at https://www.example.com, it might process an image request like this, by calling a utility API that does the work of updating your web host with the real image:

    POST https://api.example.com/images
    
    {
      "src": https://somesite.con/someimage.gif
    }
    

    This might remove the need for iframes, and your CSP could be pretty strict:

    let policy = "default-src 'none';";
    policy += " script-src 'self';";
    policy += " connect-src 'self' https://api.example.com;";
    policy += " img-src 'self';";
    policy += " style-src 'self';";
    policy += " object-src 'none';";
    policy += " frame-ancestors 'none';";
    

    DEPLOYMENT VARIATIONS

    Logically the BFF is a separate component, though it might be deployed in a couple of ways:

    SAME SITE: The browser might need to use CORS when calling the BFF if it uses a different type of deployment to the web host. Requests from the browser to the BFF might use cookie credentials.

    SAME ORIGIN: Both the web host and BFF could be deployed behind a reverse proxy like nginx and use paths under the https://www.example.com base URL. This would further simplify the CSP.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search