Dynamic permissions policy sources now receive source mappings
Rails の PermissionsPolicy では、静的なディレクティブに対してはシンボルやキーワードを自動的にマッピングし、'self' のような正しい文字列を生成していました。動的(Proc)で返されたシンボルはこのマッピングが適用されず、self という裸の文字列が出力され、ポリシーが無効になるケースがありました。本変更は動的ソースにも同様のマッピングを適用し、Content Security Policy との一貫性を確保します。
背景
問題の概要: 動的な permissions policy ソースが :self のようなシンボルを返すと、従来はそのシンボルが文字列 self としてそのまま出力され、'self' に変換されませんでした。その結果、geolocation self のような無効なポリシー文字列が生成されました。
一貫性の欠如: 静的ソースでは apply_mappings がシンボルを正しく 'self' に変換していましたが、動的ソースでは呼び出し後にこの変換が行われていませんでした。これは Content Security Policy の取り扱いと齟齬があり、実装上のバグとして扱われました。
影響範囲: 無効なポリシーはブラウザ側でブロックされる可能性があり、機能的に期待したアクセス制御が行われなくなるリスクがありました。テストでも Invalid HTTP permissions policy source の例外は発生していましたが、正しいマッピングが行われないケースは検出できていませんでした。
技術的な変更
resolve_source の拡張: actionpack/lib/action_dispatch/http/permissions_policy.rb の resolve_source メソッドに、Proc 実行後に返された結果へ apply_mappings を適用するロジックを追加しました。具体的には context.instance_exec(&source) の戻り値を変数 resolved に格納し、apply_mappings(Array.wrap(resolved)) を呼び出すよう変更しました。
変更前後の対比:
@@ -206,7 +206,8 @@ def resolve_source(source, context)
if context.nil?
raise RuntimeError, "Missing context for the dynamic permissions policy source: #{source.inspect}"
else
- context.instance_exec(&source)
+ resolved = context.instance_exec(&source)
+ apply_mappings(Array.wrap(resolved))
end
else
raise RuntimeError, "Unexpected permissions policy source: #{source.inspect}"
複数ソースの対応: apply_mappings は配列でも受け取れるため、動的ソースが [:self, "https://example.com"] のように複数要素を返した場合でも、それぞれが正しくマッピングされ、最終的に geolocation 'self' https://example.com という有効な文字列が生成されます。テスト test_dynamic_directive_returning_multiple_sources によって検証されています。
設計判断
一貫性の確保: 動的ソースに対しても apply_mappings を適用することで、静的・動的問わず同一のマッピングロジックを共有する設計となりました。これによりコードの重複が排除され、将来的なマッピングロジックの変更が一箇所で済むようになります。
最小限の侵入性: 変更は resolve_source 内の数行追加に留められ、既存の API や既存の動的ソースの戻り値の型(シンボル、文字列、配列)に対して後方互換性を保ちます。apply_mappings は元から静的ソースでも使用されているため、追加の副作用は発生しません。
テストでの明示的検証: 新たに追加された test_dynamic_directive と test_dynamic_directive_returning_multiple_sources によって、動的ソースのマッピングが正しく機能することが自動的に検証され、回帰リスクが低減しました。