12k
All articles

リンクにおける rel='noopener' が不要になった理由

target=_blankリンクでrel=noopenerが不要になった理由、reverse tabnabbingの仕組み、noreferrer・opener・COOPが今も必要な場面を解説。

OpenReplay Team
OpenReplay Team
リンクにおける rel='noopener' が不要になった理由

単純な target="_blank" リンクにおいて、rel="noopener" はもはや冗長です。Chrome、Edge、Firefox、Safari の現行バージョンはすべて noopener の挙動を自動的に適用するため、target="_blank" を指定しただけで window.openernull になります。

それでも手癖で書いてしまう、あるいは付け忘れた 1 つの anchor に対してリンターが警告を出すのを眺めているなら、それはブラウザが何年も前に閉じた穴を塞ぐ作業をしていることになります。本記事では、この属性が防ごうとしていた脆弱性、ブラウザがこの修正をデフォルト化した時期、そして rel が今なお実質的な役割を果たす具体的なケース、すなわち noreferrer(自動化されていない)、rel="opener"(明示的に有効化する)、そしてサイト全体を制御する Cross-Origin-Opener-Policy ヘッダーについて解説します。

要点

  • モダンブラウザでは target="_blank" を指定しただけで window.opener は既に null になるため、手動で rel="noopener" を追加するのは、ブラウザが既に閉じた穴に対する多層防御にすぎません。
  • 暗黙的な noopener は段階的に導入されました(Safari は 2018〜19 年、Firefox 79 は 2020 年半ば、Chromium 88 は 2021 年初頭)。現在は WHATWG HTML 標準の一部です。
  • caniuse.com によると、暗黙的な noopener はグローバルのブラウザ利用シェアの約 95% をカバーしています。
  • noreferrer は暗黙的ではありません。今も Referer ヘッダーを除去し、さらに noopener を暗黙的に含みます。したがって、リファラーのプライバシーを確保したい場合にのみ追加してください。
  • window.opener を再び有効にするには rel="opener" を使い、ドキュメント全体で opener の共有を一箇所で断ち切るには Cross-Origin-Opener-Policy: same-origin を使用します。

元々の問題: リバースタブナビング

ブラウザがデフォルトの挙動を変更する前は、target="_blank" リンクは新しく開かれたページに、それを開いたページへの有効な参照を渡していました。リバースタブナビング(Reverse tabnabbing)はこれを悪用する攻撃です。遷移先のページが window.opener を読み取り、ユーザーが新しいタブに注目している間に、元のタブをフィッシング用のクローンサイトへリダイレクトさせます。Mathias Bynens によるこの問題の定番の解説は率直にまとめています。window.opener が存在する場所ではどこでも、開かれたページは、どちらのページがどのオリジンに属していようと、opener を別の場所へ誘導できるのです。

このエクスプロイトは、開かれたドキュメント内で実行されるわずか 1 行のコードです。

if (window.opener) {
  window.opener.location = 'https://you-re-hacked.com';
}

決定的に重要なのは、これがオリジンを越えて機能する点です。2 つのページが異なるホストから提供されていても、window.opener.location の読み取りと書き込みはブロックされません。したがって同一オリジンポリシーも CORS も、opener をリダイレクトする行為を妨げません。このため、攻撃者が href を制御できる、ユーザー生成コンテンツやサードパーティのリンクをレンダリングする場所(フォーラム、コメント、プロフィール欄など)ではどこでも危険でした。

何が変わったのか: target=“_blank” は今や rel=“noopener” を暗黙的に含む

ブラウザ側がデフォルトを修正しました。<a><area><form> 要素において、target="_blank"自分で rel="noopener" を書いたのと同じ効果を持つようになりました。属性を指定しなくても、開かれたドキュメントの window.openernull を返します。この挙動は WHATWG HTML 仕様に明記されており、ハイパーリンクをたどる際のルールでは、リンクが rel="opener" でオプトアウトしない限り、_blank ターゲットはすべて noopener として扱われます。OWASP も現在では、この標準化されたデフォルトを読者に案内しており、エバーグリーンブラウザではこの攻撃はおおむね封じられたものとして扱っています。

この変更は約 3 年かけて展開されたため、「モダンブラウザはこうする」というのは単一の日付ではなくタイムラインの話です。

エンジン暗黙的 noopener を備えた最初の安定版おおよそのリリース時期
Safari / WebKitSafari 12.1(Tech Preview 68 でプレビュー)2018 年後半〜2019 年
Firefox / GeckoFirefox 792020 年半ば
Chromium(Chrome、Edge)Chrome/Edge 882021 年初頭

これらのバージョンは暗黙的な挙動についてのものであり、rel="noopener" 属性そのものがサポートされた時期ではないことに注意してください。属性のサポートはこれより数年早く実現しており、別のマイルストーンです。caniuse の暗黙的 noopener の対応表では、グローバルサポート率は約 95%、エバーグリーンブラウザは 2018 年頃からカバーされているとされています。残りの割合はわずかですが実在するため、属性を削除する前に自社のアナリティクスを確認してください。特筆すべき非対応環境はレガシーな非 Chromium 版 Edge です。

手書きが不要になったことは、無意味であることと同じではない

rel="noopener" を書くのが冗長になったからといって、rel 属性全体が無意味になるわけではありません。自動化されたのは noopener というキーワードだけです。他のキーワードは今も挙動を変えます。

キーワード機能2026 年に手書きは必要か?
noopener開かれたページの window.opener を null にする不要。target="_blank" で暗黙的に適用される
noreferrerReferer ヘッダーを除去し、さらに noopener を暗黙的に含むリファラーのプライバシーが必要な場合のみ
openerwindow.opener を復元する(明示的に有効化)必要。参照が本当に必要な場合

noreferrer は暗黙的ではありません。今も Referer ヘッダーを抑制するため、遷移元 URL を遷移先に渡したくない場合にのみ追加してください。また、セキュリティ上の利点も無償で付いてきます。noreferrer は opener も null にするため、これと併せて noopener を指定しても何も得られません。つまり、よく見かける rel="noopener noreferrer" の組み合わせはモダンブラウザでは二重に冗長です。noreferrer 単独で両方の懸念をカバーできるからです。

開かれたページに window.opener 参照を保持させる必要が本当にある場合(たとえばメッセージを送り返すポップアップなど)は、rel="opener" で明示的にオプトインしてください。この変更を導入した WebKit のリリースノートも同様に説明しています。安全な挙動が現在のデフォルトであり、rel="opener" はそれを意図的に反転させる手段です。

レガシーサポートに関する率直な注意点を 1 つ。それでも rel="noopener" を追加しておくことは無害です。Chrome の Lighthouse 監査ドキュメントは、属性を明記することで Edge Legacy のような古いエンジンを使い続けているユーザーに対して一定の保護が得られると述べています。モダンブラウザではノイズですが、間違いではありません。

COOP: スケーラブルなサイト全体の制御

ドキュメント全体で window.opener の共有を一箇所で断ち切るには、すべてのリンクに属性を付けるのではなく、Cross-Origin-Opener-Policy: same-origin レスポンスヘッダーを送信します。Cross-Origin-Opener-Policy(COOP)ヘッダーは、新しく開かれたトップレベルドキュメントが自サイトのブラウジングコンテキストグループに参加するか、独自のグループを持つかを決定します。same-origin の下では、クロスオリジンのドキュメントは別のグループに配置され、それらと opener 間の参照は切断されます。これにより opener チャネルが anchor ごとではなく、一度に、中央で閉じられます。

# nginx
add_header Cross-Origin-Opener-Policy "same-origin";
// Express
app.use((req, res, next) => {
  res.set('Cross-Origin-Opener-Policy', 'same-origin');
  next();
});

制約が 1 つあります。COOP は HTTP レスポンスヘッダーとしてのみ配信されます。<meta http-equiv> に相当する手段は存在しないため、インフラ上でレスポンスヘッダーを設定できない場合、COOP を適用することはできません。現行ブラウザで広くサポートされており、多層防御として有効化する価値があります。実際のユーザーが外部の target="_blank" リンクをクリックした際のセッションリプレイは、元のタブが遷移していないことを確認する実用的な手段であり、予期しない遷移の報告をユーザーが使っていたまさにそのブラウザ上で再現するのにも役立ちます。

結論: 今どうすべきか

モダンブラウザを対象とする新しいコードでは、rel="noopener" を手動で追加しないでください。ブラウザが設定してくれます。react/jsx-no-target-blank のように、すべての target="_blank" に対して付与を強制する lint ルールは安全に緩和できます。Edge Legacy やその他 2021 年以前のエンジンをカバーする必要がある場合にのみ、ルールを有効なままにしてください。Referer ヘッダーを抑制したい場合に限り rel="noreferrer" を追加します。opener 参照を戻す必要があるという稀なケースでは rel="opener" を使用します。スケーラブルでドキュメント全体に及ぶ保証を得るには、Cross-Origin-Opener-Policy: same-origin を送信してください。

指摘しておくべき点として、以前のガイダンス(OpenReplay 自身のすべてのリンクに rel="noreferrer noopener" を推奨する古い記事を含む)は、noopener を常に書かなければならないものとして扱っており、暗黙的なデフォルトを考慮していません。これは、ブラウザが先に進んだ後も業界が長く保持し続けた習慣を反映したものです。2026 年時点の正確な立場はより限定的です。noopener は現在デフォルトであるため手書きは不要になった一方、noreferrerrel="opener"、COOP はそれぞれ依然として明確な役割を果たします。これらは意図的に使い分け、残りはブラウザに任せましょう。

よくある質問

rel='noreferrer' は rel='noopener' を含みますか?

はい。rel='noreferrer' を設定すると自動的に rel='noopener' が暗黙的に適用されるため、Referer ヘッダーの除去に加えて window.opener も null になります。つまり、よく見かける rel='noopener noreferrer' の組み合わせはモダンブラウザでは二重に冗長です。noreferrer 単独で opener の null 化とリファラーの抑制の両方をカバーできます。noreferrer は、遷移元 URL を遷移先ページに渡したくない場合にのみ追加してください。

現在、target='_blank' リンクで rel='noopener' を完全に省略するとどうなりますか?

モダンブラウザでは危険なことは何も起きません。Chrome、Edge、Firefox、Safari は noopener の挙動を暗黙的に適用するため、target='_blank' だけで window.opener は既に null に設定されます。これは WHATWG HTML 標準に明文化されたルールです。caniuse によると、これはグローバルのブラウザ利用シェアの約 95% に相当します。リバースタブナビングはデフォルトで無効化されています。残る隙間は、非 Chromium 版の Edge Legacy のようなレガシーエンジンだけです。

ヘッダーの代わりに meta タグで Cross-Origin-Opener-Policy を設定できますか?

いいえ。COOP は HTTP レスポンスヘッダーとしてのみ配信され、meta http-equiv に相当する手段は存在しません。インフラでレスポンスヘッダーを設定できない場合、COOP は適用できず、リンクごとの rel 属性か暗黙的な noopener のデフォルトに頼ることになります。ヘッダーを設定できる場合、Cross-Origin-Opener-Policy: same-origin を送信すれば、すべての anchor に属性を付けるのではなく、ドキュメント全体で window.opener の共有を一箇所で中央集約的に断ち切れます。

本当に window.opener が必要な場合、どうやって再び有効にしますか?

リンクに rel='opener' を明示的に指定します。window.opener を null にする安全な挙動が現在のブラウザのデフォルトであるため、rel='opener' は opener 参照を意図的に復元する手段です。たとえば、ポップアップが自分を開いたページにメッセージを送り返す必要がある場合などです。これは他のリンクに影響を与えることなく、その特定のリンクに対してのみ暗黙的な noopener の挙動を反転させます。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.