調べて分かる道具箱

Cookie はなぜ見えないのか

自分で確かめる書くのは potamo-test という名前の、値が 1 だけの Cookie です。期限は5分です。
押したときにブラウザへ渡す1行

potamo-test=1; Max-Age=300; Path=/; SameSite=Lax

まず「Cookie を1つ書く」を押してみてください。この機器の中だけで動いていて、どこにも送りません。

よくある質問

ほかのサイトの Cookie を見ることはできますか?

できません。ブラウザは、いま開いているページと結びつく Cookie だけを JavaScript に渡します。RFC 6265 の 5.4 は、集める条件として「リクエスト先のホストが Cookie のドメインと一致すること」と「リクエストのパスが Cookie のパスと合うこと」を挙げています。ですから「ブラウザに溜まっている Cookie を全部見る」ページは、作り方が悪いのではなく原理として作れません。

document.cookie で有効期限やパスを調べられますか?

調べられません。返ってくるのは名前と値をセミコロンでつないだ文字列だけです。これは JavaScript の都合ではなく、Cookie という仕組みそのものの形です。RFC 6265 の 4.2.2 は「属性は返されない」と書き、続けて、サーバーでさえ Cookie ヘッダだけでは、いつ期限が切れるのか、どのホストやパスで有効なのか、Secure や HttpOnly が付いていたのかを判断できないと説明しています。

HttpOnly が付いた Cookie を JavaScript から読む方法はありますか?

ありません。読ませないことがこの属性の目的だからです。MDN は Set-Cookie の説明で「JavaScript が、たとえば Document.cookie を通じてその Cookie にアクセスすることを禁じる」と書いています。ログインの状態を持つような大事な Cookie ほどこの属性が付いているので、この画面の実験には出てきません。見えないことが、守られている印です。

cookieStore を使えば属性まで全部見えますか?

属性は取れますが、全部は見えません。cookieStore.getAll() が返すのは domain、expires、name、partitioned、path、sameSite、secure、value です。ただし取れるのは自分のサイトのぶんだけで、HttpOnly が付いた Cookie は結果に含まれません。使えるようになったのも Chrome 87、Firefox 140、Safari 18.4 と新しく、Firefox と Safari は2025年に入ってからです。

Cookie を消せば追跡されなくなりますか?

その Cookie は確かに消えますが、それで終わりではありません。消えるのはいま持っているものだけで、次に同じサイトを開けば、サーバーは Set-Cookie でまた新しいものを配れます。Cookie は仕組みの一部でしかない、というのがこのページの持ち帰りです。ほかの見分けられ方については、このページでは扱いません。

このページで書いた Cookie は残りますか?

「消す」を押せば消えます。押し忘れても、期限を5分にしてあるので自然に消えます。書くのは potamo-test という名前で、値は 1 だけです。名前も値もこのページに固定してあり、あなたを見分けられるものは何も入れていません。書き込みも読み取りもお使いの機器の中だけで起きていて、どこにも送っていません。

ブラウザに溜まっている Cookie を一覧するページは、作れません。ほかのサイトのぶんは取り出せず、自分のサイトのぶんも名前と値しか返ってこないからです。そのことを、下のボタンで確かめられます。

「ブラウザの Cookie を全部見る」ページは、原理として作れません

ブラウザは、いま開いているページと結びつく Cookie だけを JavaScript に渡します。 RFC 6265 の 5.4 には、その集め方が手順として書かれていて、条件はリクエスト先のホストが Cookie のドメインと合うこととリクエストのパスが Cookie のパスと合うことです。

ですから、このページから他のサイトの Cookie を読むことはできません。 「ブラウザに溜まっている Cookie を全部見せます」という道具をこのサイトで作れないのは、 手を抜いているからではなくそういう仕組みだからです。

ただし「オリジンごと」に分かれているわけでもありません

ここが面白いところです。RFC 6265 の前書きは、こう書いています ── あるホストの Cookie は、そのホストのすべてのポートで共有される。ブラウザがふつう使う 「同一オリジンポリシー」は、ポートが違えば別のものとして扱うのに。

同じ文書の 8.5 は、Cookie がポートによる仕切りを持たないことと、スキーム(http と https のような通信のしかた)による仕切りも持たないことを、はっきり書いています。 つまり Cookie の分かれ目は、ページの読み書きを仕切っている同一オリジンポリシーより少し粗いのです。 別のサイトからは見えない、という 一番大事なところは変わりませんが、 「オリジンごとに厳密に分かれている」と覚えると、そこだけずれます。

書けるのに、読み返せません

JavaScript から Cookie を書くときは、期限やパスといった属性をいくつも指定できます。 MDN が挙げている書ける属性は domain・ expires・max-age・partitioned・path・samesite・secure の7つです。

ところが読むときに返ってくるのは、名前と値をセミコロンでつないだ文字列だけです。 上のボタンで書いてから読むと、指定した Max-Age も Path も SameSite も1つも返ってこないことが目で見えます。

これは JavaScript の都合ではありません。RFC 6265 の 4.2.2 は「属性は返されない」と書き、続けてこう説明しています ── サーバーは Cookie ヘッダだけでは、いつ期限が切れるのか、どのホストで有効なのか、どのパスで有効なのか、Secure や HttpOnly が付いていたのかを判断できない。 つまりその Cookie を配った当のサーバーでさえ、属性を読み返せないのです。 片道の指定だと分かると、この仕組みはずいぶん見通しがよくなります。

属性ごとに、書けるか・読み返せるか
属性JavaScript からcookieStore から
Expiresいつまで持っておくか(時刻で指定)cookieStore では expires という項目で返る書けるが、読めない取れる
Max-Ageいつまで持っておくか(秒数で指定)cookieStore は秒数ではなく、期限の時刻に直して返す書けるが、読めない取れない
Domainどのドメインに送るか他のサイトのドメインは指定できない書けるが、読めない取れる
Pathどのパスで送るか消すときも同じ値をそろえる必要がある書けるが、読めない取れる
Secure暗号化された通信のときだけ送る値を持たない属性。付けるか付けないかだけ書けるが、読めない取れる
SameSite他のサイトから呼ばれたときに送るかStrict・Lax・None の3つ書けるが、読めない取れる
Partitioned埋め込み先のサイトごとに分けて持つ値を持たない属性書けるが、読めない取れる
HttpOnlyJavaScript から触らせないサーバーが Set-Cookie で付けるものだけ。JavaScript からは付けられない書くことも読むこともできない取れない

表のいちばん下、HttpOnly だけが「書くこともできない」側にいます。 次の節がその話です。

HttpOnly ── 読めないことが、守っている仕組みです

HttpOnly が付いた Cookie は、JavaScript からそもそも見えません。 MDN は Set-Cookie の説明で、「JavaScript が、たとえば Document.cookie を通じてその Cookie にアクセスすることを禁じる」と書いています。

仕組みは RFC 6265 の 5.4 の手順にそのまま書かれています。Cookie を集めるとき、その Cookie に HttpOnly の印が付いていて、集める相手が「HTTP でない窓口」なら、その Cookie を除く。ここでいう「HTTP でない窓口」が、まさに JavaScript の document.cookie です。

なぜこうなったのか

もともと Cookie にこの属性はありませんでした。入ったのはInternet Explorer 6 の Service Pack 1で、 目的はクロスサイトスクリプティング── 掲示板の書き込みなどに紛れ込ませた script を、 そのサイトの一部として動かしてしまう攻撃 ── への備えです。 script が動いてしまっても、Cookie が読めなければ持ち出されません。

当時のマイクロソフトの文書には、こう書かれています ── その Cookie は script からアクセスできない。最初にその Cookie を置いたウェブサイト自身から であっても。 同じ文書は、これだけで危険がなくなるわけではない(いくつかの手立てのうちの1つだ) とも念を押しています。

後から入った仕組みなので、RFC 6265 が仕様として書き直したのは2011年です。 いまでは、ログインの状態を持つような大事な Cookie ほどこの属性が付いています。だから、この画面の実験にはそういう Cookie が出てきません。見えないことが、盗まれにくくしている仕組みそのものです。

cookieStore なら属性が取れます。それでも全部ではありません

新しい cookieStore という窓口を使うと、document.cookie では読めなかった 属性が取れます。返ってくるのは domain・expires・name・partitioned・path・sameSite・secure・ value です。 期限もパスも、ここでは読めます。

ただし2つの制限はそのままです。1つは、取れるのが自分のサイトのぶんだけだということ。もう1つは、HttpOnly が付いた Cookie は結果に含まれないことです。 仕様を作っている WHATWG の説明にも、問い合わせでも見張りでも HttpOnly の Cookie は結果に入らない、 これは document.cookie と同じふるまいだ、と書かれています。

使えるようになった時期も、そろってはいません。Chrome は 87(2020年から)、Firefox は 140(2025年から)、Safari は 18.4(2025年から)。 Chrome だけが早く、ほかの2つは2025年に入ってからです。 だから「cookieStore を使えばよい」と言い切れる状態には、まだなっていません。

持ち帰り: 「Cookie を消せば追跡されない」は単純すぎます

Cookie を消すのは、じつはとても簡単です。上の「消す」が実際にやっているのは、期限を0秒にして書き直すことだけ。MDN も Max-Age の説明で、 0 か負の数を指定すればその Cookie は直ちに期限切れになる、と書いています。

でも消えるのは、いま持っているものだけです。 次にそのサイトを開けば、サーバーは Set-Cookie でまた新しいものを配れます。 しかも、消すときにも書いたときと同じパスをそろえる必要があります── そしてそのパスは、読み返せません。 何を書いたかを覚えているのは、自分だけです。

ここから持ち帰れるのは、Cookie は仕組みの一部でしかないということです。 「Cookie を消したから大丈夫」も「Cookie さえ止めれば安心」も、どちらも話を短くしすぎています。 Cookie そのものについて確かなことは、このページの3つ ──ほかのサイトのぶんは見えない・属性は書けるが読めない・HttpOnly は JavaScript から見えない ── です。

引用と数値の出どころ: 仕組みの決まりはRFC 6265(HTTP State Management Mechanism・2011年4月)(新しいタブで開きます)、属性の説明と書ける属性の一覧はMDN の Set-Cookie(新しいタブで開きます)とMDN の Document.cookie(新しいタブで開きます)、HttpOnly が入った経緯はマイクロソフトの当時の文書(新しいタブで開きます)、cookieStore と HttpOnly の関係はWHATWG の説明(新しいタブで開きます)、返る項目と対応バージョンはMDN の CookieStore.getAll(新しいタブで開きます)から取っています。日本語の引用は、原文の意味を変えないように訳したものです。