Safari COOP and window.open don't work even on same origin

294 views Asked by At

I am setting the Cross-Origin-Opener-Policy (COOP) to 'same-origin' and I use window.open to open a window, while being within the same origin (user is located at https://example.com/here):

// within a user-invoked event
const childWindow = window.open('https://example.com/there', 'child-window')

This works as expected on Chrome and FF, where I have knowledge about the child window's location and closed state and the child can access window.opener.

However, on Safari Desktop (and only there), it behaves as if the sites don't have the same origin:

childWindow.closed // true

in the popup:

window.opener // null

Setting the COOP header to same-origin-allow-popups won't change anything.

Is this expected behaviour and I simply misunderstood the whole concept or is Safari doing things it's own way (hello IE...)?

Edit/note:

Setting COOP to unsafe-none makes it all work but that's not a real solution, right?

Edit 2:

System Info

MacOS 12.4 Safari 15.5 (17613.2.7.1.8)

Header info

Summary
URL: https://example.com/here
Status: 200
Source: Service Worker

Request
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15

Response
Content-Type: text/html; charset=utf-8
Content-Security-Policy: upgrade-in-secure-requests;default-src 'self';script-src 'self' 'unsafe-eval' 'sha256-<hereIsAHash>' 'sha256-<hereIsAnotherHash>';child-src 'self';connect-src 'self' https://example.com wss://example.com;font-src 'self' data:;form-action 'self';frame-ancestors 'self';frame-src *;img-src 'self' data: blob:;manifest-src 'self';media-src 'self';object-src 'self';sandbox allow-same-origin allow-downloads allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-scripts allow-forms allow-modals allow-presentation;style-src 'self' 'unsafe-inline';worker-src 'self' blob:;base-uri 'self';script-src-attr 'none';upgrade-insecure-requests
X-XSS-Protection: 0
Cross-Origin-Resource-Policy: cross-origin
Content-Encoding: gzip
Referrer-Policy: no-referrer
Date: Fri, 18 Nov 2022 08:46:17 GMT
Cross-Origin-Opener-Policy: same-origin
X-DNS-Prefetch-Control: off
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Vary: Accept-Encoding
Server: nginx/1.21.6
x-download-options: noopen
x-permitted-cross-domain-policies: none
expect-ct: max-age=604800, enforce
Strict-Transport-Security: max-age=15552000; includeSubDomains, max-age=31536000
origin-agent-cluster: ?1

Helmet config

const self = '\'self\''
const data = 'data:'
const blob = 'blob:'
const unsafeEval = '\'unsafe-eval\''
const unsafeInline = '\'unsafe-inline\''
const connectSrc = ['https://example.com', 'wss://example.com']
const externalHostUrls = ['https://foobar.com']

export const helmetOptions = {
    crossOriginEmbedderPolicy: false,
    crossOriginOpenerPolicy: { policy: 'same-origin' },
    crossOriginResourcePolicy: { policy: 'cross-origin' },
    contentSecurityPolicy: {
      blockAllMixedContent: true,
      directives: {
        upgradeInSecureRequests: [],
        defaultSrc: [self],
        scriptSrc: [
          self,
          // we use hashes to allow unsafeEval when using dynamic imports
          unsafeEval,
          `'sha256-${hashes[0]}'`,
          `'sha256-${hashes[1]}'`
        ],
        childSrc: [self],
        connectSrc: connectSrc.concat(externalHostUrls),
        fontSrc: [self, data],
        formAction: [self],
        frameAncestors: [self],
        frameSrc: ['*'],
        imgSrc: [self, data, blob].concat(externalHostUrls),
        manifestSrc: [self],
        mediaSrc: [self],
        objectSrc: [self],
        sandbox: [
          'allow-same-origin',
          'allow-downloads',
          'allow-orientation-lock',
          'allow-pointer-lock',
          'allow-popups',
          'allow-popups-to-escape-sandbox',
          'allow-scripts',
          'allow-forms',
          'allow-modals',
          'allow-presentation'
        ],
        styleSrc: [self, unsafeInline],
        workerSrc: [self, blob]
      }
    },
    strictTransportSecurity: {
      maxAge: 15552000,
      includeSubDomains: true,
      preload: false
    },
    referrerPolicy: {
      policy: 'no-referrer'
    },
    expectCt: {
      enforce: true,
      maxAge: 604800
    },
    frameguard: {
      action: 'sameorigin'
    },
    dnsPrefetchControl: {
      allow: false
    },
    permittedCrossDomainPolicies: {
      permittedPolicies: 'none'
    },
    hidePoweredBy: true
  }
}

Version: [email protected]

1

There are 1 answers

1
ymz On

It is great that you added the helmet config into your question's edit - that truly helps!

My theory

Relying on MDN the upgradeInSecureRequests is deprecated and comes with several wranings. From the docs:

Warning: This directive is marked as obsolete in the specification: all mixed content is now blocked if it can't be autoupgraded.

The fact that the compatibility table (at the bottom of the page) states that there is no clear evidence for safari support (rest of browsers have more concrete data) only makes it a strong suspect

I suggest that you set this setting in your helmet config to false and test it on safari. This should not effect other browsers