AI coding agentにWebアプリを作らせていると、作業の途中で誰かに触ってもらいたくなることがあります。
- デザイナーに画面を確認してもらいたい
- チームメンバーに動作を再現してもらいたい
- 顧客に一時的なデモを見せたい
- 別の端末から開発中のアプリを試したい
しかし、開発サーバーは通常、次のようなURLで動いています。
http://localhost:3000
このURLは、そのままでは自分のPCからしか開けません。そこでよく使われるのが、ngrokやCloudflare Tunnel、h5iです。これらは、ローカルで動くWebサーバーにインターネット経由で到達するための経路を作り、外部からアクセスできるURLを発行します。
では、そもそもlocalhostはなぜ外部から見えないのでしょうか。Tunnelとは何を「トンネル」しているのでしょうか。通信は暗号化されているのでしょうか。また各サービス・ツールはどのように違うのでしょうか。
この記事では、IP address、port、NAT、TLSといった用語から順番に説明し、最後にh5i box shareのP2P共有とQuick Tunnel共有の仕組みを見ていきます。
TL;DR: ngrokやCloudflare Tunnelは、ローカルPCからクラウドへoutbound接続を作り、その中を通してpublic URLへのリクエストをlocalhostへ転送します。
h5i box shareはそれに加えて、対象を特定のsandboxとportに固定し、期限付きticket、失効、監査記録を提供します。デフォルトではh5i同士のE2E暗号化P2P接続を使い、普通のブラウザへURLを渡したい場合はCloudflare Quick Tunnelをtransportとして利用できます。
1. まず「Webアプリへアクセスする」とは何か
Clientとserver
Web通信には、主に次の2つの役割があります。
- Client:Webページを要求する側。通常はChromeやFirefoxなどのbrowserです。
- Server:要求を受け取り、HTMLやJSONなどを返す側。開発中のNode.js、Python、RustのWebアプリなどです。
browserがWebページを開くと、clientからserverへrequestが送られ、serverからclientへresponseが返ります。
Client -- request --> Server
Client <-- response -- Server
たとえば、browserがGET /というrequestを送ると、serverはHTMLをresponseとして返します。フォームを送信するときには、POST /submitのようなrequestを送ることもあります。
このとき使われる代表的な約束事がHTTP(Hypertext Transfer Protocolです。Protocolとは、通信する双方が守るmessage形式や手順の決まりです。HTTPは「どのpageを要求するか」「serverが成功・失敗のどちらを返したか」「bodyにどのdataを入れるか」などを定めています。
HTTPSは、HTTPをTLSで保護したものです。TLSについてはQuick Tunnelの節で詳しく説明しますが、この段階では「通信内容を暗号化し、途中で読まれたり書き換えられたりする危険を減らす仕組み」と考えてください。
Network上のdataは、多くの場合、packetと呼ばれる小さな単位に分けて送られます。大きなWeb pageや画像も、実際には複数のpacketとして運ばれ、受信側で組み立てられます。
IP address:通信相手の場所
インターネット上で通信相手を特定するために使われる番号がIP addressです。住所に近い役割を持ちます。
203.0.113.10
実際には、人間がIP addressを覚えなくて済むように、example.comのようなdomain nameを使います。domain nameからIP addressを調べる仕組みがDNSです。DNSは、よく「インターネットの電話帳」にたとえられます。
URL:接続方法と場所をまとめた表記
Browserへ入力する次の文字列全体をURL(Uniform Resource Locatorと呼びます。
https://example.com:8443/demo
^^^^^ ^^^^^^^^^^^ ^^^^ ^^^^^
scheme host port path
-
Scheme:どのprotocolで接続するか。ここでは
https - Host:接続先のdomain nameまたはIP address
- Port:host上のどのserviceへ接続するか
- Path:server上のどのresourceを要求するか
HTTPSでは通常port 443、HTTPでは通常port 80が省略されています。開発serverでは3000や8000などを明示することがよくあります。
Port:同じマシン上のどのserverか
1台のPCでは、複数のnetwork serviceが同時に動きます。そのため、IP addressだけでなくport numberを使って、どのserviceへ接続するかを指定します。
127.0.0.1:3000
^^^^
port number
たとえば、同じPCで次のように複数のserverを動かせます。
127.0.0.1:3000 開発中のWebアプリ
127.0.0.1:5432 PostgreSQL
127.0.0.1:8000 API server
IP addressが建物の住所なら、portは部屋番号のようなものです。
localhostとloopback
localhostは「このPC自身」を表す特別な名前です。IPv4では通常、127.0.0.1へ変換されます。
localhost:3000 == 127.0.0.1:3000
127.0.0.0/8、つまり127で始まるIP addressの範囲はloopback addressと呼ばれます。loopbackへの通信はネットワークケーブルやWi-Fiへ出ていかず、自分のPC内で折り返されます。
そのため、localhost:3000でのみ待ち受けているserverへ、別のPCから接続することはできません。相手のPCでlocalhost:3000を開いても、それはあなたのPCではなく、相手自身のPCを意味します。
2. なぜ自分のPCへインターネットから直接接続できないのか
Private IP addressとpublic IP address
家庭や大学、企業のnetworkに接続したPCには、多くの場合、次のようなprivate IP addressが割り当てられます。
192.168.1.20
10.0.0.15
Private IP addressは、同じlocal network内では使えますが、インターネット全体では一意ではありません。世界中の家庭で同じ192.168.1.20が使われています。
一方、インターネットから見えるrouter側のaddressがpublic IP addressです。
NAT:private addressをpublic addressへ変換する仕組み
家庭内の複数端末が1つのpublic IP addressを共有できるのは、routerがNAT(Network Address Translationを行うからです。
PCやスマホから外部へ通信すると、routerは例えば次の対応を一時的に記録します。
192.168.1.20:51000 <-> 203.0.113.10:42001 # PC
192.168.1.21:51000 <-> 203.0.113.10:42002 # スマホ
それぞれ次を意味します。
192.168.1.20:51000
├─ 192.168.1.20:家庭内にあるPCのprivate IP address
└─ 51000:そのPCが通信に使っているport
203.0.113.10:42001
├─ 203.0.113.10:routerのpublic IP address
└─ 42001:routerがInternet側に割り当てたport
外部serverから応答が戻ると、routerはこの対応表を見て、元のPCへ転送します。
しかし、外部から突然203.0.113.10:3000へ接続されても、routerには家庭内のどのPCへ渡すべきか分かりません。そのため、通常は通信を破棄します。
Firewall:許可していない通信を止める仕組み
Firewallは、通信の送信元、送信先、port、protocolなどを見て、通してよい通信と拒否する通信を判断します。
- Inbound traffic:外部からPCやnetworkへ入ってくる通信
- Outbound traffic:PCやnetworkから外部へ出ていく通信
一般的な家庭や企業のnetworkでは、outbound trafficは比較的許可され、外部から突然届くinbound trafficは厳しく制限されます。
Web閲覧はPC側から開始するoutbound通信なので通ります。一方、外部の人があなたの開発serverへ接続する通信はinboundなので、そのままでは止められます。
3. Port forwardingとdeployment
ローカルのserverを外部公開する古典的な方法の一つが、routerのport forwardingです。
たとえばrouterへ、次のような設定を入れます。
public IPのport 3000へ来た通信
-> 192.168.1.20のport 3000へ転送
しかし、この方法にはいくつか問題があります。
- routerの設定権限が必要
- public IP addressが変わる可能性がある
- ISPのCGNATにより、そもそも自分専用のpublic IPがない場合がある
- firewall設定が必要
- HTTPS certificateやdomainを自分で準備する必要がある
- 間違ったportや別のserviceを公開する危険がある
ISP(Internet Service Providerは、家庭や組織をインターネットへ接続する事業者です。CGNAT(Carrier-Grade NATは、ISPがさらに上流で多数の利用者に1つまたは少数のpublic IP addressを共有させる仕組みです。家庭のrouterでport forwardingを設定しても、ISP側のNATを自分では設定できないため、外部から直接到達できない場合があります。
別の選択肢は、Vercel、AWS、Fly.ioなどへアプリをdeployすることです。Deploymentでは、codeやbuild成果物をcloudへ送り、cloud上でアプリを実行します。
Deployment:
code -> cloudへ転送 -> cloud上でアプリを実行
これは本番運用には適していますが、数分だけ開発中の画面を見せたい場合には大がかりです。そこで登場するのがtunnelです。
4. Tunnelとは何か
Tunnelとは、本来は直接届かない2地点の通信を、別の接続の中に通して運ぶ仕組みです。
ngrokやCloudflare Tunnelでは、ローカルPC上のagentがcloud serviceへoutbound接続を作ります。外部からpublic URLへ届いたrequestは、その既存接続の中を通ってlocalhostへ運ばれます。
この形は、ローカル側からcloudへ接続を開始するため、多くのNATやfirewallを通過できます。routerへinbound portを開ける必要もありません。
この用途のtunnelは、PC全体を別networkへ参加させるVPNよりも、public reverse proxyとlocalhostを結ぶ持続的な通信経路に近いものです。
Reverse proxyとは
Proxyは、通信を中継するものです。Clientの代わりに外部serverへ接続するものをforward proxy、serverの手前でclientからのrequestを受けるものをreverse proxyと呼びます。
ngrokやCloudflareはpublic URLでrequestを受け、対応するlocal tunnel agentへ転送します。この意味で、cloud側はreverse proxyとして動きます。
5. ngrokの仕組み
ngrokは、localhostで動くserviceをpublic endpointへ接続するdeveloper向けserviceです。基本的な使い方は次のようになります。
ngrok http 3000
すると、次のようなpublic URLが発行されます。
https://example.ngrok.app
通信は次の経路を通ります。
Visitor browser
-> ngrok edge
-> PC上のngrok agent
-> localhost:3000
ここでedgeとは、利用者に近い場所に配置されたprovider側のserverです。VisitorはあなたのPCへ直接接続するのではなく、まずngrokのedgeへ接続します。
PC上のngrok agentは、あらかじめngrok cloudへ暗号化されたoutbound接続を作っています。Edgeが受けたrequestはこの接続の中を通り、agentがlocalhostへ渡します。ngrokの公式ドキュメントでは、この仕組みをsecure tunnelとして説明しています。
ngrokが便利なのは、単にURLを発行するだけではない点です。
- HTTP requestの内容を確認する
- requestを再送する
- Basic AuthやOAuthを追加する
- 固定domainを利用する
- HTTP以外のTCP/TLS serviceを公開する
- APIやSDKからendpointを管理する
一方、ngrokは汎用的なtunnelです。ngrok http 3000と指定したとき、port 3000を保持しているprocessが「目的のsandbox内のdev serverか」までは判断しません。意図せずホスト側の別serviceが待ち受けていれば、それを公開する可能性があります。
また、一般的なHTTP endpointではngrokがpublic HTTPS endpointとして通信を中継します。利用するTLS構成によってtrust modelは変わりますが、単に「URLがHTTPSだから、ngrokにも内容が見えない」とは限りません。通信経路上の盗聴を防ぐことと、中継serviceにも内容を見せないE2E暗号化は別の性質です。
6. Cloudflare TunnelとQuick Tunnel
Cloudflare Tunnelも、基本構造はngrokとよく似ています。ローカルでcloudflaredというdaemonを動かし、Cloudflare networkへoutbound接続を作ります。
Daemonとは、画面を直接操作しなくてもbackgroundで継続して動くprocessです。
通常のCloudflare Tunnelでは、Cloudflare accountやdomainを設定し、次のように固定hostnameをlocal serviceへ対応付けられます。
https://demo.example.com -> http://localhost:3000
Cloudflare Accessと組み合わせれば、組織accountによるloginやaccess policyも追加できます。
Quick Tunnel
開発・テスト向けに、accountや独自domainなしで使えるのがQuick Tunnelです。
cloudflared tunnel --url http://localhost:3000
実行すると、ランダムなtrycloudflare.com URLが発行されます。
https://random-words.trycloudflare.com
このURLへのrequestをCloudflareが受け、cloudflaredを通してlocalhost:3000へ転送します。公式ドキュメントによると、Quick Tunnelは開発・テスト用であり、本番service向けではありません。現在は同時に処理中のrequestが最大200、Server-Sent Events(SSE)は非対応という制限もあります。
Quick Tunnelは暗号化されているのか
通信経路は暗号化されていますが、browserからlocalhostまで1本の鍵で暗号化されるE2E暗号化ではありません。
Browser
== HTTPS/TLS ==> Cloudflare
== encrypted tunnel ==> cloudflared
-- HTTP on loopback --> localhost:3000
**TLS(Transport Layer Security)**は、通信内容の盗聴や改ざんを防ぐprotocolです。URLがhttps://から始まる場合、通常はbrowserと接続先serverの間でTLSが使われます。
Quick Tunnelでは、browserとCloudflareの間はHTTPS/TLSで暗号化されます。Cloudflareとcloudflaredの間も、QUICまたはHTTP/2を使う暗号化されたtunnelです。Cloudflareはtunnel接続の暗号化仕様を公開しています。
しかし、Cloudflareは自社edgeでbrowser側のTLSを終端します。TLS terminationとは、暗号化された通信を受け取り、そこで復号することです。その後、Cloudflareは別の暗号化された接続でcloudflaredへ転送します。
したがって、経路の途中にいる第三者による盗聴からは守られますが、Cloudflareは技術的にはHTTP requestやresponseの内容を処理できます。
Transport encryption: あり
Cloudflareからも内容を隠すE2E encryption: なし
最後のcloudflared -> http://localhost:3000はHTTPですが、同じPCのloopback内で完結します。必要ならoriginをhttps://localhost:3000にすることもできます。ただし、それだけでbrowserからoriginまでCloudflareにも読めないE2E暗号化になるわけではありません。
7. AI agentのsandboxを共有するときに追加で考えること
ngrokやQuick Tunnelは便利ですが、AI coding agentが作業するsandboxを共有する場合には、単に「portへ到達できる」だけでは不十分です。
本当にsandboxのportなのか
ホストとsandboxの両方にport 3000が存在するかもしれません。
Host port 3000 古い開発server
Sandbox port 3000 今回共有したいWebアプリ
共有toolが接続先を取り違えると、sandboxではなくホスト上のserviceを公開してしまいます。
誰が、いつまでアクセスできるのか
一時的なデモなら、URLを知る人へ無期限に公開する必要はありません。
- 1時間後に自動失効する
- 人ごとにaccessを取り消す
- share全体を停止する
- 共有中であることを明示する
といったcontrolが必要です。
何が入ってきたのか
Sandboxのnetwork policyは通常、外向きの通信、つまりegressを制御します。しかしshareは外からsandboxへ入るingressです。
- 誰向けのgrantが利用されたか
- 直接接続かrelay経由か
- 何connection開かれたか
- 何byte転送されたか
- expired/revoked ticketが拒否されたか
といった情報を、agent自身の自己申告ではなくhost側で記録できることが重要です。
8. h5i box shareの基本
h5iは、coding agent、workspace、shell、dependency、dev server、browserを1つのdisposable sandbox内で動かすlocal-firstのtoolです。
h5i box shareは、そのbox内で動いているWebアプリを外部の相手へ一時的に共有する機能です。
h5i box share my-box --port 3000 --expire 60m --label alex
デフォルトはP2P共有です。相手側もh5iを使い、発行されたticketで接続します。
h5i join -
Ticketをcommand line argumentへ直接書くとshell historyやprocess listingへ残る可能性があるため、stdinから渡す形が安全です。
pbpaste | h5i join -
# または
h5i join - < ticket.txt
普通のbrowserだけで開けるURLが必要な場合は、--tunnelを使います。
h5i box share my-box --port 3000 --tunnel
このmodeでは、h5iがcloudflaredを起動し、Cloudflare Quick TunnelのURLを発行します。
9. デフォルトのP2P共有はどう動くのか
P2Pとは
**P2P(Peer to Peer)**とは、利用者同士の端末が直接通信する構成です。Providerのserverがすべてのapplication dataを受け取るcloud tunnelとは異なり、direct pathが作れれば2つのh5i processが直接通信します。
ここで重要なのは、P2Pだからといって2台が同じ家庭、大学、LANにいるわけではないことです。たとえば、h5i box shareを実行するAliceはニューヨークの自宅、h5i joinを実行するBobは東京の大学networkにいても構いません。
その場合、AliceとBobはそれぞれ別のNATの内側にいます。2人が1つのNATを共有したり、P2Pが共通のNATを用意したりするわけではありません。
Aliceから見て、Bobのprivate addressである192.168.1.8には意味がありません。同じaddressがAliceの自宅network内でも使われているかもしれないからです。BobもAliceの10.0.0.5へ直接は接続できません。
したがって「P2PでNATを使う」というより、正確には次のように捉えるべきです。
各peerがそれぞれ別のNATやfirewallの内側にいるため、それらを越えてdirect pathを作る。
このためにNAT traversalを使います。
NAT traversalとhole punching
NAT traversalは、NATやfirewallの内側にいる端末同士が通信経路を作る技術の総称です。
具体例で追ってみましょう。Aliceのh5iは、localでは次のaddressでUDP socketを開いているとします。
AliceのPC内: 10.0.0.5:5000
AliceがInternet上のserverへpacketを送ると、Alice側のNATは送信元を書き換えます。
NAT変換前: 10.0.0.5:5000
NAT変換後: 203.0.113.10:41000
同様に、Bob側でも別のNAT変換が起きます。
NAT変換前: 192.168.1.8:6000
NAT変換後: 198.51.100.20:52000
外部から到達できる可能性があるのは、private addressではなく、NAT変換後のpublic endpointです。Endpointとは、ここではIP addressとport numberの組み合わせを意味します。
Aliceのpublic endpoint: 203.0.113.10:41000
Bobのpublic endpoint: 198.51.100.20:52000
ただし、AliceとBobは最初から相手のpublic endpointを知っているわけではありません。また、相手のendpointを知るだけでも不十分です。Bob側のNATは、Bobがまだ通信を始めていないAliceから突然packetを受け取ると、それを予期しないinbound trafficとして破棄する可能性があります。
そこで、両者が到達できるcoordination serverを待ち合わせ場所として使います。Coordination serverは、peer同士の接続に必要なaddress情報を交換するserverです。P2P libraryによっては、relay serverがこの役割も担います。
Coordination serverには、NAT変換後の送信元addressが見えます。そこで両者は、次の手順でdirect connectionを試します。
- AliceとBobが、それぞれcoordination serverへoutbound packetを送る
- Serverが観測した両者のpublic endpointを交換する
- AliceとBobが、ほぼ同時に互いのpublic endpointへUDP packetを送る
- 両側のNATに相手との通信を許す一時的なmappingができれば、direct pathが成立する
両者がほぼ同時に互いへ送信する方法は、一般にhole punchingと呼ばれます。
「hole」といっても、firewallを破壊するわけではありません。両端末が自分から通信を開始し、その相手とのpacketを受け取れる一時的なmappingをNATに作らせる、という意味です。
AliceのPC -> Alice側NAT -> Internet -> Bob側NAT -> BobのPC
^ ^
Alice側のmapping Bob側のmapping
Direct pathが成立した後、application dataは通常coordination serverを通らず、AliceとBobの間を直接流れます。Coordination serverは接続相手を発見するための待ち合わせ場所であり、常に通信内容を中継するserverとは限りません。
AliceとBobが偶然同じLANにいる場合は、private address同士で直接つながることもあります。P2P transportは通常、同一LANのaddress、IPv6の直接address、NAT traversalで発見したpublic endpoint、relayなど複数の候補を試し、利用可能な経路を選びます。しかし、同じLANにいることはP2Pの前提ではありません。
この仕組みはnetwork環境によって成功しないこともあります。企業firewall、mobile network、symmetric NAT、CGNATなどでは、外部から予測可能なmappingを作れない場合があります。
Symmetric NATは、同じlocal endpointからの通信でも、接続相手ごとに異なる外向きportを割り当てるNATの一種です。第三者serverが観測したmappingをそのまま別peerとのdirect接続へ使いにくいため、hole punchingが難しくなります。
QUICとは
Direct pathが見つかると、h5i同士はQUICで通信します。
QUICはUDPの上で動くtransport protocolです。UDPはpacketを相手へ送るための比較的単純なprotocolで、TCPのような接続管理や再送を単体では提供しません。QUICはUDPを土台にしながら、次の機能を提供します。
- TLS 1.3による暗号化と相手の認証
- packetが失われた場合の再送
- 複数streamの同時利用
- 少ない往復での接続確立
- network経路が変化した場合のconnection migration
QUIC自体の標準はRFC 9000で定義されています。h5iのP2P transportでは、NAT traversal、relay fallback、暗号化されたQUIC接続が組み合わされています。
E2E暗号化とは
E2E(End-to-End)暗号化では、通信の両端だけが内容を復号できます。
h5i share == encrypted QUIC == h5i join
中継serverを通る場合でも、暗号化と復号を行うのはh5i同士です。これはCloudflare Quick Tunnelとの重要な違いです。Quick TunnelではCloudflareがTLSを終端しますが、P2P modeではrelayを含む中間者はapplication dataを復号できません。
10. Relayとは何か
Direct P2P接続を作れない場合、relay serverが暗号化されたpacketを中継します。
Relayは郵便物を運ぶ配送拠点に近い役割です。封筒の宛先を見て運ぶ必要はありますが、中身はE2E暗号化されているため読めません。
Relayが確認できるmetadataには、概ね次のものがあります。
- 両endpointのIP address
- 接続時刻と通信時間
- packetのtiming
- 転送量
一方、適切なE2E暗号化が維持されている限り、次の内容は確認できません。
- 表示しているHTML
- HTTP request/response body
- フォーム入力
- Cookie
- WebSocket内のapplication data
Metadataとは、「通信内容そのものではないが、通信について分かる情報」です。内容が暗号化されていても、誰がいつどれくらい通信したかは別問題として残ります。
--direct-onlyを付けると、relayが必要な相手を拒否できます。
h5i box share my-box --port 3000 --direct-only
これは「できればdirect」という曖昧な指定ではありません。Share開始時にdirect pathを作れなければapplication dataを送らず、接続後に経路がrelayへ変わった場合も検出して停止します。
その代わり、厳しいNATや企業network内の相手は接続できない可能性があります。Privacyを強くするほどavailabilityが下がるtrade-offです。
11. P2P接続をbrowserでどう表示するのか
Browserはh5i独自のendpoint IDへQUIC接続できません。そのため、h5i joinは相手のPC上にlocal proxyを立て、browserにはloopback URLを渡します。
Browser
-> http://127.x.x.x:random-port
-> h5i join
-> encrypted P2P connection
-> h5i box share
-> box内のlocalhost:3000
Linuxでは、利用可能であれば127.0.0.1ではなくshare専用のランダムな127.x.x.xを使います。Cookieはportではなくhostを基準に共有されることがあるため、別のloopback addressを使うことで、他のlocalhost serviceとのcookie jarを分離しやすくなります。
Joinする側の注意点
P2P modeで開くページは、browserから見るとpublic websiteではなくloopback上のpageです。一部のbrowser protectionは、public originからlocal networkへのrequestを重点的に制限しています。そのため、悪意ある共有pageが相手PC上の別のlocalhost serviceへrequestを送る点では、public originで開くQuick Tunnelより不利な場合があります。
Same-Origin PolicyやCORSによってresponseを読めなくても、requestそのものを送るだけで状態が変わるCSRF脆弱性がlocal serviceにあれば、攻撃が成立する可能性があります。BrowserのLocal Network Accessはこの種の問題を扱っていますが、loopbackから別のloopbackへの制限はpublicからloopbackへの制限と同一ではありません。
ここでいうoriginは、scheme、host、portの組み合わせです。たとえば、http://127.0.0.1:3000とhttp://127.0.0.1:8000はportが異なるため別originです。
- Same-Origin Policy:あるoriginのpageが、別originのresponseを自由に読み取れないようにするbrowserの基本policy
- CORS(Cross-Origin Resource Sharing):別originからのresponse読み取りをserverが明示的に許可する仕組み
- CSRF(Cross-Site Request Forgery):responseを読めなくても、被害者のbrowserから意図しないrequestを送らせ、状態変更を起こす攻撃
CORSは主にresponseの読み取りを制御します。そのため、認証やCSRF対策なしで「requestが届くだけで危険な操作を実行する」localhost APIは、CORSだけでは十分に守れません。
Private windowや専用browser profileは、Cookie、history、localStorageなどを日常用profileから分離する助けになります。ただし、同じhost上で動く限り、localhostへのnetwork reachabilityまでは分離しません。信頼できないpageを強く隔離したい場合は、VMやsandbox内のbrowserを使う方が安全です。
12. --tunnel modeは何を追加するのか
相手がh5iをinstallしていない場合、P2P ticketだけを渡してもbrowserでは開けません。そこで--tunnel modeではCloudflare Quick Tunnelをtransportとして利用します。
h5i box share my-box --port 3000 --tunnel
通信経路は概ね次のようになります。
Visitor browser
-> Cloudflare Quick Tunnel
-> cloudflared
-> h5iのshare gate
-> box内のlocalhost:3000
ここでCloudflare Quick Tunnelは、public URLを発行してrequestを運ぶtransportです。その下にあるh5iのauthorizationやreceiptは維持されます。
- Linkには期限付きtokenが含まれる
- Tokenはh5iのgrant tableに対して検証される
- Grantをrevokeするとlive connectionも切断される
- Tokenはbox内のWebアプリへ渡されない
- Ingressの結果がh5i receiptへ記録される
一方、CloudflareがTLSを終端するため、この経路はE2E暗号化ではありません。h5iはその事実もreceiptへ記録します。またcloudflaredはh5iに同梱・version pinされていないため、別途installが必要です。
13. h5iがtunnelの下に追加するsecurity control
1. Boxのportだけを共有する
h5iは単純にhostのport 3000を外部公開しません。
Linuxでは、boxのnetwork namespaceへ入り、その中の127.0.0.1:3000へ接続します。Network namespaceとは、network interface、routing table、port空間などをprocess groupごとに分離するLinux kernel機能です。
そのため、box内のport 3000とhostのport 3000を別物として扱えます。Box独自のnetwork namespaceがなく、接続先を安全に区別できない場合、h5iは推測で公開せず拒否します。
macOSにはLinuxと同じnetwork namespaceがないため、h5iはportを保持しているprocessを調べ、そのprocessがbox sessionまたはその子processに属する場合だけ共有します。別のhost processがportを保持していれば拒否します。
2. Ticketをcapabilityとして扱う
P2P共有で発行されるticketは、次の情報を結び付けます。
- どのboxか
- どのportか
- どのgrantか
- いつ失効するか
- 256-bit secret
このように、「持っていること自体が特定操作の権限になるtoken」をcapabilityと呼びます。
h5iはsecretそのものではなくSHA-256 digestを保存します。Hashは、入力から固定長の値を計算する一方向関数です。元のsecretをそのまま保存せず、提示されたsecretをhashして一致を確認できます。
Ticketは人のidentityへ自動的に固定されるものではありません。Ticketを転送すれば、受け取った別の人も同じgrantを使えます。そのため、ticketはpasswordと同様に扱う必要があります。
3. Expiryとrevocation
Shareには有効期限があります。デフォルトは1時間、最大24時間です。
h5i box share my-box --port 3000 --expire 30m --label alex
Grantを個別に失効できます。
h5i box share revoke my-box <grant-id>
Share全体を停止する場合は次を使います。
h5i box share stop my-box
Authorizationはconnectionごとに再確認され、revokeされた既存connectionもwatchdogによって短時間で閉じられます。Watchdogとは、別processやconnectionの状態を定期的に監視し、異常やpolicy変更を検出したときに処理を止める仕組みです。
4. Credentialを共有アプリへ渡さない
Tunnel modeでは、最初のURLにshare tokenが含まれます。h5iは最初のrequestを受けるとtokenをHttpOnly cookieへ移し、tokenを除いたURLへredirectします。
- Address barにtokenを残さない
- 外部linkへ移動するときの
Refererへtokenを載せない - Box内のWebアプリのlogへtokenを渡さない
- Share credential用cookieをboxへ転送しない
HttpOnly cookieは、browserのJavaScriptから直接読み取れないように指定されたcookieです。Refererは、どのpageから移動してきたかをrequest先へ伝えるHTTP headerです。
ただし、linkそのものを持つ人が認可されるcapability URLである点は変わりません。URLがchat logやscreenshotへ漏れれば、失効するまで他人に使われる可能性があります。
5. Connection数を制限する
Shareからboxへ同時に入れるconnection数には上限があります。これは、1人のvisitorや悪意あるpageが無制限にsocketを開き、boxのresourceを使い切ることを防ぐためです。
Resource exhaustionとは、CPU、memory、file descriptor、connection数など有限のresourceを大量消費させ、serviceを利用不能にする攻撃や障害です。
6. Ingress receiptを残す
Share終了後、h5iのreceiptには次のような情報が記録されます。
share session, 612s (p2p transport)
shared port 3000 inside the box
peers 1
... via direct — grant ... (alex), 12 connections
refused expired / revoked / unknown ticket attempts
Receiptは、「何が起きたか」を後から確認できる記録です。h5iでは、agentが生成した説明ではなく、shareのbridgeを所有するhost側が観測した情報として保存します。
これにより、最終的なpatchやexecution logと一緒に、「このboxが誰かへ共有されていたか」「どのtransportを使ったか」「何が拒否されたか」を確認できます。
14. 3つの方式を比較する
| 項目 | ngrok | Cloudflare Quick Tunnel | h5i P2P | h5i --tunnel
|
|---|---|---|---|---|
| 主な対象 | 任意のlocal service | 一時的なlocal Web server | h5i box内のWebアプリ | h5i box内のWebアプリ |
| 相手側のsoftware | Browser | Browser | h5i | Browser |
| Account | 基本的に必要 | 不要 | 不要 | 不要 |
| 経路 | ngrok cloud経由 | Cloudflare経由 | Direct、失敗時relay | Cloudflare経由 |
| Providerから内容を隠すE2E暗号化 | 構成による | なし | あり | なし |
| NAT traversal | 不要 | 不要 | Direct path作成のため使用 | 不要 |
| 期限付きbox grant | h5iの機能ではない | Quick Tunnel単体にはない | あり | あり |
| 個別失効 | ngrok policyによる | Quick Tunnel単体では限定的 | あり | あり |
| Box/host portの識別 | しない | しない | h5iが確認 | h5iが確認 |
| h5i receipt | なし | なし | あり | あり |
| 本番利用 | 対応可能 | 非推奨 | 非推奨 | 非推奨 |
ngrokやCloudflare Tunnelは汎用的なnetwork ingressです。Webhook受信、固定domain、高度なaccess policy、本番serviceなど、h5iより広い用途を扱えます。
h5i box shareの強みは汎用性ではなく、AI agentが作業している特定boxの特定portを、一時的・失効可能・監査可能な形で共有することです。
15. どのmodeを使うべきか
h5i利用者同士で、providerに内容を見せたくない
デフォルトのP2P modeが向いています。
h5i box share my-box --port 3000
Direct pathが作れない場合でもrelay経由で利用できます。Relayはmetadataを確認できますが、E2E暗号化された内容は復号できません。
Relayも使いたくない
h5i box share my-box --port 3000 --direct-only
ただし、企業、大学、mobile networkなどでは接続できない相手が増えます。
顧客やデザイナーへ普通のURLを送りたい
h5i box share my-box --port 3000 --tunnel
相手はh5iをinstallする必要がありません。ただしCloudflareを通信経路上のtrusted partyとして受け入れる必要があります。
Webhook、固定domain、本番serviceを公開したい
ngrokや通常のCloudflare Tunnelの方が適しています。h5i box shareは、動作中のsandbox内Webアプリを短時間共有するための機能であり、本番deployment platformではありません。
まとめ
localhostのWebアプリが外部から見えないのは、loopbackが自分のPC内だけを指し、さらにNATとfirewallが予期しないinbound通信を止めるからです。
ngrokとCloudflare Tunnelは、local agentからcloudへoutbound接続を作り、public URLへ届いたrequestをその中に通すことで、この問題を解決します。Quick Tunnelなら、accountやdomainなしで一時的なHTTPS URLを発行できます。ただしCloudflareがTLSを終端するため、通信経路は暗号化されていてもE2E暗号化ではありません。
h5i box shareは、共有経路だけでなく、その手前と奥にあるsecurity boundaryも扱います。
- 本当に対象boxが保持するportか確認する
- Box、port、有効期限をticketへ固定する
- Grantを途中で失効する
- P2P modeではQUICによるE2E暗号化を使う
- Direct接続できなければ暗号化されたままrelayする
- Browser URLが必要ならQuick Tunnelをtransportとして使う
- 共有中に何が起きたかをreceiptへ残す
つまり、ngrokやCloudflare Tunnelが「localhostへ通信を届ける」ための汎用toolなのに対し、h5i box shareは「AI agentが動くsandbox内のdev serverを、boundaryを保ったまま一時共有する」ための機能です。