Development
13 min read
64 views

AIエージェント&ローカルLLMインフラ:ストリーミング、サンドボックス化、プロンプトファイアウォールの極意

ローカルLLMインフラを探る:チャンク転送トンネルによるストリーミング、Wasmでの未信頼MCPツール呼び出しのサンドボックス化、PII削除のゼロリークプロンプトファイアウォール。(163文字 — 最適化済)

IT
InstaTunnel Team
Published by the InstaTunnel team | Editorial policy
AIエージェント&ローカルLLMインフラ:ストリーミング、サンドボックス化、プロンプトファイアウォールの極意

Quick answer

AIエージェント&ローカルLLMインフラ:ストリーミングとMCP: localhost tunnel answer

A localhost tunnel gives your local app a public HTTPS URL without opening router ports, which is useful for demos, QA, mobile testing, and provider callbacks.

How do I expose localhost without opening ports?

Use a reverse HTTPS tunnel. Your machine connects outbound to the tunnel service, and the public URL forwards requests back to your local app.

When should I use a localhost tunnel?

Use one for webhook testing, OAuth callbacks, client demos, QA previews, mobile device checks, and short-lived development reviews.

ローカルのLLMサーバーとエージェントフレームワークは、今や本番に近いワークフローで一般的になっています。エンジニアは Ollama、llama.cpp、または vLLM を自分のハードウェア上で動かし、リバーストンネルを通じて公開します。彼らは Model Context Protocol (MCP) を使ってエージェントをツールに接続し、一部のサブタスクをホストされたモデルに任せます。これらの選択肢それぞれがインフラの課題を生み出します。長い生成はプロキシのタイムアウトで切り捨てられ、バッファリングはトークンごとのストリーミングを破壊し、サードパーティのツールサーバーは攻撃面を拡大します。機密データはプロンプト内から機械外へ漏れる可能性もあります。

この記事では、これらの問題に対処する3つのパターンを解説します:

  1. トンネルとプロキシを通じて長時間のストリーミング生成を維持する方法
  2. WebAssembly (Wasm) ランタイムを使った未信頼MCPツールコードのサンドボックス化
  3. クラウドモデルに送る前にエッジで秘密情報やPIIを赤字化する方法

各パターンについて、その技術の役割とできること・できないことを説明します。


1. 長時間生成:トンネルとプロキシを通じてストリームを維持

コンテキストウィンドウのオーバーフローは別の問題です。これはモデルの設定されたコンテキスト長を超える入力があった場合に起き、解決策はモデルサーバー側(例:Ollamaの num_ctx 設定)にあります。本セクションが扱うのは、ローカルモデルとクライアント間の応答が長時間または遅延して切断される問題です。

[ローカルLLMサーバー]
      │  (localhost HTTP)
      ▼
[エッジプロキシ / トンネルデーモン] ──(チャンク化HTTP / SSE)──► [リモートクライアントまたはゲートウェイ]
      ▲
 (バッファリングオフ、ハートビート、タイムアウト調整)

Ollamaはデフォルトでポート11434をリッスンし、OpenAI互換の /v1/chat/completions エンドポイントを公開しています。stream: true でServer-Sent Events (SSE)を返し、推論スタイルの生成は数分続くこともあります。モデルとクライアント間の経路には複数の失敗ポイントがあります。

ストリームが途切れる場所

  1. アイドル・応答タイムアウト。 Nginxの proxy_read_timeout はデフォルトで60秒です。これはアップストリームからの連続した読み取り間の時間を測定し、リクエスト全体の時間ではありません。大きなプロンプト処理中に最初のトークンを出すまでに長時間かかると、Nginxは接続を切断します。Cloudflareのプロキシは約100秒以内に応答がなければ524エラーを返します。これはエンタープライズプランのみ変更可能で、応答待ちの間の長い沈黙は危険です。
  2. 応答バッファリング。 Nginxはデフォルトでアップストリームの応答をバッファリングします(proxy_buffering on)。これによりクライアントはトークンごとではなくバッチでイベントを受け取ります。CDNやゲートウェイも同様の挙動を示すことがあります。
  3. 圧縮とミドルウェアのバッファリング。 圧縮層がバッファを待ってからフラッシュすると、SSEイベントの遅延が大きくなります。text/event-stream は圧縮やバッファリングミドルウェアから除外してください。

チューニング済みのNginx設定例

ローカルモデルの前にNginxを運用している場合、以下の設定がポイントをカバーします:

location /v1/chat/completions {
    # Ollamaは認証を持たないため、ここで設定(APIキー、mTLS、認証サブリクエスト)
    proxy_pass http://127.0.0.1:11434;

    # HTTP/1.1アップストリーム、keep-alive(Nginxはデフォルトでチャンクレスポンスをストリーム)
    proxy_http_version 1.1;
    proxy_set_header Connection "";

    # バイトを到着順に転送
    proxy_buffering off;
    proxy_cache off;
    gzip off;

    # アップストリームの長い沈黙を許容
    proxy_read_timeout 600s;
    proxy_send_timeout 600s;
}

よくある誤りの修正ポイント

  • X-Accel-Buffering: no はアップストリームアプリケーションから送信されるレスポンスヘッダーです。これを同じNginx設定内で add_header で設定しても効果はありません。バックエンドやラッパー側から送信してください。そうでなければ proxy_buffering off に頼るべきです。
  • Nginxのタイムアウトを延長しても、他のホップの制限は変わりません。proxy_read_timeoutを1時間にしても、Cloudflareの約100秒の制限は変わりません。

ハートビート送信

アイドルタイムアウト対策の最も確実な方法は、ストリームを常にアクティブに保つことです。応答ヘッダーを即座に送信し、その後20〜30秒ごとに : ping のようなSSEコメント行を送ると良いです。コメント行はSSEパーサーに無視され、少量のバイトで済みます。クライアント側からのキープアライブは効果がありません。サーバー側からの沈黙がタイムアウトを引き起こすためです。

トンネル固有の注意点

トンネル製品によって挙動が異なり、時間とともに変化します。一部のプロジェクトはCloudflare Tunnelを通じたSSEのパスを問題なく記録していますが、他は cloudflared でのバッファリングを報告しています。遅い長時間の生成をテストし、トンネルがバッファリングする場合は、リバースSSHトンネルを使ったNginxの運用が一般的な回避策です。

クライアント側では、レスポンスを逐次読み取りながらストリーミングパーサーを使うこと。全体を待たずに処理します。


2. エージェントのサンドボックス化:WebAssemblyで未信頼MCPツールコードを制限

Anthropicが作成したModel Context Protocol(MCP)は、AIアプリケーションとツール・データを接続する最も広く採用されている標準です。2025年12月に、AnthropicはこれをLinux FoundationのAgentic AI Foundationに寄付しました。BlockのgooseやOpenAIのAGENTS.mdとともに、Claude、ChatGPT、Gemini、VS Code、Cursorなどがサポートしています。

MCPメッセージはJSON-RPCを使用します。現行の仕様では、stdioとStreamable HTTPの2つのトランスポートを定義しています。Streamable HTTPは2024-11-05の改訂で古いHTTP+SSEから置き換えられました。

脅威

採用が進む一方で、セキュリティ強化は遅れています。研究者はMCP展開に対するいくつかの攻撃クラスを記録しています:

  • ツールの毒性化: ツールの説明やスキーマ内に隠された悪意のある命令。ユーザーには見えませんが、モデルは読取ります。Invariant Labsは2025年4月に最初のアドバイザリーを公開。
  • プロンプトインジェクション(ツール出力経由): サーバーや取得した内容が、エージェントの次の動作を乗っ取るテキストを返す。OWASP Top 10 for LLM Applications 2025のトップリスク(LLM01)です。
  • 実装バグ: 2026年初頭に、コマンドインジェクションやパストラバーサルなど、30以上のCVEが報告されています。

モデルは、実行環境に関係なく、未信頼のテキストによって操作される可能性があります。制御できるのは、侵害や悪意のあるツールがどれだけのダメージを与えられるかです。

Wasmがもたらすもの

WebAssemblyでツールコードを実行すると、権限のないサンドボックスに閉じ込められます。ゲストモジュールは、ホストが明示的に提供する関数だけを呼び出せます。WASIインポートを提供しなければ、ファイルシステムやソケットはありません。Runtimes(例:Wasmtime)は以下の制限を追加します:

  • 燃料計測: 実行命令数を制限し、無限ループを防止
  • リソースリミッター: リニアメモリの増加とインスタンス数を制限
  • エポック中断: 時間制限を設定

起動コストは低く、Wasmtimeのプーリングアロケータを使えば、インスタンス化はマイクロ秒単位です。コンパイルは別コストなので、事前にコンパイルしてキャッシュしましょう。

[エージェント / MCPクライアント]
        │ JSON-RPC
        ▼
[ホストプロセス:Wasmtime]
   ├─ 燃料予算、メモリ制限、タイムアウト
   └─ インポート:許可されたものだけ(デフォルトではWASIなし)
        ▼
[未信頼ツールモジュール]

例:能力制限なしのWasmtimeサンドボックス

この例はWasmtimeのコアモジュールAPIを使用しています。短くするために数値関数をエクスポートしています。実運用のMCPツールは、通常WITインターフェースと明示的に許可されたWASI能力を使ったコンポーネントモデルを採用します。

use wasmtime::{Config, Engine, Instance, Module, Store, StoreLimits, StoreLimitsBuilder};

struct HostState {
    limits: StoreLimits,
}

fn main() -> wasmtime::Result<()> {
    let mut config = Config::new();
    config.consume_fuel(true); // 命令計測を有効化
    let engine = Engine::new(&config)?;
    let module = Module::from_file(&engine, "untrusted_tool.wasm")?;

    let mut store = Store::new(
        &engine,
        HostState {
            limits: StoreLimitsBuilder::new()
                .memory_size(64 * 1024 * 1024) // 64MiBに制限
                .instances(1)
                .build(),
        },
    );
    store.limiter(|state| &mut state.limits);
    store.set_fuel(50_000_000)?; // 命令予算、枯渇するとトラップ

    // WASIなしの空のインポートリスト
    let instance = Instance::new(&mut store, &module, &[])?;
    let run = instance.get_typed_func::<i32, i32>(&mut store, "run")?;

    match run.call(&mut store, 42) {
        Ok(out) => println!("ツールの返り値: {out}"),
        Err(trap) => eprintln!("ツールがトラップ(予算超過やエラー): {trap}"),
    }
    Ok(())
}

モジュールが未提供のインポートを宣言している場合はインスタンス化に失敗します。これが能力モデルの正しい動作です。

このアプローチの制限

  • Wasmは強力な境界ですが、絶対ではありません。ランタイムやホスト関数にバグがあれば、隔離は破られます。常に最新に保ち、APIは最小限に。
  • これはホストをツールコードから守るものであり、毒性のあるツール説明やモデルの誤用を完全に防ぐものではありません。人間の承認や最小権限の資格情報、レビュー済みのサーバーと併用してください。
  • 既存の多くのMCPサーバーはNode.jsやPythonで、stdio経由で起動します。Wasmで動かすには書き換えや再コンパイルが必要です。OSレベルのサンドボックスやコンテナが現実的な選択肢です。

3. プロンプトファイアウォール:クラウド送信前に秘密情報とPIIを赤字化

ハイブリッド構成では、ローカルアプリからホストされたモデルへ一部タスクを送信します。プロンプト内の情報(ソースコード、接続文字列、APIキー、顧客データ、ログなど)はすべて外に出てしまいます。機密情報漏洩はOWASP Top 10 for LLM Applications 2025のリスク(LLM02)です。

ローカルプロキシは、送信リクエストを検査し、機密値をプレースホルダーに置き換えることができます。

[ローカルアプリ] ──► [ローカルプロキシ:検出+置換] ──► [クラウドLLM API]
                       │
              メモリ内プレースホルダーリスト
                       ▲
[ローカルアプリ] ◄── [レスポンス内のプレースホルダー復元]

検出レイヤー

  • 決定論的パターン。 正規表現とチェックサムで、クラウドアクセスキーID、JWT、APIキーのプレフィックス、メールアドレス、カード番号などを検出。
  • NER(固有表現認識)による検出。 Microsoft Presidioは、名前付きエンティティ認識、正規表現、ルールベースロジック、チェックサムを組み合わせたオープンソースSDKです。NERはspaCyモデルを使用します。SLMではありません。言語カバレッジはインストールしたモデルに依存します。

検出は確率的なものであり、Presidioの資料でもすべての敏感情報を検出できる保証はありません。プロンプトファイアウォールは露出を減らしますが、完全ではありません。

最小限の赤字化プロキシ例

これはストリーミングしないリクエストのみを扱います。チャンク境界をまたぐプレースホルダーの復元は含まれません。

import os
import re

import httpx
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

PATTERNS = {
    "API_KEY": re.compile(r"\bsk-[A-Za-z0-9_-]{20,}") ,
    "AWS_KEY_ID": re.compile(r"\bAKIA[0-9A-Z]{16}\b"),
    "JWT": re.compile(r"\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+"),
    "EMAIL": re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b"),
}


def scrub(text: str, mapping: dict[str, str]) -> str:
    """敏感情報を安定したプレースホルダーに置き換え、`mapping`に記録"""
    for label, pattern in PATTERNS.items():
        def repl(match: re.Match) -> str:
            value = match.group(0)
            for placeholder, original in mapping.items():
                if original == value:  # 既存のプレースホルダーを再利用
                    return placeholder
            placeholder = "{{" + f"{label}_{len(mapping) + 1}" + "}}"
            mapping[placeholder] = value
            return placeholder

        text = pattern.sub(repl, text)
    return text


def restore(obj, mapping: dict[str, str]):
    """解析済みJSON内のプレースホルダーを元に戻す"""
    if isinstance(obj, str):
        for placeholder in sorted(mapping, key=len, reverse=True):
            obj = obj.replace(placeholder, mapping[placeholder])
        return obj
    if isinstance(obj, list):
        return [restore(item, mapping) for item in obj]
    if isinstance(obj, dict):
        return {key: restore(value, mapping) for key, value in obj.items()}
    return obj


@app.post("/v1/chat/completions")
async def proxy(request: Request):
    body = await request.json()
    mapping: dict[str, str] = {}  # リクエストごとに一時的に保持

    for message in body.get("messages", []):
        if isinstance(message.get("content"), str):
            message["content"] = scrub(message["content"], mapping)

    async with httpx.AsyncClient(timeout=120) as client:
        upstream = await client.post(
            "https://api.openai.com/v1/chat/completions",
            json=body,
            headers={"Authorization": f"Bearer {os.environ['UPSTREAM_API_KEY']}"},
        )

    return JSONResponse(restore(upstream.json(), mapping), status_code=upstream.status_code)

設計のポイント

  • マッピングはローカルかつ短命に。 リクエストの間だけメモリに保持、または短いTTLのストアに保存。元の値をログに残さない。
  • プレースホルダーの区切り。 }} で終わることで {{EMAIL_1}} が {{EMAIL_10}} 内にマッチしなくなる。トークンを区切りなしで使う場合は、長いトークンを先に復元するなど工夫を。
  • 資格情報は環境変数から。 ハードコーディングしない。
  • 非テキストコンテンツも扱う。 この例では文字列以外はスキップしています。実際のプロキシはツール呼び出し引数や添付ファイルも検査すべきです。

コンプライアンスの現実

GDPRやHIPAAは個人データの処理と共有を規制します。SOC 2は監査フレームワークであり法律ではありません。識別子をプレースホルダーに置き換えることは擬似匿名化です。擬似匿名化されたデータも個人データとみなされ、検出漏れにより実データが漏れる可能性があります。プロンプトファイアウォールは他の対策と併用してください:データ最小化、ベンダー契約、アクセス制御、コンプライアンスチームのレビュー。


結論

長時間のローカル生成にはストリーミングに適したプロキシが必要です。バッファリングを避け、適切なタイムアウトと定期的なハートビートを設定しましょう。未信頼のMCPツールコードは、能力を与えずに実行できるランタイム内で動かすのが望ましいです。アウトバウンドのプロンプトは、検出できる秘密情報や識別子を除去するローカルフィルターを通すべきです。

これらの対策は完全な保証ではありません。各々の失敗モードを狭めるものであり、トンネルやランタイムのバージョン、データに対して事前にテストを行う必要があります。


参考資料

Continue from this article into the most relevant product guides and workflows.

Related Topics

#AI agents#local LLM infrastructure#context window overflow#streaming LLM state#chunked transfer tunnels#SSE streaming#HTTP response streaming#reverse tunnel optimization#buffer timeout prevention#autonomous agent sandboxing#untrusted MCP tool calls#Model Context Protocol#Wasm runtime#WebAssembly edge sandboxing#MCP server security#zero leak prompt firewall#local tunnel proxy#PII redaction#edge security#cloud LLM handoff#local AI agent architecture#LLM streaming optimization#Server Sent Events LLM#WebAssembly agent security#prompt injection protection#PII masking local LLM#edge computing AI#self hosted LLMs#local LLM orchestration#model context protocol security#AI agent privacy#developer security AI tools#LLM edge firewall#chunked encoding LLM#reverse proxy LLM#local LLM tunneling#Wasm micro-runtimes#MCP tool call inspection#data loss prevention LLM#AI infrastructure security#open source LLM agent#private LLM deployment#local prompt middleware#regex PII filter#SLM privacy layer#LLM API gateway#secure agentic workflows#edge LLM proxy#local AI security#zero trust AI infrastructure#Infrastructure & Streaming:** `local LLM infrastructure#Agent Security & Protocols:** `Model Context Protocol#Privacy & Compliance:** `zero leak prompt firewall

Keep building with InstaTunnel

Read the docs for implementation details or compare plans before you ship.

Share this article

More InstaTunnel Insights

Discover more tutorials, tips, and updates to help you build better with localhost tunneling.

Browse All Articles