Web Security & Browser Isolation • Sensei Learning System
A new tab you just opened quietly holds a handle on the tab that opened it — and that handle is writable, not readable. That asymmetry is the whole attack.
When you open a link with target="_blank", the browser hands the new page a window.opener reference back to the page that launched it. Same-origin policy still blocks the new page from reading your DOM — but it does not block it from steering you.
Cross-origin scripts in the new tab cannot touch your cookies, tokens, or localStorage. What they can do is write to window.opener.location — silently navigating your original tab to a look-alike phishing page while your eyes stay on the new one. That redirect-the-parent trick is reverse tabnabbing.
target="_blank" (and window.open()) may hand the new page a live reference back to its parent. That reference is the attack surface.
Same-origin policy blocks every read of your DOM, cookies, tokens and storage. Writing opener.location is allowed — control without data access.
rel="noopener" nulls the handle; rel="noreferrer" also hides the Referer header and implies noopener; COOP severs opener links at document level.
Chrome 88+, Firefox 79+, Safari 12.1+ default plain target="_blank" anchors to implicit noopener — the live risk concentrates in window.open(), older browsers, and webviews.
target="_blank". Tab B opens, and the browser wires window.opener in B to point at A.window.opener.location = "https://evil.example/login". Writing a location is navigation, not a data read — and it’s allowed.rel="noopener" makes window.opener null; rel="noreferrer" adds Referer suppression; COOP cuts opener relationships document-wide.Host two pages on different origins. From the opened page, run window.opener.location = "https://evil.example" and watch the parent tab navigate — that proves the control.
Then try window.opener.document.cookie: the same-origin error is the point. You have control without read access. Re-run with rel="noopener" and confirm window.opener is now null.
The usual warning — “a new page gets partial, potentially malicious access to your original page” — is true only in a narrow sense:
“Access” here means navigation control plus a few non-readable window methods. It is not data access: a cross-origin opener cannot read your cookies, auth tokens, localStorage, or DOM content.
And it is not a universal live hole — modern desktop browsers auto-apply noopener to plain target="_blank" anchors. The risk concentrates in window.open() calls, older browsers, and embedded webviews (apps, Electron shells, in-house browsers).
Credential-phishing redirects. The classic payload: swap your original tab onto a clone of the login page you were about to use. The user never notices the swap — they just land on a fake page that looks exactly like home.
window.open() is the real hole. The implicit-noopener default applies to plain anchor links; a hand-rolled window.open(url, "_blank") in your own site’s JS can still create a live opener on browsers that don’t cover that path — review open-handling code, not just anchor HTML.
Embedded webviews. In-app browsers, Electron, and kiosk webviews are common homes for older engine versions where the implicit-noopener default never landed — the attack surface lives inside apps, not on the open web.
Document-level defense. Cross-Origin-Opener-Policy (with COEP for isolation) lets a site sever opener relationships for its own documents — the header-level cousin of the per-anchor rel fixes.
location do, and why isn’t it classified as a data read under same-origin policy?
rel="noreferrer" give you that rel="noopener" alone does not?
One of them is a navigation fix; the other also touches a network header.
window.opener null?
The cookie read fails for a reason; the anchor attribute that nulls the handle is two letters short of a word.