36

I am developing a Chrome extension which makes requests from certain websites to an API I control. Until Chrome 73, the extension worked correctly. After upgrading to Chrome 73, I started getting the following error:

Cross-Origin Read Blocking (CORB) blocked cross origin response http://localhost:3000/api/users/1 with MIME type application/json

According to Chrome's documentation on CORB, CORB will block the response of a request if all of the following are true:

  1. The resource is a "data resource". Specifically, the content type is HTML, XML, JSON

  2. The server responds with an X-Content-Type-Options: nosniff header, or if this header is omitted, Chrome detects the content type is one of HTML, XML, or JSON from inspecting the file

  3. CORS does not explicitly allow access to the resource

Also, according to "Lessons from Spectre and Meltdown" (Google I/O 2018), it seems like it may be important to add mode: cors to fetch invocations, i.e., fetch(url, { mode: 'cors' }).

To try to fix this, I made the following changes:

First, I added the following headers to all responses from my API:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Origin: https://www.example.com

Second, I updated my fetch() invocation on the extension to look like this:

fetch(url, { credentials: 'include', mode: 'cors' })

However, these changes didn't work. What can I change to make my request not be blocked by CORB?

wOxxOm
  • 65,848
  • 11
  • 132
  • 136
Ceasar
  • 22,185
  • 15
  • 64
  • 83
  • 2
    See the solutions in an extensions-specific Google's article, which is [different](https://www.chromium.org/Home/chromium-security/extension-content-script-fetches). – wOxxOm Mar 18 '19 at 05:12
  • Perfect thank you. Can you make your comment an answer so I can accept it? – Ceasar Mar 18 '19 at 05:20
  • 1
    I think it might be better if you post an answer - maybe with some additional details you deem relevant - since you know more about this. I only knew about the article, not the specifics. – wOxxOm Mar 18 '19 at 05:23
  • 1
    See also https://stackoverflow.com/questions/55153960/how-can-i-remove-the-corb-warning and https://stackoverflow.com/questions/55153888/ajax-call-bug-with-chrome-new-version-73-0-3683-75 – sideshowbarker Mar 18 '19 at 06:19
  • 3
    Though using a background page is enough and appropriate to fix the issue, I'm still confused why Chrome blocked my requests from the extension. The "Changes to Cross-Origin Requests in Chrome Extension Content Scripts" articles write, "To mitigate these concerns, future versions of Chrome will limit content scripts to the same fetches that the page itself can perform." That suggest to me that cross-origin requests are still possible from an extension, but they have to follow CORS. Shouldn't my request have succeeded then since I added the CORS headers to my responses? – Ceasar Mar 18 '19 at 17:48
  • 1
    I'd also be interested in an answer to that. Chrome 73 with the NetworkService enabled is seemingly just not making CORS preflight requests for xhr requests made from a content script, even if the request requires CORS and would trigger a preflight request if made from the host page. It is possible that's a Chrome bug? Per the docs their intention is to make content scripts "subject to the same request rules as the page they are running within". If x-origin requests made from a page trigger a preflight but ones from a content script do not, that seems like it breaks that intention – Simon Woolf Mar 19 '19 at 11:41
  • You are not the only one who read https://www.chromium.org/Home/chromium-security/extension-content-script-fetches (several times) and didn't understand what they were trying to say. What's the difference between changes in Chrome 73 and Chrome 85? Chrome 73: "Chrome removed the ability to make cross-origin requests in content scripts". Chrome 85: "Chrome removed the ability to bypass CORS in cross-origin requests from content scripts". Don't these two phrases mean the same? – traxium Oct 01 '20 at 11:34
  • I suspect the reason for the change was to protect the main page logic from being hijacked. As for Extensions, they are dangerous because there is no security sandbox once inside them to access the full hard drive of the user. Hence why I turn off all Extensions and don't fully trust them. – justdan23 Jul 26 '21 at 21:58

3 Answers3

25

Based on the examples in "Changes to Cross-Origin Requests in Chrome Extension Content Scripts", I replaced all invocations of fetch with a new method fetchResource, that has a similar API, but delegates the fetch call to the background page:

// contentScript.js
function fetchResource(input, init) {
  return new Promise((resolve, reject) => {
    chrome.runtime.sendMessage({input, init}, messageResponse => {
      const [response, error] = messageResponse;
      if (response === null) {
        reject(error);
      } else {
        // Use undefined on a 204 - No Content
        const body = response.body ? new Blob([response.body]) : undefined;
        resolve(new Response(body, {
          status: response.status,
          statusText: response.statusText,
        }));
      }
    });
  });
}

// background.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  fetch(request.input, request.init).then(function(response) {
    return response.text().then(function(text) {
      sendResponse([{
        body: text,
        status: response.status,
        statusText: response.statusText,
      }, null]);
    });
  }, function(error) {
    sendResponse([null, error]);
  });
  return true;
});

This is the smallest set of changes I was able to make to my app that fixes the issue. (Note, extensions and background pages can only pass JSON-serializable objects between them, so we cannot simply pass the Fetch API Response object from the background page to the extension.)

Background pages are not affected by CORS or CORB, so the browser no longer blocks the responses from the API.

Warning! The URL must be added in manifest.json:

  • For ManifestV3 in "host_permissions": ["*://*.example.com/"]
  • For ManifestV2 in "permissions": ["*://*.example.com/"]
wOxxOm
  • 65,848
  • 11
  • 132
  • 136
Ceasar
  • 22,185
  • 15
  • 64
  • 83
  • 3
    I'm not sure if using `response.text()`, `new Blob([response.body])` are the most faithful way to rebuild the Response object, but it works in my application where I'm only dealing with JSON responses. I'm also not sure how to pass the headers through. – Ceasar Mar 18 '19 at 07:12
  • 3
    What can we do about third-party components that make their own API calls though :/ – Roman Scher Mar 22 '19 at 15:51
  • 1
    Nice, copy and pastable code. You should accept this as the answer btw. – Michael Yaworski May 31 '19 at 03:18
  • Dunno why, but since Chrome 81 this seems not to be the case. Background scripts seem to be prohibited from making `fetch` calls by the CORS – avalanche1 Apr 22 '20 at 16:22
  • With this change, I'm getting this error: Unchecked runtime.lastError: The message port closed before a response was received. Any thoughts how to fix this? – Persistent Plants Sep 02 '20 at 03:43
  • you can get headers using `Object.fromEntries(response.headers)` – Bouh May 31 '23 at 21:15
10

See https://www.chromium.org/Home/chromium-security/extension-content-script-fetches

To improve security, cross-origin fetches from content scripts are disallowed in Chrome Extensions since Chrome 85. Such requests can be made from extension background script instead, and relayed to content scripts when needed.

You can do that to avoid Cross-Origin.

Old content script, making a cross-origin fetch:

var itemId = 12345;
var url = "https://another-site.com/price-query?itemId=" +
         encodeURIComponent(request.itemId);
fetch(url)
  .then(response => response.text())
  .then(text => parsePrice(text))
  .then(price => ...)
  .catch(error => ...)

New content script, asking its background page to fetch the data instead:

chrome.runtime.sendMessage(
    {contentScriptQuery: "queryPrice", itemId: 12345},
    price => ...);

New extension background page, fetching from a known URL and relaying data:

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    if (request.contentScriptQuery == "queryPrice") {
      var url = "https://another-site.com/price-query?itemId=" +
              encodeURIComponent(request.itemId);
      fetch(url)
          .then(response => response.text())
          .then(text => parsePrice(text))
          .then(price => sendResponse(price))
          .catch(error => ...)
      return true;  // Will respond asynchronously.
    }
  });

Allow the URL in manifest.json (more info):

  • ManifestV2 (classic): "permissions": ["https://another-site.com/"]
  • ManifestV3 (upcoming): "host_permissions": ["https://another-site.com/"]
wOxxOm
  • 65,848
  • 11
  • 132
  • 136
李方郑
  • 149
  • 5
-2

Temporary solution: disable CORB with run command browser --disable-features=CrossSiteDocumentBlockingAlways,CrossSiteDocumentBlockingIfIsolating

Example run command on Linux.

For Chrome:

chrome %U --disable-features=CrossSiteDocumentBlockingAlways,CrossSiteDocumentBlockingIfIsolating

For Chromium:

chromium-browser %U --disable-features=CrossSiteDocumentBlockingAlways,CrossSiteDocumentBlockingIfIsolating

Similar question.

Source.

Epexa
  • 265
  • 2
  • 8
  • Just make sure you aren't telling users to do this. You want the security on. The only time I ever use this is for dev testing while someone else is working to resolve the CORB issues at the same time. – justdan23 Jul 26 '21 at 22:03