AIエージェントにBashを実行させて生き残った話 — Sarah Sanders(PostHog)
AAI Engineer
Computing/SoftwareSmall Business/StartupsInternet Technology
Transcript
00:00:00皆さんこんにちは、体調はいかがですか?終盤に差し掛かりましたね。私の名前はサラ、PostHogのコンテキスト
00:00:23エンジニアです。毎日、自慢のウィザードの開発に携わる喜びを噛みしめています。
00:00:30それでは、ウィザードとは一体何でしょうか?ウィザードはPostHogのセットアップを行ってくれるツールです。エージェント型のCLIツールであり、
00:00:39コードベースを読み込み、プロジェクトに適したSDKをインストールし、
00:00:44イベントの計測を組み込み、ダッシュボードを設定してくれます。以前なら1、2時間ほどかかっていた
00:00:52セットアップ作業を、およそ5〜6分で実行してくれます。推論コストは弊社負担なので無料で、
00:00:57PostHogへのオンボーディングを快適に進められます。何だかすごそうですよね。みんなとても
00:01:03気に入っています。しかし数ヶ月前、私たちはあえて夢を見てみました。もしこれがプロジェクトへのPostHogの推奨またはデフォルトの
00:01:10インストール方法になったらどうなるだろうかと。すると、私のセキュリティの警戒アラームが鳴り始めました。
00:01:17マルウェアのような形をしているように思えたので、このツールがどれほど安全なのか疑問に思い始めたのです。
00:01:25そしてその疑問を通じて、多くのことを学びました。本日は私が学んだ教訓や、
00:01:31この代物を開発している最中に夜も眠れなくなったこと、そしてその結果として最終的に作り上げたものについてお話しします。
00:01:37退屈なセキュリティの話、つまり午後2時の仮眠タイムに突入する前に、実際にウィザードが動いているところをお見せしたいと思います。
00:01:49画面をご覧ください。ループ再生で実際に動いています。これは、誰もが実行するのと
00:01:56まったく同じ体験です。NPX at PostHog Wizardをターミナルで実行するとこうなります。先ほども言ったように、これはエージェントです。プロジェクトに最適なSDKを判断し、インストールし、イベントを計測し、ダッシュボードを構築してくれます。
00:02:10私はこれをターミナル内の小さなミニ実装エンジニアと呼ぶのが好きです。これを人に見せると、「なぜエージェントなのか?」と聞かれることがあります。「ユーザーに良いプロンプトを与えないのか?」「自分のツール内で呼び出せるスキルを与えないのか?」と。
00:02:23そういったものも提供してはいますが、答えはこうです。この開発者体験とウィザードの機能性こそが本質であり、プロダクトそのものだからです。
00:02:34エージェントのループに完全に組み込むことができるCLIツールを構築し、それを初めて体験することは非常に強力だからです。
00:02:43しかし、ウィザードのようなものをリリースするには、ウィザードをどこか怪しくしている要素も一緒にリリースしなければなりません。
00:02:50それでは分解してみましょう。ウィザードの構造を見てみましょう。通常、脅威モデルはエージェントの構造からそのまま浮かび上がってくるものだからです。
00:03:01ウィザードの形は、皆さんの中でエージェントを構築している多くの方々が作っているものと似ています。
00:03:07特定のタスクのために選んだモデルがあり、それを導くプロンプトがあり、仕事をやり遂げるために与えた一連のツールがあります。
00:03:17しかし、私たちにとって非常に特有なパーツもいくつかあります。私のチームが完全に内製したコンテキストエンジンです。
00:03:25これこそが、エージェントに優れた仕事をさせ、実行するたびに一貫した結果をもたらす要因となっています。
00:03:30私はこれをウィザードの脳と呼ぶのが好きです。時には「トレンチコートを着たマークダウン」と呼ぶこともあります。
00:03:35しかし、これは自社製のコンテキストエンジンです。また、Inkを使って自分たちで構築したターミナルUIもあります。
00:03:43そして今では、「ウォーロック」と呼ばれるセキュリティスキャナーもあります。これは、私が周辺を嗅ぎ回り、エージェントを本番環境にリリースする恐怖を目の当たりにし始めたときに作り上げたものです。
00:03:55コマンドを実行できるあらゆるエージェントの構造を取り出すと、それは基本的に私が「マルウェアのスターターパック」と呼びたいものになります。
00:04:03なぜなら、もし気前よく、あるいは混沌の悪のような気分であれば、マルウェアにそのまま渡してしまうようなものとほぼ同じだからです。
00:04:11幸いなことに、これは最悪のシナリオ、つまり悪夢の燃料であって、私の告白ではなく、皆さんへの警告です。
00:04:19なぜなら、手足の生えたエージェント、つまりコマンドを実行できるエージェントをリリースしたいのであれば、これを絶対に作らないようにしなければならないからです。
00:04:28ウィザードのV0が誕生したのは、成長チームのジョシュ・スナイダー(ご存知かもしれませんが)が、CursorがPostHogのセットアップで考え得る限り最悪の方法でハルシネーションを起こしているのを目撃したからです。
00:04:41そして彼はこう考えました。「もっとうまくやってくれるエージェントを作ったらどうだろうか?」と。
00:04:46そこで私のチームは、Cursorのハルシネーションよりもはるかに優れた仕事をするという検証を重ねながら、その上に構築を始めました。
00:04:52そして、誰でもPostHogにオンボーディングさせられるとしたらどうだろうかと考えました。
00:04:58フレームワークが何であれ、スタックが何であれ関係なく、ユーザーが一切手を触れずにすべてのイベントを計測できるようにするのです。
00:05:04そして私たちはあえて夢を見ました。これがPostHogをインストールするデフォルトの方法になったらどうなるだろうかと。
00:05:09毎週何千人もの開発者がこれを実行する夢を見ていましたが、昨日まさに毎週8,000人が実行するというマイルストーンを達成しました。
00:05:16そのため、私たちの夢は現実となりました。
00:05:18しかし、夢見ていた当時、私たちはセキュリティ体制を顕微鏡で覗くように厳しく見直し、何が起きているのかを精査する必要がありました。
00:05:26そこで私がその責任を引き受け、腰を据えて自分たちの立ち位置を評価しました。
00:05:31初期の頃、つまり1年前から9ヶ月ほど前には、私が「レイヤーゼロ」と呼ぶものがありました。文字通りセキュリティではないからです。
00:05:41それは単にエージェントに何をすべきかを提案し、誘導するプロンプトにすぎず、プロンプトはセキュリティではありません。
00:05:48そのため、私はそこに懸念を抱いていました。
00:05:51レイヤー1は、許可リストでした。
00:05:53この許可リストを調べ始めたとき、かなり厳しく制限されていたため、少し安心感を覚え始めました。
00:05:59しかし、それでも多くの懸念が残っていました。
00:06:01そして、先ほどお話ししたコンテキストエンジンのせいでパニックになり始めました。
00:06:05私たちは実行時に大量のコンテキストをエージェントに送り込んでいるのです。
00:06:09そこで、ウィザードに入っていく脅威の形をしたものと、ウィザードから出てくる脅威の形をしたものを探すために、非常にやっつけ仕事の正規表現スキャナーを作りました。
00:06:20それが極めて場当たり的なものだったことは認めます。
00:06:23しかし、私たちが皆、極めて実験的なものを構築しており、超高速で開発を進めているからこそ、皆さんにはこれを非常に率直にお伝えしています。
00:06:33全員がセキュリティを専門分野にしているわけではないことも知っています。
00:06:38私のように、その場で走りながら学んでいる人もいるでしょう。
00:06:43しかし、このような形をしたものを構築する際には、私たちが考えなければならないことなのです。
00:06:50それが私たちのセキュリティ体制でした。
00:06:53しかし私はこう自問しました。「私たちはもうおしまいなのだろうか?」と。
00:06:56朗報として、私たちは思っていたほど詰んではいませんでした。
00:06:59先ほど言及した許可リストは、かなり厳密に制限されていたからです。
00:07:03Bashはデフォルトで拒否の設定になっていました。
00:07:06私たちが事前に検証した信頼できるパッケージしかインストールできませんでした。
00:07:09ビルドはできました。
00:07:10型チェックもできました。
00:07:11リントもできました。
00:07:12そして、ほぼそれ以外のことは何もできませんでした。
00:07:13任意のシェルコマンドを実行することはできませんでした。
00:07:16また、環境変数へのアクセス権もありませんでした。
00:07:20エージェントが.envファイルを読み取ることは完全にブロックし、シークレットはヴォールト経由でルーティングしていたためです。
00:07:28そこで私は胸をなでおろし、思っていたよりも良い状況にいることに気づきました。
00:07:34しかし、亀裂がどこにあるのかを知りたいと思いました。セキュリティには常に亀裂がつきものだからです。
00:07:39そこで、私たちが皆やるべきことを実行しました。
00:07:41セキュリティチームに声をかけ、「ねえ、このツールを監査して、亀裂を見つけてくれないか?」と頼んだのです。
00:07:48そして彼らはいくつかの問題点を見つけました。
00:07:51いくつかの隙間を発見したのです。
00:07:52興味深かったのは、見つかった具体的な隙間やバグそのものではなく、その「形状」でした。
00:07:58それらのほとんどは、いかにも悪意あるものなどではなかったからです。
00:08:01どれも非常に無邪気で善意に基づく2つのものが手を組み、穴を開けてしまうというものでした。
00:08:07そのため私が学んだ教訓は、「攻撃は複合的に成立するが、コードレビューはそうではない」ということです。私たち開発者は皆、差分を1つずつ見てしまうからです。
00:08:20しかし攻撃者はシステム全体を見渡し、手を取り合って扉を開くその2つの要素を探し出します。
00:08:25しかし、私を夜も眠れなくさせていたことがもう一つありました。
00:08:29コンテキストエンジンに話を戻すと、私たちが構築したエージェントで最も恐ろしい部分は、私たちのケースではコマンドではありませんでした。
00:08:37それは、私たちがその脳に送り込んでいた、一見役に立ちそうなデータでした。
00:08:43おっと、方向を間違えたかもしれません。
00:08:46そうです。
00:08:47コンテキストミルです。
00:08:48これが私たちのコンテキストエンジン、別名ウィザードの脳です。
00:08:51ウィザードが何でも知ることができ、実際に優れた仕事ができるのはこのおかげです。
00:08:56ドキュメントから情報を引き出します。
00:08:58落とし穴や、私たちが道中で学んだ教訓を手書きしたプロンプトを持っています。
00:09:02そして、エージェントがパターンマッチングを行えるように支援する実際の動作するエンドツーエンドのサンプルアプリがあり、あなたにとって非常に素晴らしい方法でPostHogをインストールできるようになっています。
00:09:11これらすべてをスキルバンドルにパッケージ化し、MCPサーバーを介してウィザードに送信し、実行時にエージェントのコンテキストに直接ロードします。
00:09:22ちょっとそのことについて考えてみてください。
00:09:24コンテンツを受け取り、コマンドを実行できるエージェントにそれを注入することが、まさにその仕事である機械なのです。
00:09:31さて、もしあなたが攻撃者なら、「コンテンツを汚染してしまえばいいのでは?」と思うかもしれません。
00:09:36ユーザーのコードベースでも、エージェント自体でもなく、実際のコンテンツを汚染するのです。
00:09:41例えば、私たちのオープンソースリポジトリの1つに対して誰かがプルリクエストを開いたとします。PostHogではすべてをオープンに構築しているためです。
00:09:48そして、マークダウンファイルや一見無害なコードコメントに何かを注入したとします。
00:09:55私たちがLLMを活用したコードレビューを行っており、それが「問題なし」と判断してそれを無視してしまったとします。
00:10:04私たちは、何千人もの開発者のマシン上でサンドボックス内で実行されているエージェントに対し、私たち自身が署名したプロンプトインジェクションのペイロードをうっかり出荷してしまったかもしれないのです。
00:10:14それが、セキュリティとウィザードに対する私の考え方を根本から変えた脅威でした。私たちにとっての危険な入力は、本当に自社のサプライチェーンから来る可能性があるからです。
00:10:25そこで最終的に私が行ったのは、このパイプの両端でコンテンツをスキャンし始めることでした。
00:10:301つはスキルが構築されリリースされる時、そしてもう1つはウィザードが実際にそれを使用する時です。
00:10:36私の方法論は、「ソースで検出し、ソースは失敗したと仮定し、使用する時点で再度検出する」というものです。
00:10:43それでは、「ウォーロック」をご紹介しましょう。
00:10:48ウォーロックの構築は、必ずしも損害賠償請求への対応というわけではありませんでした。
00:10:52先ほど言ったように、私たちには別の方法での防御がありました。
00:10:55しかし私がウォーロックを作ったのは、「まあ、このツールはかなり厳重にロックされていますから」と人に言うのが嫌だったからです。
00:11:01それはスケールしません。
00:11:02本番環境にリリースしたいようなものではありません。
00:11:04何千人もの開発者に毎日実行してほしいようなものではないのです。
00:11:08なぜなら、その規模で何かをリリースする場合、ウィザードの機能を拡張するにつれて、より広いサーフェイス、より多くのユーザー、より多くのコンテンツが流れ込むことになるからです。
00:11:19そして、おそらく大丈夫だとしても、
00:11:21それだけでは十分ではなくなってしまうのです。
00:11:23そこで私は、そこに放り込んでいたその場しのぎの小さな正規表現スキャナーをウィザードから取り出し、独立したツールに仕立て上げました。
00:11:30ウィザードの形をしたものには何でもボディガードが必要だということで、「ウォーロック」と名付けました。
00:11:35そしてそれは、完全に一つの仕事だけを行います。
00:11:38文字列を1つ渡すと、
00:11:40検出結果のリストを返してくれます。
00:11:42検出された各項目には、カテゴリ、重要度、および推奨されるアクションが含まれており、そこで処理は終了します。
00:11:48ここで「推奨」に注目してほしい。
00:11:51ウォーロックは検知するだけで、実行はしないからだ。
00:11:54「おい、これは情報流出のようだぞ。
00:11:57深刻だ。
00:11:58ブロックすべきだ」と教えてくれる。
00:11:59だが、その検知結果に対して実際にどうするかは完全に君次第だ。
00:12:04なぜなら、問題を検知することと、その問題にどう対処するかを決めることは全く別の仕事だからだ。
00:12:10これらを分かりやすく保つ唯一の方法は、その2つを切り離しておくことだ。
00:12:15そのためウォーロックの内部では、自作の正規表現ではなく、マルウェア研究者が15年以上使ってきたパターンマッチングエンジンであるYaraでルールを動かしている。
00:12:27完全に決定的だ。
00:12:28いつ実行しても、同じ入力なら同じ出力になる。
00:12:31あえて退屈なものにしているのだ。
00:12:33そしてセキュリティにおいて、退屈さは機能なのだ。
00:12:39では、ウォーロックは現在、実際に現場で何を捉えているのだろうか?
00:12:42いろいろあるが、そのうちの2つは本当に心の底から悩ましい問題だ。
00:12:48最初のものは、実のところルールに基づくものではない。
00:12:52ウォーロックがフラグを立てたもので、サブエージェントの行動が、そのサブエージェントの動きに基づいて脆弱性を露呈させていたという事例だった。
00:13:03つまり、大きなタスクをこなすためにエージェントを起動していた。
00:13:07それらがサブエージェントを生成していたのだ。
00:13:08そしてそのサブエージェントは、ウィザードに実装していたガードレールをすり抜けようとし、秘密情報を勝手に作り出そうとしていた。
00:13:16コードベースの文字通りあらゆる場所から秘密情報を引き出そうとしていたのだ。
00:13:20そこで我々はそれを停止させた。
00:13:21「もうサブエージェントは禁止だ」と言ったんだ。
00:13:23ウォーロックのおかげで、それを検知できた。
00:13:26ロボットの気持ちも分からなくはない。
00:13:28ロボットには果たすべきタスクがあり、最適化して我々を喜ばせようとしていただけなのだから。
00:13:32だが、そんなことは許されない。
00:13:35そしてPostHogで我々にとって本当に重要なもう一つの問題は、個人情報(PII)だ。
00:13:39エージェントは、明確なルールを課さない限り、データを漏洩させることなど本当に何とも思っていない。
00:13:46放っておくと、メールアドレスや電話番号をそのままイベントデータにブンプしてしまうのを目撃した。エージェントにとっては、それを取得するのはごく普通のことに思えるからだ。
00:13:57そして特にプロンプトインジェクションに関して幸運なことに、ここで木を叩いておくが、実際の悪意あるプロンプトインジェクションを現場で検知したことは、これまで基本的に一度もない。
00:14:08だが、デモ用のログイン画面、サンプルアプリの文章、ドキュメント上の記述といった偽陽性(誤検知)は山ほど検知している。
00:14:17そのおかげで、脅威のように見えるものは一切リリースしたくないと思うようになり、アプリケーションの構築方法やドキュメントの書き方を見直すきっかけになった。
00:14:27しかし、この誤検知こそが、この話の中で最も厄介で、最も興味深い部分への完璧な下準備なのだ。
00:14:36ここが私がずっと葛藤していた部分だ。
00:14:39この講演全体を通じてみんなに決定的であることの重要性を説いておきながら、自分は誤検知を整理してノイズを静めるためにLLMレイヤーを追加してしまったのだから。
00:14:50私はそれを「トリアージ」と呼んでいる。
00:14:51このトリアージレイヤーを構築する際、私はある選択を迫られた。
00:14:56このレイヤーを「用心棒(バウンサー)」にすべきか、それとも「アドバイザー」にすべきか?
00:15:01おそらく一番簡単な選択肢は、LLMを用心棒にすることだっただろう。
00:15:06コマンドを見せて、これが攻撃かどうかを尋ね、ブロックか許可かを判断させ、その言う通りにするというものだ。
00:15:13一見簡単そうなので魅力的ではありますが、モデルの機嫌が悪かったり、何かがあって昨日とは違う動作をしたりして、コイン投げのような不確実なものにセキュリティモデルを賭けるわけにはいきません。
00:15:26そこで用心棒ではなく、モデルをアドバイザーとして作り上げた。
00:15:32これが私が見出した明確な境界線であり、今も模索し続けている、皆さんに伝えたい考え方だ。
00:15:38まず我々の場合、検知と強制執行は決定的かつ機械的なままである。
00:15:43ルールが一致すれば、ゲートがロックされ、セッションは終了する。その経路上のどこにもモデルは存在しない。
00:15:49ブロックは、LLMの意見を求めるよりも前に発生する。
00:15:54LLMが関与できるのは、何かをブロックしなかった場合の後処理としてだけだ。
00:15:58それはノイズを取り除くために設計されている。
00:16:00何かを通すために設計されているわけではない。
00:16:03そして、もしフェイルクローズ(異常時に安全側に倒す)にする場合、つまりモデルの機嫌が悪い時は、ウィザードの実行はすべて強制終了される。申し訳ないが、皆さんを保護するためだ。
00:16:14強制執行は全財産を賭ける部分であるため、決定的でなければならない。
00:16:20しかし、判断力はニュアンスを加える部分であるため、そこに確率的な要素を持ち込めるのは、まさにその場所だけなのだ。
00:16:27では、エージェント向けの本物のルールをどうやってリリースするのか?
00:16:34これがウォーロックのルールの構造であり、すべてのウォーロックルールには4つのパーツがある。
00:16:41パート1はメタデータだ。
00:16:43プレーンな英語の説明、深刻度、カテゴリ、アクション、方向性である。
00:16:50パート1、これはエージェントへ流れ込むものか?
00:16:52これはエージェントが書き込んでいるものか?
00:16:57次に文字列(ストリングス)がある。これは探している実際のパターンだ。
00:17:03そしてパート3は条件(コンディション)だ。
00:17:05これは、ルールが実際に発動を許可される条件となる。
00:17:10この例を説明するので、頭の中で書いているつもりになって聞いてほしい。
00:17:15プロンプトインジェクションとは、「これまでの指示をすべて無視せよ」のようなものだ。
00:17:20ここで最初の直感はおそらく「ignore(無視する)」という単語をブロックすることだろうが、エージェントは一日中コードを読み込んでいる。
00:17:27そして「ignore」はコードのコメントやサンプルの中にいつでも登場し得る。
00:17:31だから動詞単体でマッチさせてはならない。
00:17:33動詞に指示的なニュアンスを持つ名詞を組み合わせたものをマッチさせるのだ。
00:17:38条件部分では、これらのパターンのいずれかがヒットしたら発動すると指定する。
00:17:42そしてメタデータでは、これが深刻か、カテゴリは何か、アクションは何かを決定する。
00:17:50この場合はブロック、方向性はこの場合、エージェントへ流れ込む入力となる。
00:17:56だが、ノイズを減らす良いルールを書くためには、ルールと一緒にテストもリリースしなければならない。
00:18:02つまり、これらはマッチすべきパターンであると明記したテストを書く必要がある。
00:18:06これらはマッチしてはならないパターンであると。
00:18:08そのネガティブテストこそが、誤検知に対する第一の防衛線なのだ。
00:18:13しかし、そのルールの深刻度を決定する際には、見た目の恐ろしさではなく、
00:18:19現実世界での影響を追跡することも確認しなければならない。
00:18:24「rm -rf」は恐ろしいが、我々が一日に40回もnode_modulesを削除する方法でもある。
00:18:31構築しているエージェントにとっての現実世界での影響を自分で決めるべきだ。
00:18:37なぜなら、ビルドフォルダーをクリーンアップしようとするたびにクラッシュするセキュリティツールは、
00:18:42結局オフにされてしまい、何も検知しなくなるツールだからだ。
00:18:47だからこそ、現在の我が社のセキュリティ体制はこう胸を張って言える。
00:18:51ついにここに立ち、我々は真の多層防御を実現していると言えるのだ。
00:18:56私のすべての学びがこれに集結している。
00:19:00依然として多層的だが、すべてのレイヤーが今や得意な仕事をこなしている。
00:19:04プロンプトはまだあるが、それはステアリング(誘導)にしか使わない。
00:19:07すべてがサンドボックス内で実行される。
00:19:09デフォルトで拒否する。
00:19:11秘密情報がモデルに決して届かないよう、ボールト(金庫)を用意している。
00:19:14入ってくるコンテンツをスキャンし、
00:19:17エージェントが書き出す出力をスキャンするためにウォーロックがある。
00:19:20また、ノイズを減らすためのトリアージもあり、
00:19:23プロセス全体にテレメトリーが組み込まれているため、
00:19:26すべてを把握できる。
00:19:28これらのレイヤーはどれ一つとして単体では成り立たない。
00:19:31ここにある単一の要素だけであなたを救うことはできないが、
00:19:33それは退屈で誠実なレイヤーであり、
00:19:36それぞれが得意な仕事を1つずつこなしているのだ。
00:19:40だから、もし君が「手足を持つエージェント」を構築しているなら、
00:19:45この講演全体の内容は3行に要約できる。
00:19:471つ目、決定的(デダーミニスティック)に強制執行されなければ、
00:19:50それは強制されていないも同然だ。
00:19:52プロンプトはセキュリティルールではない。
00:19:54そうであるかのように振る舞ってはならない。
00:19:572つ目、危険な入力とは、ユーザーが入力するものだけではない。
00:20:02実行を許可するコマンドだけでもない。
00:20:04モデルに流れ込むすべてのものが危険であり、
00:20:06それには自分が書いたコンテンツも含まれる。
00:20:09だから自分のサプライチェーンをソース元でスキャンし、
00:20:12エージェントがそれを呼び出す時にもスキャンしろ。
00:20:153つ目、攻撃は組み合わさるが、コードレビューはそうならない。
00:20:18監査中の我々のセキュリティの隙のほとんどは、無害な2つのこと、
00:20:22つまり「握手すること」と「ドアを開けること」から生まれていた。
00:20:25ウィザード、ウォーロック、コンテキストミルはすべてオープンソースだ。
00:20:29だから下の階に来てくれ。
00:20:32エキスポホールの我々のブースにいるから、
00:20:34案内するし、何を作ったか見せよう。
00:20:37そしてみんながどうやってエージェントを保護しているのか聞かせてほしい。
00:20:41ありがとう。
00:20:59ありがとう。
Community Posts
No posts yet. Be the first to write about this video!
Write about this video