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 以外も削除すべき