External Link Security
Links that open in a new tab using target='_blank' must include rel='noopener noreferrer' to prevent the opened page from accessing the opener's window context.
- Always add `rel="noopener noreferrer"` to any `<a target="_blank">` link
- `noopener` prevents the new tab from accessing `window.opener` and redirecting your page
- `noreferrer` additionally suppresses the `Referer` header sent to the destination site
- Modern browsers (Chrome 88+, Firefox 79+) implicitly add `noopener` for cross-origin `_blank` links, but explicit markup is required for older browsers and same-origin links
- ESLint rule `jsx-a11y/anchor-is-valid` and `eslint-plugin-security` can enforce this automatically
Rule Details
When a link opens a new browser tab with target="_blank", the new page receives a reference to the opener's window object via window.opener. A malicious destination can exploit that behavior in a reverse tabnapping (opens in new tab) flow unless you explicitly add rel="noopener" (opens in new tab) or rel="noreferrer" (opens in new tab).
Code Example
1. User is on bank.example.com
2. User clicks a link to malicious.com (opens in new tab)
3. malicious.com runs: window.opener.location = 'https://fake-bank.example.com/login'
4. User switches back to the original tab
5. Tab now shows the phishing site — user thinks the bank session expired and types credentialsWhy It Matters
A malicious site opened via target='_blank' can use window.opener.location to silently redirect your original tab to a phishing page — the user switches back and sees a fake login screen on what appears to be your domain.
The Fix
❌ Vulnerable to reverse tabnapping
<a href="https://example.com" target="_blank">Visit Example</a>
✅ Safe — no access to window.opener
<a href="https://example.com" target="_blank" rel="noopener noreferrer">Visit Example</a>React / JSX
❌ Vulnerable
<a href="https://external.com" target="_blank">External Link</a>
✅ Safe
<a href="https://external.com" target="_blank" rel="noopener noreferrer">
External Link
</a>Understanding the rel Values
| Value | What it does |
|---|---|
noopener | Sets window.opener to null in the new tab — the opened page cannot access the original window |
noreferrer | Suppresses the Referer HTTP header sent to the destination; also implies noopener |
| Both together | Maximum privacy + security: no opener access, no referrer sent |
Recommendation: Use both noopener noreferrer for external links. Use noopener alone if you need the referrer for analytics on same-origin links.
Browser Behavior
As of Chrome 88 and Firefox 79, cross-origin target="_blank" links implicitly get noopener behavior. However:
- Same-origin
_blanklinks are not implicitly protected - Older browsers still require the explicit attribute
noreferreris never added implicitly
For these reasons, always add the attribute explicitly.
Automated Enforcement
ESLint (React projects)
pnpm add -D eslint-plugin-react// .eslintrc.json
{
"rules": {
"react/jsx-no-target-blank": ["error", {
"allowReferrer": false,
"enforceDynamicLinks": "error"
}]
}
}Finding Violations in Existing Code
# Search HTML files
grep -rn 'target="_blank"' ./src --include="*.html" | grep -v 'new-tab'
# Search JSX/TSX files
grep -rn 'target="_blank"' ./src --include="*.jsx" --include="*.tsx" | grep -v 'new-tab'Internal Links
Internal links (same domain) are generally safe to open in new tabs without noopener because you control both pages. However, adding it is still a best practice for consistency:
<!-- Internal link — window.opener risk is lower, but still good practice -->
<a href="/docs/guide" target="_blank" rel="noopener">Open Docs</a>If you intentionally need the opened page to communicate back with the opener via window.opener (e.g., OAuth popup flows), omit noopener. This is a legitimate use case — document it clearly in code comments.
Exceptions
- A weaker form control is only acceptable when the business requirement and compensating controls are documented explicitly.
- If the flow is already transport-insecure, inaccessible, or externally embedded in a way that changes the threat model, fix that stronger issue first.
- False positives are common on demo, sandbox, or intentionally constrained flows, but they should still be bounded and clearly labeled.
Verification
Automated Checks
- Test the affected flow in a production-like environment, not just local development.
- Document any intentional exceptions explicitly.
Manual Checks
- Inspect the final HTTP response or browser behavior to confirm the control is actually enforced.
- Verify third-party integrations or embeds still work after the restriction is applied.
Use with AI
Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.
Check
Verify implementation
Find all anchor elements with target='_blank' in the HTML and JSX source. Verify each one includes rel='noopener noreferrer' (or at minimum rel='noopener'). Flag any external links opening in a new tab that are missing this attribute.
Fix
Auto-fix issues
Add rel='noopener noreferrer' to all <a target='_blank'> elements. In React/JSX this is rel="noopener noreferrer". Configure an ESLint rule to prevent this from recurring.
Explain
Learn more
Explain the reverse tabnapping attack enabled by window.opener, how rel='noopener' and rel='noreferrer' prevent it, and the difference between the two values.
Review
Code review
Review server config, headers, forms, and integration points related to External Link Security. Flag exact responses, cookies, or browser behaviors that violate the rule, and verify them against the effective production-like response.