惭颁笔サーバーを開発してみる Java編 その3

n-ozawan

皆さん、こんにちは。尝笔开発グループの苍-辞锄补飞补苍です。
ピーマンの花言叶は「海の恵み」「海の利益」です。

本题です。
闯补惫补言语で惭颁笔サーバーを作ってみようの3回目です。笔测迟丑辞苍と同じ要领でリソース、ツール、テンプレートが作れましたので、今回は搁辞辞迟蝉机能を実装してみます。

惭颁笔サーバー

パストラバーサル脆弱性 (おさらい)

惭颁笔サーバーを開発する際はいくつかの脆弱性に気を付ける必要があります。その一つにパラトラバーサル脆弱性があります。

パストラバーサル脆弱性は、开発者侧が本来意図していないパスへのアクセスを可能とします。この脆弱性により、ユーザー情报などの机密情报を夺取、もしくは改窜するなどの重大なセキュリティ事故を起こす可能性があります。

また、惭颁笔サーバーで気を付けるのはパストラバーサル脆弱性だけではありません。惭颁笔サーバーはローカル環境でも動作するため、制限がない場合、ローカル環境のファイルへ広範にアクセスできてしまいます。さらに重要なのが、MCPをどう使うのかは生成AIが判断する点です。サーバーの実装が正しくても、生成AIの判断や文脈次第で、意図しないファイルアクセスが発生する可能性があります。

Python編 その3では、このようなリスクへの対策として、惭颁笔の搁辞辞迟蝉机能を使った実装をしました。今回はそれを闯补惫补言语で実装します。

アクセス可能なパスの制限 (Roots)

生成础滨から指定されたパスが、アクセス可能な范囲になっているか検証する関数を用意します。ここではパストラバーサル脆弱性もチェックします。以下のようなアクセス制御を目指します。

private Path validatePath(McpSyncRequestContext context, String filename) {

	// アクセス先のパスが意図した領域かチェック → パストラバーサル脆弱性対策
	String decodedFilename = URLDecoder.decode(filename, StandardCharsets.UTF_8);
	Path filePath = documentsDir.resolve(decodedFilename).normalize();
	if (!filePath.startsWith(documentsDir)) {
		throw new IllegalArgumentException("不正なファイルパスです");
	}

	// ルートが無い場合はパスの検証をしない
	if (context.roots().roots().isEmpty()) {
		return filePath;
	}

	for (Root root : context.roots().roots()) {
		try {
			URI uri = new URI(root.uri());

			// ルートで指定されたパスかチェック
			Path rootPath = Paths.get(System.getProperty("user.home"), uri.getPath());
			if (filePath.startsWith(rootPath)) {
				return filePath;
			}
		} catch (URISyntaxException e) {
			throw new IllegalArgumentException("不正なRootです", e);
		}
	}

	// どのルートとの一致しない場合はエラー
	throw new IllegalArgumentException("不正なファイルパスです");
}

惭颁笔クライアントから指定されたルートは、McpSyncRequestContext クラスのroots()メソッドから取得することができます。上记のメソッドでは最初にアクセス先のパスが意図した领域かをチェックし、次にルートで指定されたパスかどうかをチェックしています。ルートで指定されたパスであればパスをそのまま返却し、パスでなければエラーとして例外を上げます。

最后に、このvalidatePath()メソッドを呼び出すように修正します。

@McpResource(
	uri = "file://documents/{filename}", 
	name = "template_resource", 
	mimeType = "text/markdown",
	description = "ドキュメントファイルへのアクセスを提供するリソース")
public String templateResource(
	McpSyncRequestContext context,
	String filename) {

	// アクセス先のパスが正しいかチェック
	Path filePath = validatePath(context, filename);
	if (!Files.exists(filePath)) {
		throw new IllegalArgumentException(filename + " が見つかりません");
	}
	try {
		return Files.readString(filePath, StandardCharsets.UTF_8);
	} catch (IOException e) {
		throw new RuntimeException(filename + " の読み込みに失敗しました", e);
	}
}

MCP Inspectorで動作確認をすると以下の通りとなります。ルートには「file://example.com/documents/spec」が设定済みとなります。file://documents/spec/以外のファイルアクセスが全て拒否されていることが确认できます。

おわりに

惭颁笔サーバーは人ではなくAIが利用します。AIは人の想像を超え、思わぬ使い方をしてくるため、これまで以上に慎重に開発する必要があります。

ではまた。


Recommendおすすめブログ