README は Markdown できれいに書いたのに、社内 CMS やメールに入れようとすると HTML が要る。変換ツールに貼れば終わり、と思いきや、表が崩れたり行が妙にくっついたり、ときには他人の Markdown を変換してそのまま公開し、セキュリティ事故になる。変換のときに実際に何を押さえるべきかを整理します。
Markdown はどう HTML になるのか
基本文法は # が見出し、* が強調になるのは慣れています。やっかいなのは表や打ち消し線のような拡張文法です。
PiPi Worlds の Markdown 変換ツールは GitHub 風 Markdown(GFM)に従います。表を入れると <table>・<thead>・<td> に展開され、~~打ち消し~~ は <del>old</del> になります。自動リンクにも対応します。CommonMark が定義する標準 Markdownに GFM 拡張を加えた形なので、GitHub で見たとおりの結果をそのままコピーして使えます。
プレビューは無害化、コピーソースは生
ここで最も大事な区別が出てきます。画面に見えるプレビューと、コピーボタンが渡す HTML ソースは別物です。
プレビューはページに反映される前に DOMPurify で無害化されます。そのため Markdown に <script> や onerror のような危険要素が潜んでいても、プレビューでは実行されません。一方、コピー用の HTML ソースは生の変換結果です。たとえば Hi <img src=x onerror="alert(1)"> <script>alert(2)</script> を変換すると、ソースにはその onerror と <script> がそのまま残ります。この違いを知らないと危険です。
では、どこで再度サニタイズすべきか
判断の基準はシンプルです。Markdown の出所を信頼できるかどうかです。
自分で書いた README やドキュメントを変換するなら、コピーしたソースをそのまま使ってかまいません。しかしユーザーが入力した Markdown(コメントや投稿など)を変換して Web に表示するなら、コピーした生の HTML をそのまま載せた瞬間に XSS(クロスサイトスクリプティング)の入り口になります。<script> だけ塞いでも不十分で、onerror のようなイベント属性も塞ぐ必要があります。信頼できない入力は必ずサーバーで再度サニタイズしてください。
| 出所 | コピーソースをそのまま使用 |
|---|---|
| 自分で書いた文書 | 可能 |
| ユーザー入力(コメント等) | サーバーで再サニタイズが必要 |
1回の改行で行が変わらない理由
細かいですが、よく戸惑う点です。Markdown でエンターを1回押したのに HTML で行が変わらない。
標準 Markdown は1回の改行をソフトラップと見なします。行1 と 行2 をエンター1つで分けても、同じ <p> 段落の中に続きます。段落を本当に分けるには間に空行を入れます。同じ段落の中で行だけ強制的に変えるには、行末に半角スペース2つを置きます。メールや CMS で行間が意図と違うなら、たいていこの規則が原因です。
その Markdown、どこで変換していますか
最後は変換する場所の話です。下書き、議事録、非公開の仕様を HTML に変えるとき、そのテキストがどこかへ送信されるツールなら、公開前の内容が外部サーバーを経由します。
PiPi Worlds の Markdown 変換ツールは、変換がすべてブラウザ内で実行されます。入力は送信されないため、下書きや非公開メモは手元に留まります。ライブプレビューで結果を確かめ、無害化の有無を意識しながらソースをコピーしてください。HTML の中に base64 データが見えれば Base64 ツールへ、JSON の断片が混ざっていれば JSON フォーマッターへ続けて確認できます。