ASVS 5.0 requirement V3.4.8 says every HTTP response that starts a document render, such as text/html, must carry a Cross-Origin-Opener-Policy header with same-origin or, where needed, same-origin-allow-popups. It is a Level 3 requirement.
Why it matters
When your page opens another window, or is opened by one, the two can hold references to each other's window object. Another origin that gets such a reference can navigate your page (tabnabbing) or infer things about it, for example how many frames it contains. COOP puts your page in its own browsing context group, which cuts those links.
How to implement it
Cross-Origin-Opener-Policy: same-origin
add_header Cross-Origin-Opener-Policy "same-origin" always;
app.use(helmet.crossOriginOpenerPolicy({ policy: "same-origin" }));
Use same-origin-allow-popups if your page opens popups to other origins and needs to keep a reference, as in some payment or sign in flows. Expect same-origin to break window.opener flows, so test OAuth popups first.
How to test it
curl -sI https://app.example.com/ | grep -i cross-origin-opener-policy
Then open the site from a page on another origin using window.open and check that opener is null on your side.
Common mistakes
Setting it only on the homepage. Every HTML response needs it.
Breaking sign in popups. Pick the allow popups variant when required.
Forgetting noopener on links. Use rel="noopener" as well.
Quick check
Does COOP replace rel noopener?
No, they complement each other. COOP is set by the destination, noopener by the link.
Sources
- OWASP ASVS 5.0, requirement V3.4.8
- MDN: Cross-Origin-Opener-Policy