なぜAngularの「Sanitizer」は、開発者の甘えを許さないのか
現場でコードレビューをしていると、必ずと言っていいほど目にする「やらかし」がある。それが DomSanitizer の安易な利用だ。
「この画像が表示されない」「このリンクがクリックできない」。そんな理由で、フレームワークが用意した防波堤を自ら破壊し、bypassSecurityTrustHtml を無思考で使う。それがどれほど危険な火遊びか、今日はコードを交えて徹底的に解剖する。
1. なぜ「Sanitizer」が存在するのか
Angularは、デフォルトで全てのバインディングを「安全ではない」と見なし、自動的にサニタイズ(無害化)を行う。これは、ブラウザの DOM に直接 JavaScript を注入させないための「現代の鎧」だ。
しかし、開発者は時として「動的なHTMLをレンダリングしたい」「信頼できる(はずの)APIからHTML文字列が来る」という理由で、Angularの防護壁を解除したくなる。ここで登場するのが DomSanitizer だ。
2. PoC:脆弱な実装が招く「XSSの扉」
まず、最もやってはいけない実装例を見てみよう。
// 危険なコード例:外部からの入力をそのままHTMLとしてレンダリングする
import { DomSanitizer } from ‘@angular/platform-browser’;
constructor(private sanitizer: DomSanitizer) {}
// APIから受け取った怪しい文字列
const untrustedInput = ““;
// 警告!ここでAngularの防護壁を物理的に破壊している
this.dangerousHtml = this.sanitizer.bypassSecurityTrustHtml(untrustedInput);
このコードを実行するとどうなるか。ブラウザは の読み込みに失敗し、onerror 属性に仕込まれた JavaScript が即座に発火する。これが「セッションハイジャック」や「管理者権限での不正操作」の入り口だ。攻撃者は、あなたのサービスを自分の踏み台として自由自在に操ることになる。
3. セキュアな実装:コンテキストに応じた「信頼の付与」
bypassSecurityTrust... は、「自分自身で責任を持って内容を検証した」とコンパイラに誓約書を提出する行為だ。外部からの入力に対して使うものではない。
本当に動的な HTML を扱う必要があるなら、以下の手順を踏むのが正攻法だ。
ステップ1:サーバーサイドでサニタイズする
フロントエンドだけで解決しようとせず、必ずバックエンド側(Python/Node.js等)で DOMPurify のような定評のあるライブラリを通す。
Python (Bleach) によるサーバーサイドの例:
import bleach
許可するタグと属性を厳格に指定する(ホワイトリスト方式)
allowed_tags = [‘b’, ‘i’, ‘em’, ‘strong’, ‘a’]
allowed_attrs = {‘a’: [‘href’, ‘title’]}
ユーザー入力から危険なJavaScriptを取り除く
clean_html = bleach.clean(user_input, tags=allowed_tags, attributes=allowed_attrs)
ステップ2:Angularで安全にバインドする
サーバーで浄化されたHTMLであれば、DomSanitizer を使ってもリスクは最小化される。ただし、出力時は innerHTML ではなく、できる限り ngIf や [textContent] で DOM 操作を完結させるのが鉄則だ。
どうしてもHTMLを流し込む必要がある場合の賢い実装:
import { DomSanitizer, SafeHtml } from ‘@angular/platform-browser’;
// サーバーからサニタイズ済みの安全なHTMLが返ってくると仮定
getSafeHtml(input: string): SafeHtml {
// ここで初めて、サニタイズ済みであることが確実な文字列に信頼を与える
return this.sanitizer.bypassSecurityTrustHtml(input);
}
4. 最後に:インフラ層での「最後の砦」
アプリケーション側の実装ミスをゼロにするのは不可能に近い。だからこそ、防御は多層化すべきだ。特に Content Security Policy (CSP) の設定は必須である。
Nginx や CloudFront で以下のヘッダーを付与するだけで、仮に bypassSecurityTrustHtml を悪用されても、外部スクリプトの実行を物理的にブロックできる。
Nginx の設定例:
インラインスクリプトを禁止し、信頼されたソースからのみロードを許可
add_header Content-Security-Policy “default-src ‘self’; script-src ‘self’; object-src ‘none’; style-src ‘self’ ‘unsafe-inline’;”;
エンジニアの皆さんに伝えたいこと
「動くこと」と「安全であること」は、エンジニアリングの別次元の話だ。Angular の Sanitizer は、君たちが安全なアプリケーションを書くための強力な味方であり、同時に「お前は本当に信頼できるのか?」と常に問うてくる鏡でもある。
便利な API を見つけたとき、すぐに bypass という単語を探すのではなく、「なぜこの仕組みが存在するのか」という設計思想に立ち返ってほしい。それができるエンジニアこそが、次世代のセキュリティを担う人材だと私は信じている。
もし実装で迷ったら、まずは「DOMPurify」のドキュメントを読み、次に自分の CSP 設定を見直す。泥臭いけれど、これが一番の近道だ。
コメント