機密情報の漏洩を防げ! Webフロントエンド開発で意識すべき漏洩パターンとその対策
by
mizdra
Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
機密情報の漏洩を防げ! Webフロントエンド開発で 意識すべき漏洩パターンとその対策 id:mizdra 2025/11/15 YAPC::Fukuoka 2025 1
Slide 2
Slide 2 text
自己紹介 ● mizdra (みずどら) ● インターネット大好き ● 株式会社はてな ○ フロントエンドエキスパート ○ 社内のフロントエンド啓発活動 2
Slide 3
Slide 3 text
3 今日のテーマ Web フロントエンドにおける 機密情報の漏洩対策
Slide 4
Slide 4 text
4 機密情報 ● 機密情報と言っても色々ある ○ 個人情報 (メールアドレス, 住所) ○ 業務上の情報 (開発環境のドメイン, 未公開のキャンペーン情報) ○ 認証情報 (API トークン, Cookie) ○ ログ (アクセスログ、アプリケーションログ) ○ ソースコード ● 要は漏れるとマズいもの
Slide 5
Slide 5 text
5 Web フロントエンドにおいては ● 漏れないように注意して開発しようという話ではある ● ただし Web フロントエンドフロントエンドでは... ○ 機密情報の漏洩が起きやすい ● 何故か ○ ユーザに面してる ○ HTML や JavaScript がブラウザから読める ● 見える情報が多いゆえに脆弱
Slide 6
Slide 6 text
6 最近の Web フロントエンドは 落とし穴が結構ある ● モダンなフロントエンドフレームワークでは... ○ 機密情報を漏洩させやすいポイントがある ● フレームワークの裏側の挙動を知ってれば回避可能だが... ○ 知らないとハマる ○ テンプレートエンジン (Xslate, erb) に慣れてる人ほどハマりやすい
Slide 7
Slide 7 text
7 このセッションの目的 モダンな Web フロントエンド開発で漏洩を防ぐための知識 と術を伝えます。 ● 従来技術 (Xslate, erb) との違い ○ なぜモダンなフロントエンド FW は漏洩させやすいのか ○ 漏洩を防ぐにはどうしたら良いのか ● 漏洩の有無を調べる方法 ● うっかり漏洩するのを防ぐテクニック
Slide 8
Slide 8 text
8 おことわり ● React, Next.js の話が割と多め ● とはいえ他の View ライブラリ/FW に適用可能な話もある ● 最もメジャーな流派の事情を知るのは良いこと
Slide 9
Slide 9 text
9 従来技術との違い
Slide 10
Slide 10 text
10 従来技術とは ● ここでいう従来技術というのは... ○ Xslate (Perl), erb (Ruby), PHP ○ いわゆるテンプレートエンジン ● リクエストが来たら、サーバでテンプレートをレンダー ○ HTML を返す
Slide 11
Slide 11 text
例: erb で Counter を作る
Slide 12
Slide 12 text
12 最近の Web フロントエンド開発 ● JS の View ライブラリを使って UI を組むのが主流 ○ React, Vue.js, Angular, … ● ブラウザ上でコンポーネントをレンダーするだけでなく ○ SSR (サーバー上でレンダー) するのが当たり前 ○ SSR をサポートする FW (Next.js, Nuxt.js など) を使う ● 1つの View ライブラリ、1つの言語で UI を実装
Slide 13
Slide 13 text
例: Next.js (Pages Router) で Counter 13
Slide 14
Slide 14 text
14 erb と違い、テンプレートは不要。 JavaScript のコードだけあれば良い。 例: Next.js (Pages Router) で Counter
Slide 15
Slide 15 text
15 機密情報の漏洩度合いが違う ● より綺麗な形で従来技術と同じことができるが... ○ 完全に同じだと思ってると、痛い目を見る ● 実はモダンなフロントエンド FWでは... ○ 機密情報の漏洩度合いが高まってる
Slide 16
Slide 16 text
期間限定のキャンペーン情報を出す (erb) 16 お正月になってたら表示される、みたいなやつ
Slide 17
Slide 17 text
17 Next.js (Pages Router) で同じことを やってみる
Slide 18
Slide 18 text
18 Next.js (Pages Router) の場合 ● 実はキャンペーン情報が漏洩してる ○ お正月前に、キャンペーンが存在することが分かってしまう
Slide 19
Slide 19 text
19 Next.js (Pages Router) の挙動 ● 全コンポーネントはブラウザ向けの bundle に含まれる ○ コンポーネントに書いてあることは、全部漏洩する ● コンポーネントに機密情報を書いてはならない
Slide 20
Slide 20 text
20 何故コンポーネントがブラウザ向けの bundle に含まれるのか ● もしかしたら疑問に思うかも ○ erb はそうじゃないのに、なんで Next.js はそうなるの ● コンポーネントはレンダーが1回限りじゃない ○ サーバー上で1度レンダーされる (SSR) ○ state が変わったら、ブラウザでもレンダーされる (CSR)
Slide 21
Slide 21 text
再掲: Next.js Pages Router で Counter 21
Slide 22
Slide 22 text
22 何故コンポーネントがブラウザ向けの bundle に含まれるのか ● ブラウザでレンダーするには... ○ ブラウザが、コンポーネントのコードを持ってないといけない ● そのため... ○ 全てのコンポーネントが、ブラウザ向けの bundle に含まれる
Slide 23
Slide 23 text
23 伝えたかったこと ● テンプレートとコンポーネント、同じように見えるが... ○ 漏洩の度合いが違う ○ コンポーネントは全部ブラウザの bundle に含まれる ● コンポーネントには機密情報を書いてはならない
Slide 24
Slide 24 text
24 モダンなフロントエンド FW での漏洩対策
Slide 25
Slide 25 text
25 モダンなフロントエンド FW での漏洩対策 ● サーバーに機密情報を追いやる ● いくつかの実装パターンがある ○ 1. サーバーから機密情報を fetch する ○ 2. getServerSideProps を使う (Next.js Pages Router 限定) ○ 3. Server Component を使う (React 限定)
Slide 26
Slide 26 text
26 1. サーバーから機密情報を fetch する Next.js 以外の FW でもできる 最も手軽な回避策
Slide 27
Slide 27 text
27 2. getServerSideProps を使う ● Next.js Pages Router 限定のテク ○ 面白いので紹介 ● getServerSideProps とは ○ ページコンポーネント (例: TopPage) に渡す props を生成する関数 ● これを使っても漏洩を回避できる
Slide 28
Slide 28 text
28 例: getServerSideProps を使った回避策
Slide 29
Slide 29 text
29 なぜこれで漏洩を回避できるのか ● 実は getServerSideProps は... ○ サーバーサイドで実行される ● 関数自体のコードも、サーバーの bundle にのみ含まれる ○ ブラウザの bundle に含まれない ● 機密情報を書いても漏洩しない!
Slide 30
Slide 30 text
30 例: getServerSideProps を使った回避策 サーバーの bundle に含まれる ブラウザの bundleに含まれる
Slide 31
Slide 31 text
31 3. Server Component (SC) を使う ● React 限定のテク ● React 19 で安定化された、新しい種類のコンポーネント ○ サーバーで"のみ"レンダーされるコンポーネント ○ Xslate や erb みたいなやつ ● ブラウザの bundle に含まれない ○ 漏洩を回避できる!
Slide 32
Slide 32 text
補足: Client Component (CC) ● Server Component の対になるもの ● その正体は... ○ SC 登場以前にあったコンポーネントのこと ● Server Component 登場によって... ○ 名前の整理が行われただけ ○ 今まで書いてたやつが、Client Component と呼ばれるように 32
Slide 33
Slide 33 text
補足: 'use client' ディレクティブ ● RSC の世界では、デフォルトで SCになる (※1) ● CC にするには、 ‘use client’ を付ける 33 ※1: 厳密には何もつけないと SC / CC のどちらでもない、未確定の状態になる。 コンポーネント利用側に応じて SC / CC どちらになるか、後から決まる。
Slide 34
Slide 34 text
補足: SC と CC の違い 34 Server Components Client Components ブラウザの bundle 含まれない 含まれる イベントリスナ ❌ 登録できない ✅ 登録できる サーバ専用 API ✅ 使える ❌ 使えない ブラウザ専用 API ❌ 使えない ✅ 使える React Hooks ❌ 使えない ✅ 使える
Slide 35
Slide 35 text
補足: サポート状況 35 ● Server Component をサポートしている FW が必要 ○ Next.js, Waku, React Router, … ○ production ready なものは Next.js のみ ● Next.js はサポートしているものの... ○ App Router (新しい Router 実装) でのみサポート ○ Pages Router (古い Router 実装) では未サポート ■ コンポーネントはすべて Client Component という扱い
Slide 36
Slide 36 text
補足: 他にも色々 36 ● SC/CC は入れ子にできる ● … ● 後は資料見て!!! https://speakerdeck.com/mizdra/react-server-components-noyi-wen-wojie-kim ing-kasu
Slide 37
Slide 37 text
37 Server Component で漏洩を回避する例 (詳細は割愛するが) Next.js では app/page.tsx が Server Component なので、ここに書いたら良い
Slide 38
Slide 38 text
38 Server Component の落とし穴 ● SC => CC に props を渡すとき... ○ 実はその props の中身がユーザから見える
Slide 39
Slide 39 text
CC に渡す props からの漏洩 ● SC から CC に渡した props はユーザから丸見え!!! ○ props が丸ごとシリアライズされ、HTML に埋め込まれてる コードは以下の記事を参考にしつつ、改変してます。 https://zenn.dev/cybozu_frontend/articles/react-taint-apis
Slide 40
Slide 40 text
40 CC の props はなぜ HTML に 埋め込まれる? ● erb で data-initial="<%= count %>" してたのと同じ ● サーバーからクライアントにデータを渡すには... ○ HTML にシリアライズして埋め込まないといけない
Slide 41
Slide 41 text
41 対策: 必要なプロパティだけ渡す pass などの機密情報は除外
Slide 42
Slide 42 text
42 漏洩の有無を調べる方法
Slide 43
Slide 43 text
43 漏洩の有無を調べる ● 漏洩対策を実施するだけでなく... ○ 本当に漏洩が起きてないか確認することも重要 ● 調査のためのテクニックをいくつか紹介
Slide 44
Slide 44 text
44 調査方法①: grep する ● ブラウザ向けのビルド成果物を grep する ○ 例: grep --color -i -r -o -E '機密情報' ./dist ● Next.js の場合 ○ .next/static にブラウザ向けの成果物があるので、そこを grep する
Slide 45
Slide 45 text
45 調査方法②: Chrome DevTools の Network パネルを使う ● Network パネル ○ ページ閲覧中に発生したリクエストを覗き見れる
Slide 46
Slide 46 text
46 調査方法②: Chrome DevTools の Network パネルを使う ● 実は検索機能が付いてる ○ ページ閲覧中にリクエストされたリソースに対し、平文検索する ● これを使えば、機密情報の漏洩を調べられる
Slide 47
Slide 47 text
47 使い方
Slide 48
Slide 48 text
48 注意 ● ここでの検索対象は... ○ 実際にブラウザでリクエストされたリソースだけ ○ リクエストされなかったリソースは検索対象にならない ● 本当は漏洩しているのだけど... ○ Network パネルの検索で見つからないことがある
Slide 49
Slide 49 text
49 Network パネルの検索で見つけられない例
Slide 50
Slide 50 text
50 うっかり漏洩を防ぐ テクニック
Slide 51
Slide 51 text
51 うっかり漏洩するのを防ぐ ● 漏洩対策を心がけていても... ○ うっかり対策が忘れられて漏れることも ● 万が一不備があっても漏れない・漏れを検知できると良い ○ テクニックを3つ紹介
Slide 52
Slide 52 text
52 ① CI で grep する シンプルだけど、これが一番効く!
Slide 53
Slide 53 text
53 ② CC に機密情報が渡るのを Taint API で防ぐ ● React に Taint API という機能がある ○ 任意の値を汚染 (taint) できる ● 汚染された値を CC に渡すと... ○ 実行時エラーが発生する ● 誤って CC に機密情報を渡してしまうのを防げる
Slide 54
Slide 54 text
例: Taint API で user.pass を汚染する コードは以下の記事を参考にしつつ、改変してます。 https://zenn.dev/cybozu_frontend/articles/react-taint-apis user.pass を汚染 (taint) する
Slide 55
Slide 55 text
55 ③ GraphQL で data fetch ● 一般的にはレンダーに必要な data を fetch するのに... ○ `prisma.users.findUnique(...)` や `fetch(...)` を使いがち ● その代わりに、GraphQL を使う
Slide 56
Slide 56 text
56
Slide 57
Slide 57 text
57 何が良いの ● GraphQL スキーマに書かれた field しか、コンポーネント から触れなくなる ○ スキーマに書いてない余計なもの (User.pass など) は、resolver か ら返される段階で削ぎ落とされる ● コンポーネントから余計なものを参照できない ○ 漏洩もしない
Slide 58
Slide 58 text
58 とはいえ ● このためだけに GraphQL を利用するのはオーバーキル ○ resolver の実装コストは無視できない ● 一般にはオススメできないが... ○ すごく漏洩に気を遣うプロジェクトではアリ
Slide 59
Slide 59 text
59 まとめ ● モダンなフロントエンド FW では... ○ (通常) コンポーネントがブラウザから丸見え ○ fetch, getServerSideProps, Server Component で漏洩を防ぐ ● 漏洩の有無を調べることも大事 ○ grep, Chrome Devtools ● うっかりミスを防ぐのも重要 ○ CI で grep, Taint API, GraphQL で多層防御
Slide 60
Slide 60 text
60 おまけ
Slide 61
Slide 61 text
61 よくある漏洩パターン ① Source Map の漏洩
Slide 62
Slide 62 text
62 Source Map とは ● ビルド前後のコードの対応関係が記録されたファイル ○ 拡張子は *.map ● Source Map があることで ○ スタックトレースがオリジナルのコードのものになる ○ オリジナルのコードに breakpoint を仕掛けてステップ実行できる
Slide 63
Slide 63 text
63 Source Map の例 コードは https://web.dev/articles/source-maps?hl=ja より引用
Slide 64
Slide 64 text
64 Source Map の例 コードは https://web.dev/articles/source-maps?hl=ja より引用 ビルド前後の対応関係が圧縮されて格納されてる
Slide 65
Slide 65 text
65 Source Map の例 コードは https://web.dev/articles/source-maps?hl=ja より引用 オリジナルのコードが含まれてる
Slide 66
Slide 66 text
66 Source Map が漏れた場合 ● sourcesContent も漏洩してしまう ● => オリジナルのコードが漏れるのと同じことに
Slide 67
Slide 67 text
67 対策 ● 本番ビルドでは Source Map を生成しない ● とはいえ...どうしても生成したい時はある ○ エラー監視サービス (Sentry など) に Source Map を送りたいとか ● 以下のようなビルドフローを組むと良い ○ 1. Source Map 生成ありで本番ビルド ○ 2. エラー監視サービスに Source Map をアップロード ○ 3. Source Map を削除 (`rm -rf dist/**/*.map`)
Slide 68
Slide 68 text
68 余談 ● @sentry/nextjs ○ ビルド時に Source Map を Sentry にアップロードしてくれる ○ アップロード後、自動で削除もしてくれる ● しかし... ○ 最近まで CSS の Source Map が削除されないバグがあった ○ https://github.com/getsentry/sentry-javascript/issues/18125 ○ 5日前に報告して、修正リリース済み
Slide 69
Slide 69 text
69 よくある漏洩パターン ② コンポーネントの 関連リソースからの漏洩
Slide 70
Slide 70 text
70 コンポーネントの関連リソースからの漏洩 ● 関連リソースとは ○ コンポーネントファイルから import したり、参照しているもの ○ CSS, 画像, JavaScript モジュール ● 全部 .next/static 配下に出力される (ブラウザから見える) ● 開発環境用の隠し画像ファイルが漏れるとかはありがち... ○ 気をつけよう
Slide 71
Slide 71 text
71 対策 ● 可能な限り、開発環境用のリソースの置き場を限定する ○ `app/internal/…`: ページコンポーネント ○ `components/internal/…`: 汎用コンポーネント ○ `assets/internal/…`: 画像や json ファイルなど ● 本番環境向けのビルドをする直前に、それらを削除する ○ rm -rf {app,components,assets}/internal
Slide 72
Slide 72 text
72 app/internal を削除するだけでは駄目? ● 理論上はそれで良い ● ただし本番環境用のページから components/internal を うっかり参照してしまうと... ○ ブラウザ向けのビルド成果物にそれが含まれてしまう ● rm -rf components/internal しておくと... ○ 本番環境向けにビルドした時に、ビルドエラーになる ● ミスを防ぐために app/internal 以外も削除すべき