Referrer を制御する

Web ブラウザーは通常 HTTP 要求の <a href=https://wiki.suikawiki.org/n/Referer%3A><code>Referer:</code></a> ヘッダーに参照元ページの URL を入れますが (あるいは <a href=https://wiki.suikawiki.org/n/document.referrer><code>document.referrer</code></a> で参照元ページの URL を取得できますが)、 Web サイト側でこれを制御したいことがあります。

例えば、次のような場面が想定されます。

- URL にユーザー名や秘密の ID などを含めざるを得ない時は、プライバシーやセキュリティーの観点から、この URL を外部に漏らしたくありません。
- 社内システムに URL を貼りたいことがありますが、社内システムの URL を外部に漏らしたくありません。
- Web アプリケーションの開発用サーバーは、その所在を外部に漏らしたくありません。
- 投稿者と友達のみに公開される SNS の投稿にリンクが含まれる時、その個別 URL を漏らしたくありません。 (SNS 全体の URL が漏れることは問題ありません。場合によっては、流入元を示すため、敢えて残したいです。)
- 提携 Web サイト間での利用者の動向を分析するに当たり、 `https:` から `http:` へのリンクであっても `Referer:` が送信されてほしい場合もあります。
- 外部リンクで `Referer:` は送出したくないとしても、同じサイトの `POST` では CSRF 検査のため `Referer:` や `Origin:` を送ってほしいことがあります。

このような場合に、 Web サイト側で出来る対策を、古典的な方法から順に紹介します。

この記事は2013年に最初に執筆したものですが、仕様と実装の変化にあわせて令和3(2021)年まで何度か改訂しています。

# 中間ページを挟む

***この方法は、おすすめできません。***

本来のリンク先の URL に直接リンクするのではなく、その URL へのリンクを含むページへリンクします。例えば、 <code>http://www.example.com/</code> にリンクしたいなら、 <code>http://redirector.example.com/<mark>http://www.example.com/</mark></code> にリンクして、そちらでは <code>http://www.example.com/</code> へのリンクが表示されるようにします。 <code>www.example.com</code> に送られる <code>Referer:</code> は <code>redirector.example.com</code> の URL になりますから、元のページの URL は漏れません。

これだけでは閲覧者が余分にリンクをたどる必要がありますから、 <code>redirector.example.com</code> にはリンクを自動的にたどる機能があると便利です。 <code>&lt;meta http-equiv=Refresh></code> や <code>location.replace</code> を使ったり、リンクの <code>a</code> 要素の <code>click</code> メソッドを呼び出したりする方法があります。これらの場合には仕様上は <code>redirector.example.com</code> の <code>Referer:</code> が送られるはずですが、<a href="http://www.facebook.com/notes/facebook-engineering/protecting-privacy-with-referrers/392382738919">ブラウザーによっては送られないことがある</a>ようです。ただ単に特定のページの <code>Referer:</code> を送らないようにしたいのではなく、その値を制御したいのであれば、どの方法を採るか検討する必要がありそうです。なお、 <code>location.href</code> への代入だと履歴に余分なページが残ってしまい、「戻る」が正常に動かなくなってしまいます。また、 HTTP リダイレクトは元の <code>www.example.com</code> の URL がリダイレクト先の <code>Referer:</code> にも使われてしまうので、この目的では使えません。

この方法は <code>img</code> 要素などには使えません。

<code>data:</code> URL を使えば中間ページを別途用意する必要はありません。ただし対応していない古い Web ブラウザーではリンクをたどれなくなってしまいます。

```html
<a href="data:text/html,<a href='http://www.example.com/'>進む</a>">リンク</a>
```

どの方法もリンクが汚くなってしまうのが難点です。 CSS の <code>:visited</code> や Web ブラウザーの「リンクの URL をコピー」のような機能が思った通りに動かなくなりますし、制作者側もリンク時に一手間必要になります。

10年以上前から 2ch で使われている <a href=https://wiki.suikawiki.org/n/ime.nu><code>ime.nu</code></a> が有名です。かつてはこれがほとんど唯一の方法で、よく使われていました。

# HTTPS (HTTP/TLS or HTTP/SSL) を使う

***この方法は、今となっては不適切です。***

<a href=https://wiki.suikawiki.org/n/Referer%3A#section-%E8%A6%81%E6%B1%82%E3%81%AE%E5%8F%82%E7%85%A7%E5%85%83%E3%81%AE%E6%B1%BA%E5%AE%9A data-old-href="https://tools.ietf.org/html/rfc2616#section-15.1.3"><code>https:</code> から <code>http:</code> へのリンクでは、 <code>Referer:</code> は送らない</a>ことになっています。これを利用して、漏らしたくない URL を含むホスト全体を <code>https</code> で提供すれば、 <code>http:</code> URL への参照があっても <code>Referer:</code> の送信を抑制できます。

TLS (SSL) 証明書を用意しなければならないなど多少の手間はありますが、対象となるページやリンク自体に手を入れなくてもいいという意味では、とても手軽な方法です。

ただし、 <code>https:</code> のページへの <code>Referer:</code> は送信されてしまいます。 <code>https:</code> 化することで <code>Referer:</code> が送信されなくなったと安心してしまいがちですが、実際には対策として不完全で、**危険**です。最近は <code>https:</code> を使うのが当然という流れになってきていますから、 `Referer:` が送信されてしまうケースはどんどん増えていきます。

# <code>rel=noreferrer</code>

<code>a</code> 要素や <code>area</code> 要素や <code>form</code> 要素に <a href="https://www.whatwg.org/specs/web-apps/current-work/#link-type-noreferrer"><code>rel=noreferrer</code></a> を指定すると、そのリンクは <code>Referer:</code> を送信しなくなります。

```html
<a href="http://www.example.com/" rel=noreferrer>リンク</a>
```

リンクそれぞれで <code>Referer:</code> を送信するか制御できるので便利ですが、先述のような用途だと指定漏れのおそれがあり、あまり安心できないかもしれません。 CMS などでサーバーやページ全体を変更できない場合には、自分で編集できるリンクに属性を指定するだけで良くて便利かもしれません。

Chrome、Safari など <a href="http://www.webkit.org/blog/907/webkit-nightlies-support-html5-noreferrer-link-relation/">WebKit/Blink 系のブラウザーは早くから実装しています</a>。 Firefox でも<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=530396">実装されました</a>。以前は未対応の Web ブラウザーがまだ多いのが難点でしたが、現在となってはかなり実用的な方法です。

この方法は <code>img</code> 要素などには使えません。

<code>form</code> 要素でも最近使えるようになりました。 Web ブラウザーの対応状況には注意が必要です。

平成31(2019)年の HTML Standard 改訂で、 <code>window.open</code> の引数にも <code>noreferrer</code> が指定できるようになりました。 Web ブラウザーの対応状況には注意が必要です。

この機能は <a href=https://html.spec.whatwg.org/#link-type-noreferrer>HTML Standard</a> で規定されています。

# <code>&lt;meta name=referrer></code>

<a href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-delivery-meta"><code>&lt;meta name=referrer></code></a> は、ページ内のリンク等で referrer をどう処理するべきかを指定するものです。

<code>Referer:</code> の送信の有無に加え、送る場合に <a href=https://wiki.suikawiki.org/n/%E8%B5%B7%E6%BA%90>origin</a> (URL scheme とホストの部分のみで、 path や query を除いたもの。) だけを送るオプションも選ぶことができます。 URL の path や query の部分に漏らしたくない文字列が含まれていても、どの Web サイトからの流入なのかリンク先に伝えたいなら、 origin のみ送信すれば良いというわけです。

- 実際に Google 検索は origin のみを送るように指定されており、 query に含まれる検索キーワードは <code>Referer:</code> に含まれませんが、 Google のリンクをたどったアクセスであることは伝わるようになっています。
- ユーザーIDがURLに含まれるソーシャルブックマークサービスや友達のみに公開される SNS の記事などで、プライバシー保護のため origin のみ送るように指定したいかもしれません。

```html
<!-- Referer: を送らない -->
<meta name="referrer" content="no-referrer">
<!-- content="never" は旧仕様 -->
```

```html
<!-- Referer: に origin のみ含める -->
<meta name="referrer" content="origin">
```

この指定はすべてのリンクに適用されますし、 <code>img</code> や <code>iframe</code> などにも有効です。また、通常 <code>Referer:</code> を送信しない <code>https:</code> のページから <code>http:</code> の URL への参照でも <code>Referer:</code> を送信するように指定できます。

`unsafe-url` という値を使うと、通常は referrer が送られないことになっている、 `https:` のページから `http:` URL へのリンクでも、 referrer を送信することができます。近年 HTTPS への移行が進んでいるとはいえ、まだ `http:` しかない Web サイトも多いですが、流入元が自サイトであることを知らせたいとき、便利です。 (もちろん、自ページの URL が流出するべきでないものであるとき、この値を使ってはいけません。値に「unsafe」とある通り、 `https:` の URL が平文で送信されることになります。)

利用できる値と動作をまとめると、次の表のようになります。

| 値          | 同じ origin | 同じ scheme、`http:` → `https:` | `https:` → `http:` | 旧仕様の値 |
|-------------|------------|-------------|----------------|-------|
|`no-referrer`| 送信しない  | 送信しない | 送信しない | `never` |
|`origin`     | origin のみ | origin のみ | origin のみ | |
|`no-referrer-when-downgrade` | 送信する | 送信する | 送信しない | `default` |
|`origin-when-cross-origin` | 送信する | origin のみ | origin のみ | |
|`unsafe-url` | 送信する | 送信する | 送信する | `always` |

この他に、比較的新しく追加された `same-origin`, `strict-origin`, `strict-origin-when-cross-origin` という値があります。

新しい値が仕様に追加された直後には、まだすべての Web ブラウザーで実装されていないかもしれませんが、

```html
<meta name="referrer" content="origin">
<meta name="referrer" content="strict-origin">
```

... のように複数の値を指定すると、ブラウザーが対応しているもので最後に指定された値が適用されます。この例では、 古めのブラウザーなら `origin` が、新しいブラウザーなら `strict-origin` が適用されます。

※ 本記事の最初の執筆時点では残念ながらまだ Chrome しか実装していませんでしたが、現在は Firefox でも <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=704320">実装されています</a>。

※ 以前は、 Safari と Edge は「旧仕様の値」にしか対応していませんでした。「旧仕様の値」は新たに使うべきではないのですが、当面はやむなくそちらを使う方が良い場面もあると言っていました。「旧仕様の値」は Chrome や Firefox のような新しい Web ブラウザーでも正常に認識されます。

※ リンクごとに個別に `referrerpolicy=""` 属性を指定できなかった時代は、スクリプトで動的に `<meta name=referrer>` を書き換えて、同じドメインのサイトには URL 全体を、違うドメインのサイトには origin だけを送るように無理矢理に制御しているサイトも<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=704320#c58">あったようですw</a> さすがにどうかと思いましたが...

この機能は <a href=https://html.spec.whatwg.org/#meta-referrer>HTML Standard</a> で規定されています。 (<a href="https://wiki.whatwg.org/wiki/New_Features_Awaiting_Implementation_Interest">当初 WHATWG Wiki で提案され</a>、いずれ HTML Standard にも追加されるだろうと思われていましたが、<a href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-delivery-meta">Referrer Policy</a> という仕様書で標準化されることになりました。この時、指定できる値が一部変更されました (旧仕様の値も認識はされますが、新しい値を使うべきだとされています)。その後改めて HTML Standard と統合されました。)

# CSP

***この方法は、廃止されました。***

CSP (HTTP <code>Content-Security-Policy:</code> ヘッダー) で <code>&lt;meta name=referrer></code> 相当の指定をできるようにすることが<a href="https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html#referrer">検討されていました</a>。

```http
Content-Security-Policy: referrer no-referrer;
```

個別の Web ページを書き換えずに Web サイト全体に適用したいときに、サーバーの設定を書き換えるだけで済むのは便利かもしれません。

<a href="https://w3c.github.io/webappsec-referrer-policy/#directive-referrer">Referrer Policy</a> 仕様書で規定されていました。

本記事の最初の執筆時点ではどの Web ブラウザーも実装していませんでしたが、その後 Chrome と Firefox が実装しました。

しかし、 <a href=https://lists.w3.org/Archives/Public/public-webappsec/2015Oct/0043.html>CSP よりも独立した HTTP ヘッダーとするのが適切</a>と判断され、この方法は削除されました。

# <code>referrerpolicy=""</code>

HTML <code>a</code> 要素、 <code>img</code> 要素、 <code>iframe</code> 要素、 <code>script</code> 要素、 <code>link</code> 要素、 <code>area</code> 要素、 SVG <code>a</code> 要素の <code>referrerpolicy</code> 属性を使うと、 <code>&lt;meta name=referrer></code> と同様の指定がリンクごとに行えます。

要素の属性 (内容属性) としてだけでなく、 JavaScript のプロパティー (IDL属性) `referrerPolicy` としてもアクセスできます。

この機能は <a href=https://html.spec.whatwg.org/#attr-hyperlink-referrerpolicy>HTML Standard</a> で規定されています。 (当初は <a href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-delivery-referrer-attribute">Referrer Policy</a> 仕様書で規定されていました。)

本記事の最初の執筆時点ではどの Web ブラウザーも実装していませんでしたが、現在ではおおむね問題なく使うことができます。

# <code>request.referrerPolicy</code>

<code>fetch</code> API で使う <code>Request</code> オブジェクトには <code>referrerPolicy</code> プロパティー (IDL属性) があり、 <code>&lt;meta name=referrer></code> 同様の値を指定できます。

<a href="https://fetch.spec.whatwg.org/#dom-request-referrerpolicy">Fetch Standard</a> で規定されています。

# <code>Referrer-Policy:</code> ヘッダー

CSP の方法にかわって、新設の独立した HTTP ヘッダー `Referrer-Policy` で <code>&lt;meta name=referrer></code> 同様の値を指定できるようになります。

```http
Referrer-Policy: origin
```

昔からある `Referer` ヘッダーとは違って、綴りが `Referrer` となっていることに注意が必要です。

<a href=https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-header>Referrer Policy</a> 仕様書 (と <a href=https://html.spec.whatwg.org/#initialise-the-document-object>HTML Standard</a>) で規定されています。

# <code>history.replaceState</code>

ここまでの方法は <code>Referer:</code> の送信自体を制御していましたが、 <code>Referer:</code> で送信される URL の方を変更する手もあります。

<a href="https://www.whatwg.org/specs/web-apps/current-work/#dom-history-replacestate"><code>history.replaceState</code></a> を使うと現在のページの表示はそのままで、 URL を書き換えることができます (いわゆる <a href=https://wiki.suikawiki.org/n/Pjax>Pjax</a> です)。そうすると <code>Referer:</code> で送られる URL も変更されます。ただし新しい URL は元の URL と同じホスト (正確には同じ <a href=https://wiki.suikawiki.org/n/%E8%B5%B7%E6%BA%90>origin</a>) でなければなりません。サーバーの存在自体を漏らしたくないときには使えませんが、 path や query に漏らしたくない文字列が含まれているときには有用です。なお <code>history.pushState</code> では Web ブラウザーの履歴に余分な項目が追加されてしまいますから不適当です。

注意したいのは、書き換えた新しい URL も同じ内容のページを表すものでないといけないことです。そうしないと「戻る」で移動してきた時に意図しないページに戻ってしまいます。 <code>replaceState</code> の第1引数には任意のデータを保持しておけますから、書き換える前の path や query に含まれていた情報をそこに入れておいて、書き換え先の URL はそれを使って (<code>history.state</code> でアクセスできます。) 元のページを復元すると良いでしょう。

```html
<script>
  var m = location.href.match(/\/([^\/]+)\/$/);
  if (m) {
    history.replaceState ({user_name: m[1]}, document.title, location.href.replace(/\/[^\/]+\/$/, '/my/'));
  }
</script>
```

この方法は主要 Web ブラウザーすべてで使えます。実装の始まった頃には <code>Referer:</code> の URL が変更されないブラウザーもありましたが、現在では完全に無視して問題ありません。

サーバーの設定を変更したり個別のリンクに手を入れたりせずに、すべての Web ブラウザーで <code>Referer:</code> を制御できるのはこの方法だけです。

# まとめ

Web ブラウザーの <code>Referer:</code> の送出を Web サイト側で制御する方法を紹介しました。本記事の最初の執筆 (2013年) 時点では、どのブラウザーでも実装されている `replaceState` の方法が (手間がかかるものの) 最善かもしれないと述べていました。現在では <code>&lt;meta name=referrer></code> が最も使い勝手が良さそうですが、ブラウザーによっては新しい値に対応していなかったりするので、注意が必要です。

ところで <code>Referer:</code> HTTP ヘッダーの名前以外はすべて「r」が2つの referrer ですので注意しましょう。

- Referrer (r が2つの正しい綴り)
  - <code>document.referrer</code> (DOM)
  - <code>request.referrerPolicy</code> (DOM)
  - <code>window.open (<var>...</var>, <var>...</var>, 'noreferrer')</code>
  - <code>rel=noreferrer</code> (HTML)
  - <code>referrerpolicy=""</code> (HTML)
  - <code>&lt;meta name=referrer></code> (HTML)
  - <code>Referrer-Policy:</code> (HTTP)
  - <code>referrer no-referrer</code> (CSP)
- Referer (r が1つ)
  - <code>Referer:</code> (HTTP)

# 蛇足

- WebSocket ハンドシェイクでは、 `Referer:` は常に省略されます。
- FTP には `Referer:` が無いので送信されません (が、 `document.referrer` はあります)。
- 令和元(2019)年の仕様改訂で、セキュリティー上の理由から、 `Referer:` に指定される URL の最大長が4096バイト (4KB) 以下に制限されました。 URL が 4KB を超える時は path と query が削られて起源のみになります。