mcpField
I
integrate-arcjet-guard-agent-framework-go

integrate-arcjet-guard-agent-framework-go

github.com/arcjetセキュリティ480 回インストール3 週間前に更新
リポジトリ

integrate-arcjet-guard-agent-framework-go について

Microsoft Agent Framework を使う Go アプリケーションに Arcjet Guard を組み込むスキルです。関数やツール、エージェント呼び出し、入力テキストを保護する方法と、デフォルトで拒否する動作を説明します。

主な機能

  1. 01GuardAction による Go 関数の保護
  2. 02GuardTool と GuardTools によるツールのラップ
  3. 03GuardMiddleware によるエージェントの保護
  4. 04入力テキストとツール呼び出しの検査
  5. 05拒否を成功したツール結果として返却

活用シーン

  1. 01返金などリスクの高いツールへのレート制限
  2. 02ユーザープロンプトのインジェクション検査
  3. 03mcptool.ListTools が返す MCP ツールの保護

MCP 対応

mcptool.ListTools が返す MCP ツールを GuardTools でラップし、ツール名に応じたポリシーを適用する方法を説明します。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/arcjet/skills --skill integrate-arcjet-guard-agent-framework-go

インストール後、Claude Code で/integrate-arcjet-guard-agent-framework-goを入力して読み込まれたか確認します。

詳細

Microsoft Agent Framework for Go に Arcjet Guard を統合する

github.com/arcjet/arcjet-go/agentframework は、アプリケーションに既存の arcjet.GuardClient をラップします。Guard の共通の基本要素(クライアント、ルール、ラベル、 判定、キャプチャ)については、 ../arcjet/references/guards_go.md を参照してください。 フレームワーク固有ではない内容については、そのリファレンスを読み込んでください。

3 つの適用先に、1 つの判定ルールがあります。

  • 任意の Go 関数 → ルートモジュールの arcjet.GuardAction。フレームワークへの依存はありません。*arcjet.GuardDeniedError または *arcjet.GuardUnavailableError を返します。
  • 配線時に名前を指定できる tool.FuncTool(functool.New、mcptool.ListTools、または agenttool.New から作成)→ agentframework.GuardTool。リストには GuardTools を使います。結果も引き続き tool.FuncTool です。
  • モデルがツールを選ぶエージェント、またはスクリーニング対象のユーザーテキスト → agent.Config.Middlewares の agentframework.GuardMiddleware。

これは Go フレームワークです。Microsoft Agent Framework の .NET および Python 実装には Arcjet アダプターがありません。ここでは @arcjet/guard や arcjet.guard をインポートしないでください。

拒否はツールの結果であり、エラーではありません

このフレームワークでは、ツールのエラーは Error: Function failed. に置き換えられ、失敗するツール呼び出しが 3 ラウンド連続した後、4 回目で実行が終了します。そのため、 GuardTool は成功した結果として arcjet.GuardDenialResult を返します。これをエラーを返すことで「修正」しないでください。また、エラーに拒否内容を含めるために IncludeDetailedErrors を設定しないでください。

デフォルトではフェイルクローズ

GuardTool、GuardTools、GuardMiddleware、arcjet.GuardAction は、ポリシーを評価できない場合に拒否します。これはポリシーの DENY とは異なる結果であり、モデルには異なる結果が返されます。

  • ポリシーを評価できなかった場合:arcjet.NewGuardUnavailableResult()。理由は ERROR、再試行までのヒントは 5 秒です。
  • レート制限の超過やプロンプトインジェクションの検出などによりポリシーが呼び出しを拒否した場合:arcjet.NewGuardDenialResult(decision)、またはその代わりに ToolPolicy.OnDeny が返す値。

受信したリクエストの拒否は、そのメッセージとともに実行を終了します。読み取り専用の検索など、強制よりも可用性が重要な場合に限り、OnGuardError: arcjet.OnGuardErrorAllow を設定してください。これは利用不可時の処理だけを変更し、DENY は常にブロックします。

人による承認はポリシーではありません

tool.ApprovalRequiredFunc と toolapproval ミドルウェアは、人に確認を求めます。 GuardTool はラップしたツールの承認ステータスを保持します。また、Arcjet の判定を自動承認ルールに渡すアダプターはありません。ホステッドツール (hostedtool.*)はプロバイダー側で実行されるため、Guard で保護できません。

ワークフローに別のヘルパーは不要です

workflow/ は 3 つ目の適用先ではありません。agentworkflow でホストされるエージェントは agent.Agent.Run を通じて実行されるため、ワークフロー内でも GuardMiddleware と GuardTool がそのまま適用されます。ベアなエグゼキューターは通常の Go コードです。ハンドラー内で arcjet.GuardAction を呼び出し、拒否時に実行をどう扱うかをそこで決めてください。後続で判断するものはありません。

デフォルトの実行環境では相関情報は引き継がれません。inproc.Default は inproc.OffThread であり、その実行ループは独自のコンテキストを構築するため、Run の前に arcjet.ContextWithCorrelationID で設定した ID はエグゼキューターに届かず、相関付けには使われません。呼び出し元のコンテキストを引き継ぐのは inproc.Lockstep のみです。代わりに、エグゼキューター内で GuardActionPolicy.CorrelationID を明示的に設定してください。いずれの場合も、こちらがコンテキストより優先されます。

最初に人に確認する質問

コードから推測できないことだけを質問し、デフォルトを提案してください。

  1. どのツールがリスクを伴いますか(副作用がある、取り消せない、費用が発生する、メッセージを送信するなど)?これらにはフェイルクローズのToolPolicyを適用します。読み取り専用ツールではOnGuardErrorAllowを使用できます。
  2. 制限は何ですか?(「ユーザーごとに1時間あたり5件の返金」→ハードコードしたBucketを使うGuardTokenBucket。)
  3. ActorとSecurityMetadata.Userに設定するユーザーは誰ですか?認証済みリクエストから取得した不透明なIDを使用し、個人を特定できる情報(PII)は使わないでください。
  4. 相関IDは何ですか?アプリケーションがすでに持っている会話IDまたはリクエストIDです。Runの前にarcjet.ContextWithCorrelationIDを使ってコンテキストに設定してください。新たに生成してはいけません。
  5. 受信テキストをスクリーニングする必要がありますか?必要な場合は、InboundPolicyを使ってGuardMiddlewareを適用します。ここでフェイルクローズにすると、障害が続く間、エージェントが停止します。そのため、この箇所に限ってはOnGuardErrorAllowを選ぶのも妥当です。

読者が間違えやすい点

  1. ラベルはハードコードします。Action: "refund.issued"のように設定し、fmt.Sprintfは使いません。GuardToolsのポリシー関数では、switch t.Name()を使います。
  2. **LabelではなくActionです。**ラッパーではActionを使い、未加工のGuardRequestではLabelを使います。値には同じスラッグを指定します。ToolPolicy.ActionとInboundPolicy.Actionは、ヘルパーの構築時に検証され、最初の呼び出し時には検証されません。誤字があると、起動時にGuardTool/GuardTools/GuardMiddlewareからエラーが返されます。自分で作成したスラッグは、arcjet.ValidateGuardLabelで検証してください。
  3. **拒否は結果です。**上記を参照してください。
  4. **相関IDは呼び出し元が管理します。**コンテキストに設定するか、agentframework.CorrelationIDStateKeyを使ってセッションに保存してください。agent.Session.ServiceIDは使わないでください。プロバイダーによって実行中に書き換えられます。IDが自動生成されることはありません。
  5. **Argsはツールの型付き入力をデコードします。**構造体入力の場合、引数オブジェクトはその値です。スカラーの場合は、フレームワークがラップします。
  6. ラップするのは一度だけです。GuardToolsとGuardMiddlewareは、GuardToolですでにラップされているツールをスキップするため、組み合わせても呼び出しごとの評価は1回です。
  7. ルールは通常、引数から解決されるため、ToolPolicy.Rulesはスライスではなく関数です。
  8. キャプチャ時の**successはポリシーがアクションを判定したこと**を意味し、degradedは完全な判定が行われず、OnGuardErrorAllowのもとで実行されたことを意味します。

手順1:ガードクライアントをインストールして取得する

go get github.com/arcjet/arcjet-go@latest
go get github.com/arcjet/arcjet-go/agentframework@latest

agentframeworkモジュールにはGo 1.26が必要です。ルートのgithub.com/arcjet/arcjet-goモジュールは引き続きGo 1.25以降に対応しています。プロジェクトのGoのバージョンが1.26より古い場合はユーザーに伝え、作業を中止してください。パッケージスコープでarcjet.NewGuardClientを1つ作成します。Keyが空の場合、ARCJET_KEYを読み込みます。

手順2:名前を指定できるツールをゲートする:GuardTool

guarded := agentframework.MustGuardTool(guard, issueRefund, agentframework.ToolPolicy{
	Action: "refund.issued",
	Actor: func(ctx context.Context, _ json.RawMessage) (string, error) {
		return userIDFromContext(ctx)
	},
	Rules: agentframework.Args(func(ctx context.Context, in refundArgs) ([]arcjet.GuardRuleInput, error) {
		userID, err := userIDFromContext(ctx)
		if err != nil {
			return nil, err
		}
		return []arcjet.GuardRuleInput{refundLimit.Key(userID, 1)}, nil
	}),
	Metadata: arcjet.SecurityMetadata{Reversibility: "irreversible"}.Metadata(),
})

ActorとRulesは呼び出しごとに実行され、その呼び出しのコンテキストを受け取るため、そこから呼び出し元のIDを取得してください。パッケージスコープの変数やキャプチャした変数に保持すると、プロセスの存続期間中、すべての呼び出し元が同じレート制限バケットを共有し、すべての判定で同じアクターが指定されます。Metadataはツールのラップ時に一度だけ読み込まれるため、呼び出しごとのIDをここに設定してはいけません。Actorですでにユーザーを指定しています。

GuardToolは(tool.FuncTool, error)を返します。MustGuardToolは設定エラーが発生するとパニックするため、パッケージレベルでの初期化に適しています。MCPツールの場合は、mcptool.ListToolsの出力をGuardToolsでラップし、ツール名に応じて分岐するポリシー関数を指定してください。

ポリシー関数が false を返すと、ツールは保護されないままになります

GuardTools は、ポリシー関数が false を返した場合、そのツールを変更せずに追加します。また、tool.FuncTool ではないツールに対しても同じ処理をします。エラーは発生せず、ログも出力されず、ツールは保護されないまま実行されます。そのため、ツール名を switch で判定する場合、保護されるのは列挙した名前だけです。後からツールを追加しても、誰かが case を追加するまでは保護されません。

どちらの方針にするかを決め、明記してください。

  • デフォルトで拒否する。 すべてのツールにポリシーを返し、switch が認識しない名前には制限的なポリシーを返します。新しいツールが保護されないままになることはありません。
  • デフォルトで許可する。 認識しない名前には false を返し、テストで保護対象のツールを列挙して、新しいツールの追加を検出できるようにします。

選んだ方針をテストしてください。GuardTools は処理を見送ると、渡された値をそのまま返します。そのため、保護されていないツールの検出には同一性比較を使います。

デフォルトで拒否する場合は、ポリシーに未対応のツールを渡し、それでも保護された状態で返ってくることを確認してください。テストの要点は、列挙されていないツールです。デフォルトの分岐でそのツールを引き続きカバーしなくなった場合、このテストは失敗します。

tools := []tool.Tool{lookupOrder, issueRefund, unlistedTool}
guarded, err := agentframework.GuardTools(client, tools, toolPolicy)
if err != nil {
	t.Fatal(err)
}
for i, g := range guarded {
	if g == tools[i] {
		t.Errorf("tool %q came back unguarded", g.Name())
	}
}

デフォルトで許可する場合は、保護対象外にするツールを列挙し、それらだけが対象外であることを確認してください。上記のループに認識しないツールを含めないでください。ポリシーはそのツールの処理を見送るため、アサーションは実行のたびにそのツールを報告してしまいます。ポリシーの case がない状態でエージェントに追加されたツールは、ここに未列挙の項目として表示されます。

guarded, err := agentframework.GuardTools(client, agentTools, toolPolicy)
if err != nil {
	t.Fatal(err)
}
exempt := []string{"health_check"} // deliberately unguarded
var unguarded []string
for i, g := range guarded {
	if g == agentTools[i] {
		unguarded = append(unguarded, g.Name())
	}
}
if !slices.Equal(unguarded, exempt) {
	t.Errorf("unguarded %v, want %v", unguarded, exempt)
}

ここでは、arcjet.GuardConfig{Key: "ajkey_test"} で構築したクライアントで十分です。このテストでは Arcjet API にアクセスしないため、これはアプリケーションコード内のキーではなく、テスト内のリテラルです。

ステップ 3:エージェントを保護する:GuardMiddleware

mw, err := agentframework.GuardMiddleware(guard, agentframework.MiddlewareConfig{
	Tools: func(t tool.Tool) (agentframework.ToolPolicy, bool) {
		switch t.Name() {
		case "issue_refund":
			return agentframework.ToolPolicy{Action: "refund.issued", /* ... */}, true
		}
		return agentframework.ToolPolicy{}, false
	},
	Inbound: &agentframework.InboundPolicy{
		Action: "message.received",
		Rules: func(_ context.Context, text string) ([]arcjet.GuardRuleInput, error) {
			return []arcjet.GuardRuleInput{promptScan.Text(text)}, nil
		},
	},
})
a := anthropicprovider.NewAgent(client, anthropicprovider.AgentConfig{
	Config: agent.Config{Tools: tools, Middlewares: []agent.Middleware{mw}},
})

ミドルウェアは agent.Config.Tools と実行ごとの agent.WithTool からツールを取得します。

ステップ 4:相関付ける

ctx = arcjet.ContextWithCorrelationID(ctx, conversationID)
resp, err := a.RunText(ctx, prompt).Collect()

統合を検証する

  1. go vet ./... が成功し、プロジェクトがビルドできること。
  2. 次の動作を確認します。許可されたツール呼び出し、レート制限された呼び出し(モデルが arcjetDenied: true を受け取り、その理由を説明する)、受信プロンプトインジェクションによる拒否(プロバイダーが呼び出される前に実行が終了する)、およびフェイルクローズ(到達不能な Arcjet が reason: "ERROR" を返す)。
  3. Arcjet Console または CLI で、判定が呼び出し元所有の相関 ID を共有していることを確認します。
  4. 実際の ARCJET_KEY を使用した手動 E2E テストは、実行するまでは未検証です。

実装例: examples/agentframework。 別のサンプル名を新たに作らないでください。この skills リポジトリにサンプルを追加しないでください。

コメント