Regular web pages can use the [`fetch()`](https://developer.mozilla.org/docs/Web/API/Fetch_API) or [`XMLHttpRequest`](https://www.w3.org/TR/XMLHttpRequest/) APIs to send and receive data from remote
servers, but they're limited by the [same origin policy](https://en.wikipedia.org/wiki/Same_origin_policy). [Content scripts](https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts) initiate requests
on behalf of the web origin that the content script has been injected into and therefore content
scripts are also subject to the [same origin policy](https://en.wikipedia.org/wiki/Same_origin_policy). Extension origins aren't so limited. A script
executing in an extension service worker or foreground tab can talk to remote servers outside of
its origin, as long as the extension requests [host permissions](https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions#host-permissions).

## Extension origin

Each running extension exists within its own separate security origin. Without requesting additional
privileges, the extension can call `fetch()` to get resources within its installation. For
example, if an extension contains a JSON configuration file called `config.json`, in a
`config_resources/` folder, the extension can retrieve the file's contents like this:

    const response = await fetch('/config_resources/config.json');
    const jsonData = await response.json();

If the extension attempts to request content from a security origin other than its own, say https://www.google.com, this will be treated as a [cross-origin request](https://developer.chrome.com/docs/extensions/develop/concepts/developer.mozilla.org/docs/Web/HTTP/CORS) request unless the extension has [host permissions](https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions#host-permissions). Cross-origin requests are always treated as such in content scripts, even if the extension has host permissions.

## Request cross-origin permissions

To request access to remote servers outside an extension's origin, add hosts, [match patterns](https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns),
or both to the [host_permissions](https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions) section of the [manifest](https://developer.chrome.com/docs/extensions/reference/manifest) file.

    {
      "name": "My extension",
      ...
      "host_permissions": [
        "https://www.google.com/"
      ],
      ...
    }

Cross-origin permission values can be fully qualified host names, like these:

- "https://www.google.com/"
- "https://www.gmail.com/"

Or they can be match patterns, like these:

- "https://\*.google.com/"
- "https://\*/"

A match pattern of "https://\*/" allows HTTPS access to all reachable domains. Note that here, match
patterns are similar to [content script match patterns](https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns), but any path information following the
host is ignored.

Also note that access is granted both by host and by scheme. If an extension wants both secure and
non-secure HTTP access to a given host or set of hosts, it must declare the permissions separately:

    "host_permissions": [
      "http://www.google.com/",
      "https://www.google.com/"
    ]

## Fetch() vs. XMLHttpRequest()

`fetch()` was created specifically for service workers and follows a broader web trend away from synchronous operations. The `XMLHttpRequest()` API is supported in extensions outside of the service worker, and calling it triggers the extension service worker's fetch handler. New work should favor `fetch()` wherever possible.

## Security considerations

### Avoid cross-site scripting vulnerabilities

When using resources retrieved via `fetch()`, your offscreen document, side panel or popup should be careful not to
fall victim to [cross-site scripting](https://en.wikipedia.org/wiki/Cross-site_scripting). Specifically, avoid using dangerous APIs such as
`innerHTML`. For example:

    const response = await fetch("https://api.example.com/data.json");
    const jsonData = await response.json();
    // WARNING! Might be injecting a malicious script!
    document.getElementById("resp").innerHTML = jsonData;
        ...

Instead, prefer safer APIs that do not run scripts:

    const response = await fetch("https://api.example.com/data.json");
    const jsonData = await response.json();
    // JSON.parse does not evaluate the attacker's scripts.
    let resp = JSON.parse(jsonData);

    const response = await fetch("https://api.example.com/data.json");
    const jsonData = response.json();
    // textContent does not let the attacker inject HTML elements.
    document.getElementById("resp").textContent = jsonData;

### Limit content script access to cross-origin requests

When performing cross-origin requests on behalf of a content script, be careful to [guard against
malicious web pages](https://developer.chrome.com/docs/extensions/develop/security-privacy/stay-secure#content_scripts) that might try to impersonate a content script. In particular, do not allow
content scripts to request an arbitrary URL.

Consider an example where an extension performs a cross-origin request to let a content script
discover the price of an item. One not-so-secure approach would be to have the content script specify
the exact resource to be fetched by the background page.

    browser.runtime.onMessage.addListener(
      function(request, sender, sendResponse) {
        if (request.contentScriptQuery == 'fetchUrl') {
          // WARNING: SECURITY PROBLEM - a malicious web page may abuse
          // the message handler to get access to arbitrary cross-origin
          // resources.
          fetch(request.url)
            .then(response => response.text())
            .then(text => sendResponse(text))
            .catch(error => ...)
          return true;  // Will respond asynchronously.
        }
      }
    );

    browser.runtime.sendMessage(
      {
        contentScriptQuery: 'fetchUrl',
        url: `https://another-site.com/price-query?itemId=${encodeURIComponent(request.itemId)}`
      },
      response => parsePrice(response.text())
    );

In the approach above, the content script can ask the extension to fetch any URL that the extension
has access to. A malicious web page may be able to forge such messages and trick the extension into
giving access to cross-origin resources.

Instead, design message handlers that limit the resources that can be fetched. Below, only the
`itemId` is provided by the content script, and not the full URL.

    browser.runtime.onMessage.addListener(
      function(request, sender, sendResponse) {
        if (request.contentScriptQuery == 'queryPrice') {
          const 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.
        }
      }
    );

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

### Prefer HTTPS over HTTP

Additionally, be especially careful of resources retrieved via HTTP. If your extension is used on a
hostile network, a network attacker (aka a ["man-in-the-middle"](https://en.wikipedia.org/wiki/Man-in-the-middle_attack)) could modify the response
and, potentially, attack your extension. Instead, prefer HTTPS whenever possible.

### Adjust the content security policy

If you modify the default [Content Security Policy](https://developer.chrome.com/docs/extensions/develop/migrate/improve-security#update-csp) for your extension by adding a
`content_security_policy` attribute to your manifest, you'll need to ensure that any hosts to which
you'd like to connect are allowed. While the default policy doesn't restrict connections to hosts,
be careful when explicitly adding either the `connect-src` or `default-src` directives.