ADR-0026 レンダラと IPC の守りを固め、画像は資料フォルダの中だけにする
状況
- 2026-09-13 のセキュリティ点検で、次のことが分かった。
- 下書きの定型プロンプトは、コンソールの前面がツールではなくシェルでも送られていた。
notes/ のファイル名に $(...) を含めると、シェルの命令として実行された。
- レンダラは sandbox が無効で、CSP も無かった。新しいウィンドウと画面遷移も止めていなかった。main のファイル系 IPC は、どの絶対パスでも受け付けていた。レンダラへの入口は見つかっていないが、乗っ取られればそのまま手元のファイルの読み書きまで届く。
- 配布版でも環境変数
ELECTRON_RENDERER_URL を読み、別の URL を画面として読み込めた。Electron fuses は既定値のままだった。
- deck.md の画像は、https の URL をそのまま読み込み、絶対パスや
../ のファイルも読み込んで書き出しに入れていた。受け取った資料を開くだけで外部に通信し、書き出した pptx に手元の別のファイルが入りうる。
決定
- 下書きのボタンは、コンソールの前面のプロセスを main に尋ね(
pty:foreground。node-pty の process)、シェル自身(起動したシェルか、zsh・bash・fish などの対話シェル)なら送らない。$ ; | & < > ` \ ! や改行を含むファイル名は参照に入れず、そのことを知らせる。資料のファイル名に含まれていれば送らない。
- レンダラを
sandbox: true で動かす。preload は CommonJS(preload.cjs)でビルドする。
- ビルドしたページに CSP を入れる(
vite-csp.ts。Electron とブラウザ版で同じ)。スクリプトはアプリ自身のファイルだけ、画像はアプリ・blob:・data: だけ。開発サーバーでは入れない(ホットリロードがインラインのスクリプトを使う)。
- 新しいウィンドウは開かない。今のページ以外への遷移と、webview の追加は止める。
- main のファイル系 IPC(読み書き・一覧・削除・監視・書き出し・端末の起動・Finder で表示・開く)は、許したパスの中だけを扱う(
electron/paths.ts)。許すのは、ダイアログで選んだフォルダ(Markdown を選んだときはその親)、起動引数のフォルダ、起動時の settings.json にある最近の資料と masters.dir、既定のマスター保管フォルダ、settings.json 自体。シンボリックリンクは解決してから比べ、行き先の無いリンクは許さない。shell.openPath は json・md・pptx だけを開く。
- 配布版は
ELECTRON_RENDERER_URL を読まない。electron-builder で fuses を設定する(RunAsNode、NODE_OPTIONS、–inspect を無効にし、app.asar だけをその整合性を確かめて読み、Cookie を暗号化する)。画面は file:// のページなので、GrantFileProtocolExtraPrivileges は既定のままにする。
- deck.md の画像は、資料フォルダの中の相対パスだけを読む(
src/model/imageSrc.ts、tools/export_pptx.py の asset_path)。URL、絶対パス、フォルダの外に出る ../、外を指すリンクは、プレビューに表示せずに理由を出し、書き出しにも入れない。data:image/ の埋め込みは中身を持っているので、そのまま使う(書き出しは base64 のもの)。
結果
- 定型プロンプトがシェルの命令として実行されることはなくなる。
- 前面の判定はプロセス名で行うので、ツールがコンソールのシェルと同じ名前で動く場合(bash のコンソールで bash のスクリプトとして動くツールなど)は、シェルと区別できず送れない。macOS の
/bin/sh は bash として動く。
- 主な守りは乗っ取りを防ぐ層(CSP・sandbox・遷移の禁止)で、パスの制限は被害を狭める層になる。コンソールは本物の端末なので、乗っ取られたレンダラが端末に書き込めば命令は実行できる。
- settings.json はレンダラから書けるので、最近の資料に書き足されたパスは次の起動から許される。
- 外部の画像や、フォルダの外の画像を参照していた資料は、画像を資料フォルダに入れ直す必要がある。
状態