セキュリティモデル
Wippyのセキュリティモデルは、コードがアクセスできるもの、できないもの、そしてそれらの境界を誰が強制するのかを定義します。多くのフレームワークが1つにまとめてしまう2つの層で機能するため、構築を始める前に読む価値があります。ランタイムは各プロセスを隔離し、危険なケイパビリティをそもそも存在させません。そして属性ベースのポリシー層が、プロセスが使用を許されるレジストリケイパビリティを統制します。両方を理解すると、アプリケーションの構成の仕方が変わります。
信頼モデル
Wippyの隔離層は、プロセスにアンビエント権限を一切与えません。新規のLuaプロセスやWASMプロセスは、ファイルシステム、ネットワーク、ホストOS、他プロセスのメモリに触れられません。それらのケイパビリティが環境に存在しないからです。ケイパビリティはレジストリを通じてのみ到達します。すなわち、プロセスに明示的に付与された関数、ツール、コネクション、設定です。
その上で、レジストリケイパビリティへのアクセスは属性ベースアクセス制御(ABAC)によって統制されます。保護された操作はすべて、現在のアクターのセキュリティスコープ、つまりリソースに対するアクションを許可または拒否する一連のポリシー(任意でアクターとリソースのメタデータを条件にできます)に照らして検査されます。これは宣言的です。ポリシーはアプリケーションコードではなく設定で定義します。
アクターとスコープの両方を伴ってプロセスが動作している場合、アクセスはデフォルト拒否です。ポリシーが明示的に許可し、かつどのポリシーも拒否しない場合にのみ、リクエストは許可されます。ストリクトモードは不完全なケース、つまりアクターもスコープも確立されていない場合を統制します。これはデフォルトで有効であり、不完全なコンテキストは拒否されます。ランタイム設定でsecurity.strict_mode: falseを指定すると、代わりに寛容な挙動を選択できます。想定しておくべき帰結は、セキュリティコンテキストが宣言されていないプロセスはデフォルトではあらゆる検査に失敗するということです。そのようなプロセスにはエントリ上でsecurity:ブロックを与えるか、コンテキストを供給する経路から起動してください。最小権限のポリシーと組み合わせることで、不在による拒否という隔離の上に、フェイルクローズドな認可が得られます。ポリシー構文、評価ルール、security:ブロックの形についてはセキュリティリファレンスを参照してください。
プロセス隔離
Wippyにおけるすべての実行単位は、独自の組み込みインタープリタ(LuaまたはWASM)を持つ隔離されたプロセスで動作します。
プロセスが持つもの: 独自のメモリ空間(Luaの場合、ベースラインオーバーヘッドは約13 KB)。レジストリのスコープされたビュー。アクターのアイデンティティとセキュリティスコープ。クラッシュ復旧と再起動制限を備えた監督されたライフサイクル。
プロセスが持たないもの: ファイルシステムへのアクセス(レジストリで制御されたファイルシステムエントリ経由を除く)。ネットワークへのアクセス(付与されたHTTPクライアントやツールモジュール経由を除く)。他プロセスのメモリへのアクセス。ホストとなるGoランタイムへのアクセス。環境変数へのアクセス(付与された環境エントリ経由を除く)。
隔離の強制方法: 各Luaプロセスは最小限の標準ライブラリから開始します。ファイルI/O、OSプロセスへのアクセス、動的なコードロード、ネットワークは一切ロードされないため、環境に存在せず、プロセスは存在しないものを復元できません。モジュールのロードは制限されます。requireは、そのプロセスに明示的に付与されたモジュールとレジストリエントリのみを解決し、ファイルシステムの検索パスはありません。WASMプロセスはWASIを通じて同等の隔離を実現します。そのエントリ向けに設定されたホスト関数とマウント済みファイルシステムエントリのみに到達できます。
これは(seccompやAppArmorのような)ランタイム権限によるサンドボックス化ではありません。不在によるサンドボックス化です。危険なケイパビリティは決してロードされないため、悪用も回避も権限昇格もできません。
ケイパビリティ制御
レジストリはWippyのケイパビリティストアであり、セキュリティポリシーはその認可層です。
すべてのケイパビリティはレジストリエントリです。 関数、ツール、エージェント定義、データベースコネクション、環境参照、設定値、スケジュールされたタスクはすべて、宣言された種別、スキーマ、メタデータを持つレジストリエントリです。エントリは登録時にその種別のハンドラによって検証されます。
エントリIDは名前空間付きです。 IDはコロン1つのnamespace:nameという形式を持ち、名前空間はドット区切りのセグメントで階層化されます。例えばtenant_acme.tools:read(名前空間tenant_acme.tools、名前read)です。ポリシーはアクションとリソースにマッチし、リソースパターンは名前空間のプレフィックスを対象にできるため、単一のルールで名前空間全体をカバーできます。
ポリシーがアクセスを決定します。 ケイパビリティへの各アクセス(レジストリ参照、関数呼び出し、データベースハンドル、ファイルオープン)は、アクターのスコープに照らして検査されます。ポリシーは、対象とするアクションとリソース、許可または拒否の効果、そして任意でアクターとリソースのメタデータに対する条件を宣言します。評価は起動時に一度ではなく、アクセスごとに行われます。いずれかのポリシーが拒否すればアクセスは拒否され、少なくとも1つが許可しどれも拒否しなければ許可され、どのポリシーもマッチしなければ拒否されます。(コンテキストにアクターもスコープもまったくない場合、その不完全なケースはポリシー評価ではなくストリクトモードによって解決されます。)
コンテキストは宣言されるものであり、どこからともなく継承されるものではありません。 関数は呼び出し元のアクターとスコープを継承します。スポーンされたプロセスも同様に継承します。そのフレームはスポーン元のフレームからフォークされ、自身のエントリ上のsecurity:ブロックがその継承されたコンテキストを変更します。ブロックが指定するactorは継承されたアクターを置き換え、レジストリIDで列挙されたポリシーとポリシーグループは継承されたスコープにマージされます。解決はアトミックです。指定されたポリシーやグループのいずれかが欠けていれば、部分的なスコープで進行するのではなくスポーンが失敗します。CLIコマンドはさらにmeta.command.securityを宣言でき、これはオペレーター自身がコマンドを起動した信頼された起動経路でのみ適用されます。
ツール引数はスキーマで形が決まります。 ツールは入力に対するJSON Schemaを宣言します。そのスキーマはモデルに渡され、モデルは準拠した引数を生成します。そしてツールへのアクセスは呼び出しが実行される前にポリシー検査されます。
データ境界
データベースコネクションはレジストリエントリです。 プロセスが自前で接続文字列を組み立てることはありません。レジストリIDでコネクションを要求し、その要求はハンドルが返される前にポリシー検査されます。テナントBのデータベースエントリをポリシーで付与されていないプロセスは、そのハンドルを取得できません。
LLMのAPIキーは環境システムに置かれます。 Claude、GPT、その他プロバイダーのキーは環境システムから読み取られます(例えばenv.storage.osエントリを通じて公開されるOS環境変数を、env.variableエントリが参照し、その読み取りはenv.getアクションでポリシー検査されます)。プロバイダーは内部でそれらを読み取ります。プロセス引数で渡されることも、呼び出し側のコードに返されることもありません。
ファイルとブロブのストレージも同じモデルに従います。 プロセスはファイルシステムまたはクラウドストレージのレジストリエントリを通じて読み書きし、各アクセスがポリシー検査されます。WASMプロセスは、そのエントリ向けに明示的にマウントされたファイルシステムエントリを通じてのみファイルにアクセスします。
エージェントのセキュリティ
エージェントはツールを使うLLM駆動のプロセスです。あなたのコードが直接制御しない判断を実行時に下すため、その境界は重要です。Wippyはこれを、他のプロセスと同じレジストリおよびポリシーの仕組みで扱います。
ツールへのアクセス。 エージェントは定義に列挙されたツールしか呼び出せず、各ツールの実行はポリシー検査されるfuncs.callを通ります。拒否された呼び出しは、ツール関数が動作する前に失敗します。顧客データの読み取りは可能だが削除は不可というエージェントは、定義に削除ツールを持たないか、そのアクションをポリシーで拒否されているかのいずれかです。
外部ツールとMCPツール。 Wippyは外部ツールを利用でき、また自身のツールをModel Context Protocolで公開できます。利用するツールは、ネイティブツールと同じ関数呼び出し経路とポリシー検査を通ります。Wippyが外部MCPクライアントに公開するツールは、クライアントが実行できるアクションを制限するスコープ付きで失効可能なアクセストークンによってゲートされます。
構造化出力。 LLMモジュールは、プロバイダーのネイティブな構造化出力サポートを用いてスキーマ制約付き(構造化)出力を要求できるため、エージェントの出力を宣言された形に従わせられます。
可観測性。 OpenTelemetryを有効にすると、LLMプロバイダーの呼び出しとツールの呼び出しがトレースされ、トークン使用量はusage-trackerコントラクトを通じて記録されます。これにより、エージェントが何を呼び出し何を消費したかの監査証跡が得られます。可観測性を参照してください。
自己変更の境界。 ある名前空間でツールを作成することを許可されたエージェントでも、別の名前空間にある自身の定義への書き込みは拒否できます。レジストリへの書き込みはポリシー検査されるアクションであるため、エージェント自身の名前空間に対する拒否ポリシーは、エージェントが自らを編集したり自らに新たなアクセスを付与したりすることを防ぎます。
マルチテナントの強制
複数の顧客が単一のWippyインスタンスを共有するデプロイでは、隔離はテナントIDを確認するアプリケーションコードではなく、あらゆる操作の実行前に行われるポリシー評価によって強制されます。
テナント隔離はポリシーで強制されます。 各テナントにアクターと、そのテナントの名前空間のみをカバーするポリシーを持つスコープを与えます。ストリクトモードが有効なら、テナントのプロセスはコードが動作する前にスコープ外のリソースへのアクセスを拒否されます。実効的な隔離はテナントごとのポリシーを書くことに依存します。ランタイムはそれらを強制しますが、テナント構造を推測してはくれません。
テナントをまたぐアクセスは明示的です。 テナント間で共有されるケイパビリティは、各テナントのポリシーが許可する共有名前空間に置かれます。共有は名前空間ごとのオプトインです。
並行性はホストで制限されます。 プロセスホストはワーカープールで並行性を制限します。プロセスグループ(pg.scope)は隔離されたクラスタ全体のメンバーシップとブロードキャストの名前空間を提供し、グループ数とメンバー数の上限を設定できます。テナントごとのCPUやメモリの上限はランタイムの組み込み機能ではありません。それらはインフラ層で強制してください。
マルチテナントアーキテクチャの専用ガイドを計画中です。
範囲と制限
Wippyのセキュリティモデルは、プロセス隔離、ケイパビリティ制御、データ境界をカバーします。以下はランタイムの範囲外であり、インフラ側の責任として残ります。
保存データの暗号化。 データベース、ディスク、ブロブストレージの暗号化は、基盤となるインフラ(PostgreSQL TDE、ディスク暗号化など)が扱います。Wippyはストレージ層が暗号化を扱うことを前提とします。
ネットワークレベルの隔離。 プロセス隔離はアプリケーション層で行われます。Wippyとその依存先(データベース、LLM API、外部サービス)の間のネットワーク分離は、VPC、セキュリティグループ、ファイアウォールといったインフラが扱います。
アイデンティティ管理。 認証(ユーザーが誰であるかの検証)はあなたの認証層が扱います。Wippyのセキュリティモデルは認証の後から始まります。認証済みユーザーのプロセスが何をできるかを制御するものであり、ユーザーが誰であるかを扱うものではありません。アクターとスコープを運ぶトークンは、トークンストアを通じて発行および検証できます。
インフラの監査ログ。 Wippyのトレーシングはプロセスレベルの操作(関数呼び出し、ツール呼び出し、プロセス活動)をカバーします。インフラレベルのアクセス(サーバーへのSSH、データベース管理操作)はインフラのツールで監査すべきです。
よくある質問
あるテナントのエージェントが別のテナントのデータにアクセスできますか? 各テナントのリソースがポリシーでスコープされていれば、できません。テナントごとのポリシーとストリクトモードのもとでは、ランタイムはエージェントのコードが動作する前にテナントのスコープ外のリソースへのアクセスを拒否します。
エージェントは自身の権限を昇格できますか? 自身の定義への書き込みをポリシーが許可している場合に限ります。レジストリへの書き込みはポリシー検査されるため、エージェント自身の名前空間に対する拒否ポリシーが自己変更を防ぎます。ある名前空間でツールを作成できるエージェントであっても、自身のスコープがまだカバーしていない名前空間へのアクセスを自らに付与することはできません。
エージェントが何をしたかを確認するには? OpenTelemetryを有効にすると、LLMとツールの呼び出しがトレースされ、トークン使用量はusage-trackerコントラクトを通じて記録されます。可観測性を参照してください。
エージェントが予期しない振る舞いをしたらどうなりますか? サンドボックスによって封じ込められます。ファイルシステムもネットワークもOSもなく、付与された範囲を超えて他プロセスにアクセスすることもできません。ポリシーが許可する、自身の定義にあるツールしか呼び出せず、それらの呼び出しはログに残ります。
テナント隔離を強制するのは私のコードですか、ランタイムですか? ランタイムです。ポリシーエンジンは操作の実行前に各アクセスを評価します。あなたの仕事はテナントごとのポリシーを書くことで、ランタイムがそれを強制します。
外部のMCPツールはどのように保護されますか? MCP経由で利用するツールは、ネイティブツールと同じ関数呼び出し経路とポリシー検査を通ります。Wippyが外部MCPクライアントに公開するツールは、スコープ付きで失効可能なアクセストークンによってゲートされます。MCPサービスを接続してもセキュリティモデルは回避されません。
セキュリティリファレンス
| 関心事 | Wippyのアプローチ |
|---|---|
| プロセス隔離 | プロセスごとに独立したインタープリタ(LuaまたはWASM)、共有メモリなし |
| デフォルトのアクセス | アクターとスコープの両方が設定されている場合、マッチしないポリシーは拒否。デフォルトで有効なストリクトモードは、アクターもスコープも確立されていない場合に拒否 |
| コンテキストの宣言 | エントリ上のsecurity:ブロック(アクター、ポリシー、グループ)。解決はアトミックでフェイルクローズド |
| サプライチェーン | モジュールパックはインストール時と起動時にダイジェストで検証。不一致ならモジュールを拒否 |
| ノード間の信頼 | 相互認証されたノード間メッシュ。ノードごとのed25519アイデンティティと明示的な信頼ピアマップ |
| ワークフローへの伝播 | アクターとスコープは署名済みでオーディエンス束縛されたヘッダーとしてTemporalへ運ばれる。検証失敗は実行を失敗させる |
| ケイパビリティ制御 | レジストリエントリを属性ベースのセキュリティポリシー(アクター、スコープ、アクション、リソース)で統制 |
| データ境界 | コネクションとストレージはレジストリエントリ。各アクセスはエントリIDでポリシー検査される |
| APIキー管理 | 環境システムに保存され、プロバイダーが内部で読み取り、プロセスのコードには公開されない |
| エージェントのツール制御 | ツールはエージェントの定義に限定。各呼び出しはfuncs.callのポリシーで検査 |
| 外部ツール (MCP) | 同じ関数呼び出し経路とポリシー検査。公開するツールはスコープ付きトークンでゲート |
| エージェントの監査証跡 | OpenTelemetryトレーシング(有効時)とusage-trackerの記録 |
| マルチテナント隔離 | テナントごとのポリシーとスコープを、各操作の前にランタイムが評価 |
| 並行性の制限 | ホストのワーカープールで制限。テナントごとのCPU/メモリ上限は組み込みなし |
| 自己変更 | レジストリ書き込みアクションへの拒否ポリシーが、エージェントによる自身の定義の編集を防ぐ |
関連項目
- セキュリティリファレンス - ポリシー、スコープ、アクター、トークンストア、
security:ブロック - 依存関係管理 - モジュールのダイジェスト検証
- クラスタ - ノード間のアイデンティティとピアの信頼
- Temporalワークフロー - 署名済みコンテキストの伝播
- レジストリ - ケイパビリティストア
- プロセスモデル - プロセスの隔離とライフサイクル
- エージェント - エージェント定義とツール利用