Learn Measure Blog Live About
A screenshot of Python code related to Resource Isolation Policy.

Protect your resources from web attacks with Fetch Metadata

Protect your resources from web attacks with Fetch Metadata

Prevent CSRF, XSSI, and cross-origin information leaks.

Updated
Appears in: Safe and secure

Why should you care about isolating your web resources?

Many web applications are vulnerable to cross-origin attacks like cross-site request forgery (CSRF), cross-site script inclusion (XSSI), timing attacks, cross-origin information leaks or speculative execution side-channel (Spectre) attacks.

Fetch Metadata request headers allow you to deploy a strong defense-in-depth mechanism—a Resource Isolation Policy—to protect your application against these common cross-origin attacks.

It is common for resources exposed by a given web application to only be loaded by the application itself, and not by other websites. In such cases, deploying a Resource Isolation Policy based on Fetch Metadata request headers takes little effort, and at the same time protects the application from cross-site attacks.

Browser compatibility

Fetch Metadata request headers are supported as of Chrome 76 and in other Chromium-based browsers, and are under development in Firefox. See Browser compatibility for up-to-date browser support information.

Background

Many cross-site attacks are possible because the web is open by default and your application server cannot easily protect itself from communication originating from external applications. A typical cross-origin attack is cross-site request forgery (CSRF) where an attacker lures a user onto a site they control and then submits a form to the server the user is logged in to. Since the server cannot tell if the request originated from another domain (cross-site) and the browser automatically attaches cookies to cross-site requests, the server will execute the action requested by the attacker on behalf of the user.

Other cross-site attacks like cross-site script inclusion (XSSI) or cross-origin information leaks are similar in nature to CSRF and rely on loading resources from a victim application in an attacker-controlled document and leaking information about the victim applications. Since applications cannot easily distinguish trusted requests from untrusted ones, they cannot discard malicious cross-site traffic.

Gotchas!

Apart from attacks on resources as described above, window references can also lead to cross-origin information leaks and Spectre attacks. You can prevent them by setting the Cross-Origin-Opener-Policy response header to same-origin.

Introducing Fetch Metadata

Fetch Metadata request headers are a new web platform security feature designed to help servers defend themselves against cross-origin attacks. By providing information about the context of an HTTP request in a set of Sec-Fetch-* headers, they allow the responding server to apply security policies before processing the request. This lets developers decide whether to accept or reject a request based on the way it was made and the context in which it will be used, making it possible to respond to only legitimate requests made by their own application.

Same-Origin

Requests originating from sites served by your own server (same-origin) will continue to work.

A fetch request from https://site.example for the resource https://site.example/foo.json in JavaScript causes the browser to send the HTTP request header "Sec Fetch-Site: same-origin".

Cross-site

Malicious cross-site requests can be rejected by the server because of the additional context in the HTTP request provided by Sec-Fetch-* headers.

An image on https://evil.example that has set the src attribute of an img element to "//site.example/foo.json" causes the browser to send the HTTP request header "Sec-Fetch-Site: cross-site".

Sec-Fetch-Site

Sec-Fetch-Site tells the server which site sent the request. The browser sets this value to one of the following:

  • same-origin, if the request was made by your own application (e.g. site.example)
  • same-site, if the request was made by a subdomain of your site (e.g. bar.site.example)
  • none, if the request was explicitly caused by a user's interaction with the user agent (e.g. clicking on a bookmark)
  • cross-site, if the request was sent by another website (e.g. evil.example)

Sec-Fetch-Mode

Sec-Fetch-Mode indicates the mode of the request. This roughly corresponds to the type of the request and allows you to distinguish resource loads from navigation requests. For example, a destination of navigate indicates a top-level navigation request while no-cors indicates resource requests like loading an image.

Sec-Fetch-Dest

Sec-Fetch-Dest exposes a request's destination (e.g. if a script or an img tag caused a resource to be requested by the browser).

How to use Fetch Metadata to protect against cross-origin attacks

The extra information these request headers provide is quite simple, but the additional context allows you to build powerful security logic on the server-side, also referred to as a Resource Isolation Policy, with just a few lines of code.

Implementing a Resource Isolation Policy

A Resource Isolation Policy prevents your resources from being requested by external websites. Blocking such traffic mitigates common cross-site web vulnerabilities such as CSRF, XSSI, timing attacks, and cross-origin information leaks. This policy can be enabled for all endpoints of your application and will allow all resource requests coming from your own application as well as direct navigations (via an HTTP GET request). Endpoints that are supposed to be loaded in a cross-site context (e.g. endpoints loaded using CORS) can be opted out of this logic.

Step 1: Allow requests from browsers which don't send Fetch Metadata

Since not all browsers support Fetch Metadata, you need to allow requests that don't set Sec-Fetch-* headers by checking for the presence of sec-fetch-site.

All of the following examples are Python code.

if not req['sec-fetch-site']:
return True # Allow this request

Caution: Since Fetch Metadata is only supported in modern browsers, it should be used as a defense-in-depth protection and not as your primary line of defense.

Step 2: Allow same-site and browser-initiated requests

Any requests that do not originate from a cross-origin context (like evil.example) will be allowed. In particular, these are requests that:

  • Originate from your own application (e.g. a same-origin request where site.example requests site.example/foo.json will always be allowed).
  • Originate from your subdomains.
  • Are explicitly caused by a user's interaction with the user agent (e.g. direct navigation or by clicking a bookmark, etc.).
if req['sec-fetch-site'] in ('same-origin', 'same-site', 'none'):
return True # Allow this request
Gotchas!

In case your subdomains are not fully trusted, you can make the policy stricter by blocking requests from subdomains by removing the same-site value.

Step 3: Allow simple top-level navigation and iframing

To ensure that your site can still be linked from other sites, you have to allow simple (HTTP GET) top-level navigation.

if req['sec-fetch-mode'] == 'navigate' and req.method == 'GET'
# <object> and <embed> send navigation requests, which we disallow.
and req['sec-fetch-dest'] not in ('object', 'embed'):
return True # Allow this request
Gotchas!

The logic above protects your application's endpoints from being used as resources by other websites, but will permit top-level navigation and embedding (e.g. loading in an <iframe>). To further improve security, you can use Fetch Metadata headers to restrict cross-site navigations to only an allowed set of pages.

Step 4: Opt out endpoints that are meant to serve cross-site traffic (Optional)

In some cases, your application might provide resources which are meant to be loaded cross-site. These resources need to be exempted on a per-path or per-endpoint basis. Examples of such endpoints are:

  • Endpoints meant to be accessed cross-origin: If your application is serving endpoints that are CORS enabled, you need to explicitly opt them out from resource isolation to ensure that cross-site requests to these endpoints are still possible.
  • Public resources (e.g. images, styles, etc.): Any public and unauthenticated resources that should be loadable cross-origin from other sites can be exempted as well.
if req.path in ('/my_CORS_endpoint', '/favicon.png'):
return True

Caution: Before opting out parts of your application from these security restrictions, make sure they are static and don't contain any sensitive user information.

Step 5: Reject all other requests that are cross-site and not navigational

Any other cross-site request will be rejected by this Resource Isolation Policy and thus protect your application from common cross-site attacks.

Gotchas!

By default, requests violating your policy should be rejected with an HTTP 403 response. But, depending on your use case, you can also consider other actions, such as:

  • Only logging violations. This is especially useful when testing the compatibility of the policy and finding endpoints that might need to be opted out.
  • Modifying the request. In certain scenarios, consider performing other actions like redirecting to your landing page and dropping authentication credentials (e.g. cookies). However, be aware that this could weaken the protections of a Fetch Metadata-based policy.

Example: The following code demonstrates a complete implementation of a robust Resource Isolation Policy on the server or as a middleware to deny potentially malicious cross-site resource requests, while allowing simple navigational requests:

# Reject cross-origin requests to protect from CSRF, XSSI, and other bugs
def allow_request(req):
# Allow requests from browsers which don't send Fetch Metadata
if not req['sec-fetch-site']:
return True

# Allow same-site and browser-initiated requests
if req['sec-fetch-site'] in ('same-origin', 'same-site', 'none'):
return True

# Allow simple top-level navigations except <object> and <embed>
if req['sec-fetch-mode'] == 'navigate' and req.method == 'GET'
and req['sec-fetch-dest'] not in ('object', 'embed'):
return True

# [OPTIONAL] Exempt paths/endpoints meant to be served cross-origin.
if req.path in ('/my_CORS_endpoint', '/favicon.png'):
return True

# Reject all other requests that are cross-site and not navigational
return False

Deploying a Resource Isolation Policy

  1. Install a module like the code snippet from above to log and monitor how your site behaves and make sure the restrictions don't affect any legitimate traffic.
  2. Fix potential violations by exempting legitimate cross-origin endpoints.
  3. Enforce the policy by dropping non-compliant requests.

Identifying and fixing policy violations

It's recommended that you test your policy in a side-effect free way by first enabling it in reporting mode in your server-side code. Alternatively, you can implement this logic in middleware, or in a reverse proxy which logs any violations that your policy might produce when applied to production traffic.

From our experience of rolling out a Fetch Metadata Resource Isolation Policy at Google, most applications are by default compatible with such a policy and rarely require exempting endpoints to allow cross-site traffic.

Enforcing a Resource Isolation Policy

After you've checked that your policy doesn't impact legitimate production traffic, you're ready to enforce restrictions, guaranteeing that other sites will not be able to request your resources and protecting your users from cross-site attacks.

Caution: Make sure that you reject invalid requests before running authentication checks or any other processing of the request to prevent revealing sensitive timing information.

Further reading

Last updated: Improve article