0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

レスポンスヘッダーを次のリクエストに自動で引き継ぐChrome拡張「Header Relay」を作った(MV3 / declarativeNetReq

0
Posted at

レスポンスヘッダーを次のリクエストに自動で引き継ぐChrome拡張「Header Relay」

作ったもの

API開発と検証向けのChrome拡張 Header Relay を作りました。

やることはシンプルで、レスポンスヘッダーから値をキャプチャして、以降の対象originへのリクエストに自動で付与します。固定ヘッダーの常時付与もできます。

モチベーション

APIサーバーが x-session-token やトレースID、ゲートウェイ用ヘッダーをレスポンスで返し、次のリクエストで送り返すことを期待する構成、ありますよね。ブラウザで検証しようとすると、

  1. DevToolsのNetworkタブでレスポンスヘッダーを確認
  2. 値をコピーしてModHeader系拡張に貼り付け
  3. トークンがローテーションするたびに1に戻る

という手作業ループになります。ModHeader系は静的な値しか扱えないので、このループからは抜けられません。mitmproxyやCharlesなら抜けられますが、「ブラウザにトークンを送り返させたいだけ」には大げさです。この中間を埋めるのがHeader Relayです。

主な機能

  • Captured Header:対象originのレスポンスから指定ヘッダーの値を保持し、以降のリクエストに自動付与
  • Fixed Header:常に付与する静的ヘッダー
  • Profile:origin単位の設定モジュール。複数を同時に有効化できる
  • Excluded Path:globパターンでassetsなどを除外(保存前に試せるテスター付き)
  • URL Probe:URLを貼ると、どのProfileにマッチしどのヘッダーが付くかを保存前にプレビュー
  • 監査ログ:直近1000件かつ7日以内のみローカル保持。リクエストURLは永続化しない
  • UIは英語、日本語、韓国語対応

MV3でどう実現するか

作る側の問題はここからです。かつてなら blocking webRequest のリスナーでリクエスト直前にヘッダーを差し込めば済みましたが、Manifest V3はまさにその書き換えを廃止しました。一番素直な手が、最初から選択肢にありません。

Header Relayは 観測(webRequest)と書き換え(declarativeNetRequest)の分離 で実現しています。

  1. non-blockingな webRequest.onHeadersReceived で有効originのレスポンスヘッダーを観測し、設定されたヘッダー値を storage.session(メモリのみ)に保存
  2. 有効Profile群+セッション状態を compileConfig() で中間表現にコンパイルし、単一のDNR session rulesetへ同期。付与はattach rule、Excluded Pathはremove ruleで表現
  3. 次のリクエストではChromeのDNRエンジンがネイティブにヘッダーを付与(リクエスト時にJSは介在しない)

単一コンパイルパスに寄せる

origin一致判定や除外判定は、DNR rule生成にもRuntime Status表示にもURL Probeにも必要です。素直に書くと同じURLマッチングが3箇所に育ち、3箇所はいつかズレます。「Probeでは付くと言われたのに実際は付かない」というバグの出どころがこれです。

なので、すべて compileConfig() の出力(CompiledConfig)だけを入力にしています。DNR rule生成もRuntime Status表示もProbeも同じ中間表現を見るため、表示と実挙動のズレが構造的に入り込みにくくなります。

競合はエラーにする

複数の有効Profileが同一originの同一ヘッダー名を付与しようとしたら、どうするか。DNRのruleはpriorityフィールドを持っているので、自動解決もできます。ただ、検証ツールで一番怖いのは「どちらの値が付いたのか分からない」状態です。静かに間違った値が付くくらいなら、何も付かないほうがましです。

なのでコンパイルエラーにして、owned ruleを全削除します。UIとRuntime Statusにエラーが出るので、どちらかをdisableすれば復旧します。

権限とプライバシー設計

「ヘッダーを読む拡張」は疑われて当然のカテゴリなので、設計を保守的に振っています。

  • キャプチャ値は storage.session(メモリ)のみ。ブラウザ再起動、拡張リロード、Profile無効化、権限取消で消去。UIではデフォルトでマスク表示
  • 外部送信なし。解析やテレメトリも含め、設定したTarget Origin以外にデータは出ていかない
  • host permissionは http://localhost/* 以外すべてoptional。Target Origin追加時のユーザー操作を起点に permissions.request() で対象originだけ要求し、未許可originにはruleを生成しない(fail-closed)
  • Set-Cookie / Host / Content-Length / Transfer-Encoding などmessage framingやtransportに関わるヘッダー名は保存自体を拒否。Authorization などは警告付きで許可

技術スタック

  • WXT + TypeScript + SolidJS + Tailwind CSS
  • ブラウザAPIに触るのは StoragePort / DnrPort / AuditPort の3ポートだけに限定。テストではin-memory adapterを差し込み、拡張全体をVitestでheadlessに駆動できます(設定投入→偽レスポンス→適用されたrulesetへのassertが、module mockなしで書ける)
  • リリースはchangesets + GitHub Actionsで自動化

おわりに

localhost:3000向けのデフォルトProfile(x-session-token キャプチャ+固定 x-client-id)が入っているので、インストール直後からローカル開発で試せます。固定ヘッダー用途なら、ストアから削除されたModHeaderの置き換えとしても使えます。

ソースコードは非公開ですが、不具合報告や「こういうヘッダーワークフローに対応してほしい」という要望は GitHub issues で受け付けています。フィードバックをもらえると嬉しいです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?