Private Network Access: introducing preflights

Titouan Rigoudy
Titouan Rigoudy
Yifan Luo
Yifan Luo

Updates

  • July 7, 2022: Updated current status and added IP address space definition.
  • April 27, 2022: Updated timeline announcement.
  • March 7, 2022: Announced rollback after issues were discovered in Chrome 98.

Introduction

Chrome is deprecating direct access to private network endpoints from public websites as part of the Private Network Access (PNA) specification.

Chrome will start sending a CORS preflight request ahead of any private network request for a subresource, which asks for explicit permission from the target server. This preflight request will carry a new header, Access-Control-Request-Private-Network: true, and the response to it must carry a corresponding header, Access-Control-Allow-Private-Network: true.

The aim is to protect users from cross-site request forgery (CSRF) attacks targeting routers and other devices on private networks. These attacks have affected hundreds of thousands of users, allowing attackers to redirect them to malicious servers.

Rollout plan

Chrome will roll this change out in two phases to give websites time to notice the change and adjust accordingly.

  1. In Chrome 104:

    • Chrome experiments by sending preflight requests ahead of private network subresource requests.
    • Preflight failures only display warnings in DevTools, without otherwise affecting the private network requests.
    • Chrome gathers compatibility data and reaches out to the largest affected websites.
    • We expect this to be broadly compatible with existing websites.
  2. In Chrome 113 at the earliest:

    • This will begin only if and when compatibility data indicates that the change is safe enough and we've outreached directly when necessary.
    • Chrome enforces that preflight requests must succeed, otherwise failing the requests.
    • A deprecation trial starts at the same time to allow for websites affected by this phase to request a time extension. The trial will last for at least 6 months.

What is Private Network Access (PNA)

Private Network Access (formerly known as CORS-RFC1918) restricts the ability of websites to send requests to servers on private networks.

Chrome has already implemented part of the specification: as of Chrome 96, only secure contexts are allowed to make private network requests. Refer to our previous blog post for details.

The specification also extends the Cross-Origin Resource Sharing (CORS) protocol so that websites must now explicitly request a grant from servers on private networks before being allowed to send arbitrary requests.

How does PNA classify IP addresses and identify a private network

The IP addresses are classified into three IP address spaces: - public - private - local

Local IP address space contains IP addresses that are either IPv4 loopback addresses (127.0.0.0/8) defined in section 3.2.1.3 of RFC1122 or IPv6 loopback addresses (::1/128) defined in section 2.5.3 of RFC4291.

Private IP address space contains IP addresses that have meaning only within the current network, including 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16 defined in RFC1918, link-local addresses 169.254.0.0/16 defined in RFC3927, unique local IPv6 unicast addresses fc00::/7 defined in RFC4193, link-local IPv6 unicast addresses fe80::/10 defined in section 2.5.6 of RFC4291 and IPv4-mapped IPv6 addresses where the mapped IPv4 address is itself private.

Public IP Address space contains all other addresses not mentioned previously.

A local IP address is considered more private than a private IP address which is considered more private than a public IP address.

Requests are private when a more available network sends a request to a
   less available network.
Relationship between public, private, local networks in Private Network Access (CORS-RFC1918)

Learn more at Feedback wanted: CORS for private networks (RFC1918).

Preflight requests

Background

Preflight requests are a mechanism introduced by the Cross-Origin Resource Sharing (CORS) standard used to request permission from a target website before sending it an HTTP request that might have side effects. This ensures that the target server understands the CORS protocol and significantly reduces the risk of CSRF attacks.

The permission request is sent as an OPTIONS HTTP request with specific CORS request headers describing the upcoming HTTP request. The response must carry specific CORS response headers explicitly agreeing to the upcoming request.

Sequence diagram which represents CORS preflight. An OPTIONS HTTP
   request is sent to the target, which returns a 200 OK. Then the CORS
   request header is sent, returning a CORS response header

What's new in Private Network Access

A new pair of request and response headers is introduced to preflight requests:

  • Access-Control-Request-Private-Network: true is set on all PNA preflight requests
  • Access-Control-Allow-Private-Network: true must be set on all PNA preflight responses

Preflight requests for PNA are sent for all private network requests, regardless of request method and mode. They are sent ahead of requests in cors mode as well as no-cors and all other modes. This is because all private network requests can be used for CSRF attacks, regardless of request mode and whether or not the response contents are made available to the initiator.

Preflight requests for PNA are also sent for same-origin requests, if the target IP address is more private than the initiator. This is unlike regular CORS, where preflight requests are only for cross-origin requests. Preflight requests for same-origin requests guard against DNS rebinding attacks.

Examples

Observable behavior depends on the request's mode.

No-CORS mode

Say https://foo.example/index.html embeds <img src="https://bar.example/cat.gif" alt="dancing cat"/>, and bar.example resolves to 192.168.1.1, a private IP address according to RFC 1918.

Chrome first sends a preflight request:

HTTP/1.1 OPTIONS /cat.gif
Origin: https://foo.example
Access-Control-Request-Private-Network: true

For this request to succeed, the server must respond with:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Private-Network: true

Then Chrome will send the actual request:

HTTP/1.1 GET /cat.gif
...

To which the server can respond normally.

CORS mode

Say https://foo.example/index.html runs the following code:

await fetch('https://bar.example/delete-everything', {
  method: 'PUT',
  credentials: 'include',
})

Again, say bar.example resolves to 192.168.1.1.

Chrome first sends a preflight request:

HTTP/1.1 OPTIONS /delete-everything
Origin: https://foo.example
Access-Control-Request-Method: PUT
Access-Control-Request-Credentials: true
Access-Control-Request-Private-Network: true

For this request to succeed, the server must respond with:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Credentials: true
Access-Control-Allow-Private-Network: true

Then Chrome will send the actual request:

HTTP/1.1 PUT /delete-everything
Origin: https://foo.example

To which the server can respond per usual CORS rules:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://foo.example

How to know if your website is affected

Starting in Chrome 104, if a private network request is detected, a preflight request will be sent ahead of it. If this preflight request fails, the final request will still be sent, but a warning will be surfaced in the DevTools issues panel.

A failed preflight request warning in the Devtools Issues panel. This states:
   ensure private network requests are only made to resources that allow them,
   along with details about the specific request and listed affected resources.

Affected preflight requests can also be viewed and diagnosed in the network panel:

A failed preflight request in the DevTools Network panel for localhost
   gives a 501 status.

If your request would have triggered a regular CORS preflight without Private Network Access rules, then two preflights may appear in the network panel, with the first one always appearing to have failed. This is a known bug, and you can safely ignore it.

A spurious failed preflight request ahead of a successful preflight in
   the DevTools Network panel.

To review what happens if preflight success was enforced, you can pass the following command-line argument, starting in Chrome 98:

--enable-features=PrivateNetworkAccessRespectPreflightResults

Any failed preflight request will result in a failed fetch. This can allow you to test whether your website would work after the second phase of our rollout plan. Errors can be diagnosed in the same way as warnings using the DevTools panels mentioned above.

What to do if your website is affected

When this change rolls out in Chrome 104, it is not expected to break any website. However, we strongly encourage you to update affected request paths to ensure your website keeps running as expected.

There are two solutions available to you:

  1. Handle preflight requests on the server side
  2. Disable PNA checks with enterprise policies

Handle preflight requests server-side

Update the target server of any affected fetches to handle PNA preflight requests. First, implement support for standard CORS preflight requests on affected routes. Then add support for the two new response headers.

When your server receives a preflight request (an OPTIONS request with CORS headers), the server should check for the presence of an Access-Control-Request-Private-Network: true header. If this header is present on the request, the server should examine the Origin header and the request path along with any other relevant information (such as Access-Control-Request-Headers) to ensure the request is safe to allow. Typically, you should allow access to a single origin under your control.

Once your server has decided to allow the request, it should respond 204 No Content (or 200 OK) with the necessary CORS headers and the new PNA header. These headers include Access-Control-Allow-Origin and Access-Control-Allow-Private-Network: true, as well as others as needed.

Refer to the examples for concrete scenarios.

Disable Private Network Access checks using enterprise policies

If you have administrative control over your users, you can disable Private Network Access checks using either of the following policies:

For more information, refer to Understand Chrome policy management.

Give us feedback

If you are hosting a website within a private network that expects requests from public networks, the Chrome team is interested in your feedback and use cases. Let us know by filing an issue with Chromium at crbug.com and set the component to Blink>SecurityFeature>CORS>PrivateNetworkAccess.

What's next

Next up, Chrome will extend Private Network Access checks to cover web workers: dedicated workers, shared workers and service workers. We're tentatively aiming for Chrome 107 to begin showing warnings.

Then, Chrome will extend Private Network Access checks to cover navigations, including iframes and popups. We're tentatively aiming for Chrome 108 to start showing warnings.

In both cases, we will be proceeding cautiously with a similar phased rollout, in order to give web developers time to adjust and estimate compatibility risk.

Acknowledgements

Cover photo by Mark Olsen on Unsplash.