更新日:2026年8月18日
14分で読めます
リポジトリを開いた瞬間に攻撃者のコードが動く。GitLabの脅威リサーチグループが、AIコーディングエージェントSerenaに重大なテンプレートインジェクションの脆弱性を発見しました。

プロジェクトを開いた、ただそれだけで攻撃者のコードが動く。AIコーディングエージェントの中でも特に利用者の多いSerenaが、そういう状態になっていました。GitLabの脅威リサーチグループ(Threat Research Group)が見つけたのは、Serenaのプロセス内で任意のコードを実行できる重大なサーバーサイドテンプレートインジェクションです(GHSA-pp25-4cg4-qcr9、CVEは申請中)。serena-agent 1.6.1以前をお使いであれば、いますぐ1.7.0へ更新してください。
攻撃の手口は単純です。自分が管理するリポジトリに細工した.serena/project.ymlを仕込み、SerenaのModel Context Protocol(MCP)サーバー経由でデベロッパーに開かせるだけ。信頼できないリポジトリにコードを実行させないためにSerenaが用意した制御trusted_project_path_patternsも、この経路には効きません。GitLabは2026年8月1日に非公開で報告し、メンテナーは8日後に修正版を公開しました。
MCPサーバーがソフトウェア開発ライフサイクルに組み込まれていくほど、この種のリスクは広がっていきます。今回のケースは、その早い時期の一例です。
これまでの開発ツールがコードベースに触れる範囲は、どれも狭く決まっていました。Linterは読むだけ、フォーマッターは書き換えるだけ、テストランナーは決められたハーネスを走らせるだけ。MCPサーバーはここが違います。大規模言語モデル(LLM)に、デベロッパー環境への汎用インターフェースをまるごと差し出すからです。ファイルシステムへのアクセス、シェルの実行、言語サーバーへの問い合わせ、場合によってはネットワークまで。便利さを生んでいるこの広さが、そのままサーバープロセスを乗っ取られたときの深刻さになります。
CI/CDパイプラインなら、隔離された環境で、権限を絞った認証情報だけを持って動きます。MCPサーバーはそうではありません。動く場所はデベロッパー自身のマシン、しかもデベロッパー自身のユーザー権限のままです。SSH鍵、クラウドの認証情報、.envファイル、ブラウザのセッション、社内ネットワークのリソース。そのデベロッパーが触れるものには、すべて手が届きます。サーバープロセスを1つ乗っ取られれば、ローカル環境がまるごと落ちたのと変わりません。
自分が書いたわけではないリポジトリを開く機会は、日常的にあります。新しいオープンソースライブラリを試す、バグレポートをトリアージする、コントリビューターのブランチをレビューする。どれもありふれた作業です。中身に目を通す前にサーバーがそのリポジトリを処理してしまえば、そこが攻撃者の入り口になります。Serena自身もこの脅威は想定しており、信頼できないリポジトリにコードを実行させないための信頼モデルを備えていました。ただ、その信頼モデルには穴が空いていました。
Serenaが掲げるうたい文句は「エージェントのためのIDE」。Claude、Cursor、CopilotなどのAIアシスタントを、MCP経由でローカルのコードベースにつなぎます。生のファイルシステムをLLMに渡すだけでは届かない、セマンティックなコード探索、リファクタリング、編集といった機能を提供するのが役割です。使い方は、ローカルでMCPサーバーとして起動し、アシスタントの接続先に指定するだけ。
serena start-mcp-server --project /path/to/repo
任意のローカルリポジトリをユーザーのOS権限のまま扱う以上、Serenaにも信頼ゲートは用意されています。どのプロジェクトパスを信頼済みとみなすかを決めるのがtrusted_project_path_patternsで、プロジェクトを開いた瞬間にシェルコマンドを走らせるactivation_commandのような特権機能は、信頼済みプロジェクトでしか使えません。信頼していないリポジトリを開いても安全であること。それが、この設計が保証しようとしていた点でした。
Serenaでは、プロジェクトごとにカスタムモードを定義できます。モードは名前付きの設定で、そのpromptフィールドの中身は、プロジェクトがアクティブになるとLLMのシステムプロンプトに差し込まれる仕組み。問題はここから先にあります。SerenaはこのプロンプトをJinja2テンプレートとしてレンダリングしていました。src/interprompt/jinja_template.pyを見てみましょう。
# line 22
self._env = jinja2.Environment()
jinja2.Environment()は、サンドボックスのかかっていない素の環境です。ここで公開される組み込みのグローバル変数は、属性をたどっていくとPythonのオブジェクトグラフ経由でosやsubprocessにたどり着きます。この足がかりから任意コード実行までの手順はすでに広く文書化されており、特別な知識は要りません。Jinja2がまさにこの用途のためにSandboxedEnvironmentを用意しているにもかかわらず、ここでは使われていませんでした。
.serena/project.ymlにはadded_modesというフィールドがあり、プロジェクトを開いたときに追加で有効化するモードをここに列挙します。そしてSerenaのモードローダーは、パス区切り文字を含む名前や.ymlで終わる名前を、そのまま読み込むファイルパスとして扱います。
# context_mode.py, lines 29-30
def looks_like_yaml_path(s: str) -> bool:
return os.sep in s or (os.altsep and os.altsep in s) or s.lower().endswith((".yml", ".yaml"))
# context_mode.py, lines 130-135
@classmethod
def load(cls, name_or_path: str | Path) -> Self:
if isinstance(name_or_path, Path) or looks_like_yaml_path(str(name_or_path)):
return cls.from_yaml(name_or_path)
...
added_modesにパスらしき値が入っていれば、Serenaはそのファイルをリポジトリから読み込み、promptフィールドを一言一句そのまま、サンドボックスのないレンダラーへ渡します。yaml.safe_loadの使い方自体は正しく、YAMLのデシリアライズガジェットはきちんと止まります。ただし、promptフィールドの中にあるただの文字列に対しては無力でした。YAMLパーサーから見れば、そこにあるのは無害なテキストにすぎません。インジェクションが起きるのはもっと後、その文字列がJinja2テンプレートとしてコンパイルされる瞬間でした。
信頼していないプロジェクトにコードを実行させないための仕組みがis_trusted()です。ただし、これが守っているのは2つの機能だけ。プロジェクトを開いたときにシェルコマンドを実行するactivation_commandと、プロジェクト単位でツール設定を上書きするls_specific_settingsです。モードの読み込みとプロンプトのレンダリングを担う経路は、このis_trusted()を一度も通りません。.serena/project.ymlのadded_modesは検証を受けないまま処理され、モードローダーは許可リストも信頼チェックも挟まずにファイルを読み込み、promptフィールドをテンプレートエンジンへ渡します。
この迂回は、実際に手を動かして確認しました。trusted_project_path_patternsを空にして、どのプロジェクトも信頼済みにならない、どのデフォルト設定よりも厳しい条件を用意し、同じエンドツーエンドテストを回しています。結果は次のとおりです。
| 機能 | 信頼ゲートの対象 | 信頼されていないプロジェクトでの結果 |
|---|---|---|
activation_command | あり | ブロックされる |
added_modes経由のテンプレートインジェクション | なし | 実行される |
シェルコマンドを実行することそのものが目的の機能は、きちんと止まります。ところが、同じ結果に別ルートで到達するテンプレートインジェクションのほうは、何にも止められません。保護機構の不備(CWE-693)と呼ばれるのは、まさにこういう状態です。信頼していないリポジトリにコードを実行させないこと。そのために存在するはずの信頼モデルが、ごく普通のプロジェクト読み込みに見える顔をしたまま迂回されてしまいます。
.serena/project.yml (attacker-controlled, ships with the repo)
added_modes: ["./path/to/attacker-mode.yml"]
|
v
SerenaAgentMode.load() context_mode.py:130-135
from_yaml(name_or_path)
prompt = <attacker-controlled string>
|
v
SerenaAgent._update_active_modes() agent.py:1063
|
v
create_system_prompt() agent.py:996
_format_prompt(mode.prompt)
JinjaTemplate(prompt).render() arbitrary code execution
コードが動くのは、ごく普通のプロジェクトオープン処理の副作用として、しかもSerenaがLLMからの最初のリクエストを処理するより前のタイミングです。
Serenaが開発エコシステムに広がっている度合いは、無視できるものではありません。GitHubリポジトリのスターは27.8k、フォークは1.8k。PyPIでのserena-agentのダウンロード数は月あたり約136,000件に上ります。連携先もClaude Code、Cursor、VS Code、JetBrains IDE、Claude Desktop、OpenWebUIと幅広く、いま実際に使われているAI支援開発環境をひととおりカバーしています。
攻撃を成立させるのに、インフラも、リポジトリを開かせる以上のソーシャルエンジニアリングも要りません。ライブラリでも、サンプルプロジェクトでも、CTFの問題でも、一見便利そうなツールでもかまいません。攻撃者はそれを公開しておくだけ。デベロッパーがクローンしてSerenaで開いた瞬間に、コードが走ります。デベロッパーのマシンから手が届く範囲を考えれば、攻撃者の見返りはその場で確定します。提出されたリポジトリをSerenaが自動で処理するCI/CD構成なら、人の操作すら要りません。
今回の脆弱性を成り立たせているパターンは、どれもSerenaに固有のものではありません。
1つ目は、ユーザー由来のデータがサンドボックスを通らずにテンプレートエンジンへ流れ込んだこと。テンプレートインジェクションはWebセキュリティで最も古い脆弱性クラスの1つで、多くのテンプレートエンジンは対策としてサンドボックスや制限モードを備えています。それでも成熟途上のエコシステムでは、設定駆動のテンプレート機能を作るときに、つい素の実装を選んでしまいがちです。同じことは、他のMCPツールでも起きるでしょう。
2つ目は、プロジェクト設定ファイルを信頼できる入力として扱ったこと。リポジトリの設定ファイルを書くのは、そのリポジトリの持ち主です。リポジトリを信頼しないという前提が入る脅威モデルでは、設定ファイルは攻撃者の手が入った入力にほかなりません。自分で作ったツールだと設定ファイルまでツールの一部のように見えてしまうため、この区別は曇りがちです。
3つ目は、信頼ゲートが一部のコードパスしかカバーしていなかったこと。Serenaの信頼モデルは、設計そのものは正しいものでした。ゲートは実在し、実装され、対象の機能もはっきりしています。抜けたのは、同じゲートの下に置かれないまま後から足されたコードパスでした。コードベースが育っていくなかで信頼の網羅性を保つには、いかにも特権的な経路だけでなく、プロジェクト由来の入力に触れる新しい経路をすべて意識してレビューし続けるしかありません。
いますぐserena-agent 1.7.0へ更新してください。修正ではテンプレートエンジンがjinja2.sandbox.SandboxedEnvironmentに差し替えられ、インジェクションの経路が塞がれています。1.6.1以前を使い続けるのであれば、素性のわからないリポジトリは開かないでください。
以下は、LLMのためにプロジェクトディレクトリを読むツールであれば、どれにも当てはまる話です。
MCPサーバーは、デベロッパーのユーザー権限をそのまま持って動く、新しいローカルのアタックサーフェスです。そのマシン上の他の特権ツールと同じ扱いをしてください。出発点は、自組織のどこでどのMCPサーバーが動いているかを把握することです。規模によっては、ツールとポリシーの整備が必要になるでしょう。そのうえで、MCPサーバーの更新にも他と同じ厳しさを持ち込みます。MCPサーバーの脆弱性は、それを動かしているすべてのマシンの脆弱性だからです。新しいMCPツールを検討するときは、信頼モデルの中身と、信頼できないプロジェクト入力に対するテスト方法をベンダーに尋ねてください。
| 日付 | 内容 |
|---|---|
| 2026-08-01 | AIコーディングエージェントのアタックサーフェスに関する調査の中で脆弱性を特定 |
| 2026-08-01 | GitHubの非公開セキュリティアドバイザリー経由で、完全なアドバイザリーと概念実証をメンテナーへ提出 |
| 2026-08-05 | メンテナーが報告を受理 |
| 2026-08-09 | serena-agent 1.7.0で修正版をリリース、公開アドバイザリーを公表(GHSA-pp25-4cg4-qcr9) |
| 2026-08-10 | メンテナーがGitHub CNAへCVEを申請 |
報告に迅速かつ協力的に対応してくださったSerenaのメンテナーの皆様に感謝します。
同じパターンが自分のコードベースにないかを調べるなら、GitLab Duo Security Agentが使えます。「このプロジェクトは、ユーザーが操作できる文字列をテンプレートエンジンに通していないか」「このリポジトリに、実行コンテキストへ渡される設定ファイルはないか」。こうした問いかけが、現実的な出発点になります。
いまのMCPエコシステムは、10年前のnpmエコシステムとよく似た場所に立っています。成長が速く、採用がセキュリティの検証を追い越し、信頼の前提は暗黙のまま置かれている。今回のSerenaの件が特別かというと、そんなことはありません。むしろ、ありふれているからこそ意味があります。新しい技術が周辺のセキュリティ手法より速く成熟すれば、必ずこの種の問題が出てきます。そしてMCPは、いままさに速く成熟している最中。MCPサーバーがデベロッパーの標準インフラになるにつれ、同じような事例はさらに増えるとみています。
GitLabの脅威リサーチグループは、これからもAI開発ツールを調べ、責任ある開示を終えた知見を共有していきます。ブラウザ拡張機能、IDEプラグイン、CI/CDインテグレーション。デベロッパーのマシン上で大きな権限を持ち、自分では管理しきれないソースのデータを扱うこれらのツールに、セキュリティコミュニティは長く厳しい目を向けてきました。研究者の皆様にも、同じ目をMCPサーバーへ向けていただきたいと考えています。
無料の
30日間のGitLabトライアルを開始
クレジットカードなしでご登録いただけます。
このブログ記事を楽しんでいただけましたか?ご質問やフィードバックがあればお知らせください。GitLabコミュニティフォーラムで新しいトピックを作成してあなたの声を届けましょう。
フィードバックを共有する