babyrite: Security Report (2026/08/02)
babyrite v1.4.2 では,チャンネルの権限検証フローにまつわる複数の不具合を修正しました.このうち 3 件は脆弱性と認定し,GitHub Security Advisory を発行しています.
そのセキュリティレポートを公開します.
Release babyrite: v1.4.2 - m1sk9/babyrite
特別な理由がない限り最新版へアップデートしてください.
v1.4.2 で修正した脆弱性
脆弱性は全部で 3 件です.いずれも,本来アクセス権限がないチャンネルのメッセージが babyrite を通じて閲覧できてしまうものです.
| Advisory | Severity | Affected |
|---|---|---|
| GHSA-vqqw-5pfp-qv9g | 7.7 High | v0.11.0 以上 v1.4.2 未満 |
| GHSA-fgg3-r9gf-q7rj | 6.3 Medium | v0.11.0 以上 v1.4.2 未満 |
| GHSA-8q64-5qhq-rr54 | 6.3 Medium | v1.3.0 以上 v1.4.2 未満 |
スコアはいずれも CVSS v3.1 です.ベクターは各 advisory に記載しています.
回避策
ありません.3 件とも babyrite 内部の判定ロジックに起因するもので,v1.4.2 へのアップデートが唯一の根本対処です.また babyrite はセキュリティポリシーのとおり,脆弱性修正を古いバージョンへバックポートしません.
どうしてもアップデートできない場合,漏洩し得る範囲は bot が閲覧できるチャンネルに限られます.非公開チャンネルから bot の閲覧権限を外すか,bot 自体を停止することで影響を抑えられます.
advisory を発行していない修正について
v1.4.2 には,脆弱性としては扱わなかったものの権限・出力まわりを固める修正も同時に入っています.リリースノートと照らして「これは advisory がないのか」と迷わないよう挙げておきます.
- #646 スレッドの NSFW 判定を親チャンネルで行うようにした
- #647 コードブロック内でメンションが解釈されたり,フェンスを脱出したりしないようにした
- #645 GitHub の raw 取得でサイズ上限を受信バイト数に対して適用するようにした
解説
GHSA-vqqw-5pfp-qv9g: 偽装リンクによる別サーバーの非公開チャンネル読み出し
babyrite はメッセージプレビュー生成を効率よく行うためにチャンネル情報をキャッシュしています.このキャッシュはチャンネル ID のみをキーにしており,取得時にそのチャンネルが要求したサーバーのものかを検証していませんでした.
一方でリンクの検証は,URL に書かれたサーバー ID が現在のサーバーと一致するかだけを見ていました.そのため「自分のサーバーの ID」と「別サーバーのチャンネル ID」を組み合わせたリンクを作ると,検証を通過したうえでキャッシュから別サーバーのチャンネルが返ってきました.
権限はサーバーごとに定義されるため,この状態では別サーバーのチャンネルの権限設定を自分のサーバーのロールに当てはめて比較することになり,権限チェックの判定が無意味になっていました.結果として,bot が読み取れる別サーバーの非公開チャンネルの内容が展開され得ました.
成立には,bot が両方のサーバーに参加していること,対象チャンネルが直近のキャッシュに載っていること,攻撃者がそのチャンネル ID とメッセージ ID を知っていることが必要でした.なお,babyrite が非公開チャンネルへのリンクを正しく拒否した時点でそのチャンネルがキャッシュに載るため,この前提は通常の利用の中で満たされ得ました.
修正: キャッシュから取得したチャンネルが要求したサーバーのものかを毎回検証し,一致しない場合はエラーにするようにしました.チャンネル ID は Discord 全体で一意なので,不一致は「古い情報」ではなく「要求そのものが誤っている」ことを意味します.
GHSA-fgg3-r9gf-q7rj: 権限変更が最大 12 時間反映されない
権限チェックは,キャッシュに保持しているチャンネル情報に含まれる権限設定をもとに判定していました.しかしチャンネルの変更を受け取ってキャッシュを破棄する仕組みがなく,エントリはアクセスがなければ 1 時間 (TTI),アクセスが続く限り最大 12 時間 (TTL) 生存しました.
そのためチャンネルを公開から非公開に変更しても,babyrite は最大 12 時間それを公開チャンネルとして扱い続けました.bot 自身の閲覧権限は変わらないため,非公開化した後に投稿されたメッセージも,制限したはずの読者が読めるチャンネルへ展開され得ました.
キャッシュはアクセスするたびに寿命が延びる設定だったため,誰でも展開を繰り返すだけでこの状態を TTL の上限まで維持できました.また,サーバー内で一度でもリンク展開が行われるとサーバー全体のチャンネル一覧がキャッシュされるため,攻撃者側の準備は不要でした.
なお「情報を隠すためにチャンネルを非公開にする」という最も切迫した場面でこそ露出するという,望ましくない相関がありました.
修正: チャンネルとスレッドの作成・更新・削除を Discord から受け取った時点で該当キャッシュを破棄するようにしました.あわせて TTL を 12 時間から 1 時間へ短縮しています (TTI はもとから 1 時間で変更していません).イベントの配信は保証されないため(再接続時に取りこぼす場合があります),TTL がその影響範囲の上限になります.
GHSA-8q64-5qhq-rr54: メンバー個別許可が可視性判定で無視される
この節の用語
babyrite のドキュメントに合わせて,次の意味で使います.一般的な語感とは向きが逆なので注意してください.
- 引用元チャンネル: メッセージリンクが投稿された側.プレビューが展開されるチャンネル
- リンク先チャンネル: リンクが指している側.引用されるメッセージがあるチャンネル
安全な条件は「引用元チャンネルを閲覧できるメンバー全員が,リンク先チャンネルも閲覧できること」です.
この判定を,ロールの集合の比較で行っていました.この方法では,メンバーを個別に指定した権限設定を表現できず,無視されていました.
Discord で「プライベートチャンネルを作って人を個別に追加する」と,@everyone の閲覧を拒否したうえで各メンバーに個別の許可を与える形になります.このとき引用元チャンネルを閲覧できるロールの集合は「管理者権限を持つロールのみ」(多くの場合は空集合)と評価され,これはほぼすべてのチャンネルの集合の部分集合になります.そのため「リンク先は引用元と同じかそれ以上に見える」という判定が通ってしまい,そのチャンネルのメンバーはロールで制限された任意のチャンネルを引用できました.
flowchart TB
subgraph judge["v1.4.1 までの判定(ロール集合の包含)"]
direction LR
a["引用元<br>個別許可で作った非公開チャンネル<br>閲覧できるロール = ∅"]
b["リンク先<br>@Dev 限定チャンネル<br>閲覧できるロール = {@Dev}"]
a -- "∅ ⊆ {@Dev} なので通過" --> b
end
subgraph fact["実際の閲覧者"]
direction LR
c["引用元にいる人 = C, D(個別許可)"]
d["リンク先を見られる人 = A, B"]
c -- "C, D は A, B に含まれない → 漏洩" --> d
end
judge --> fact
これは v1.3.0 で混入した退行 (regression) です.それ以前は「リンク先で @everyone の閲覧が拒否されていれば展開しない」という粗い規則で,このケースも弾いていました.v1.3.0 で「同程度に制限されたチャンネル同士の引用を許す」ために規則を緩めた際,メンバー個別の指定が考慮から漏れました.
修正: 引用元がメンバー個別の許可を含む場合は,全員が閲覧できるチャンネルのみ引用できるようにしました.個別に許可されたメンバーがロールから判別できない以上,その人が制限付きチャンネルを閲覧できるかは判定できないため,確実に安全な範囲に限る判断です.
報告者
3 件とも開発者 (@m1sk9) 自身が発見したもので,外部からの報告はありません.
babyrite に脆弱性を発見した場合は,Issue ではなく Security Advisory から報告してください.手順はセキュリティポリシーに記載しています.