1
-# Portal - The Trustless Relay Network for Localhost
1
+# Portal - Self-Hostable Relay Tunnel for Localhost
2
3
[English](./README.md) | [简体中文](./README.zh-CN.md)
4
5
<p align="center"><img width="800" alt="Portal Demo" src="./portal.gif" /></p>
6
7
-<p align="center"><b>Expose local services to the public internet with zero trust in the relay operator.</b><br/>No port forwarding. No inbound firewall rules. No manual DNS setup. No surveillance.</p>
7
+<p align="center"><b>Expose local services through self-hosted or public relays.</b><br/>No port forwarding. No inbound firewall rules. No manual DNS setup. No accounts.</p>
8
9
-## Why Portal? The Trustless Advantage
9
+## Why Portal?
10
11
-Most tunneling services (ngrok, Cloudflare Tunnel) terminate your TLS connection at their edge. This means **they can read your plaintext traffic**. Portal is built on a fundamentally different model: **relays are blind by design**.
11
+Portal is a local tunnel runtime and relay network for publishing services to the agentic web.
12
+It publishes local apps, APIs, tools, and agents through self-hosted or public relays,
13
+keeps routing and x402 payment policy in the tunnel process, and avoids requiring a hosted vendor account.
14
13
-- **End-to-End Encryption with ECH** — Your HTTPS traffic stays encrypted through the relay, and ECH-capable clients avoid exposing the real hostname in plaintext SNI. Portal keeps TLS on your machine, so relay operators cannot read your web traffic or easily profile it by hostname.
15
+- **Self-Hostable, Fully Open Source** - Run your own relay with a single
16
+ command. The relay is MIT-licensed with no enterprise tier, no feature gating,
17
+ and no call-home. Your relay, your rules.
18
15
-- **Built-in MITM Detection** — Portal actively self-probes its own connection after real traffic begins. It compares TLS keying material exported on both the client and server sides. A mismatch is treated as suspected relay-side TLS termination and the relay is banned by default.
19
+- **Anonymous Relay Network** - Connect to public relays without a hosted
20
+ account or central operator. Combine self-hosted relays with public relays in
21
+ a pool to split trust across independent operators you choose.
22
17
-- **Self-Hostable, Fully Open Source** — Run your own relay with a single command. The relay is MIT-licensed with no enterprise tier, no feature gating, and no call-home. Your relay, your rules.
23
+- **End-to-End Tenant TLS And ECH** - Because relays are trustless, Portal
24
+ terminates tenant TLS at the user's endpoint instead of the relay. Portal also
25
+ provides ECH to avoid exposing the real hostname in plaintext SNI.
26
19
-- **Anonymous Relay Network** — Because relays are trustless, you can connect to any public relay in the registry without compromising your privacy. Combine self-hosted relays with public relays in a pool — or chain them in a multi-hop route — to split trust across independent operators you choose.
27
+- **Built-in MITM Detection** - Portal actively self-probes its own connection
28
+ after real traffic begins. It compares TLS keying material exported on both
29
+ sides and treats a mismatch as suspected relay-side TLS termination.
30
21
-- **Multi-Hop Relay Routing** — Chain multiple relays together (similar to Tor). No single relay knows both the origin and the destination of the traffic. Use `--multi-hop-depth 3` to select a three-hop route automatically.
31
+- **Multi-Hop Relay Routing** - Chain multiple relays together so no single
32
+ relay knows both the origin and the destination. Use `--multi-hop-depth 3` to
33
+ select a three-hop route automatically.
34
23
-- **No Accounts, No API Keys** — Authentication uses SIWE (Sign-In with Ethereum) with a locally generated secp256k1 key pair. No email, no registration, no vendor lock-in.
35
+- **No Accounts, No API Keys** - Authentication uses SIWE-compatible signing
36
+ with a locally generated secp256k1 key pair. No email, no registration, no
37
+ vendor lock-in.
38
+
39
+- **Built-in x402 Payments** - Routed HTTP paths can require Sui gasless
40
+ USDC x402 payment before proxying. Browser apps can import `/x402/client.js`,
41
+ and native clients can call `/x402/prepare` directly and send `X-PAYMENT`.
42
43
## Comparison
44
45
| | Portal | ngrok | Cloudflare Tunnel | frp |
46
|---|---|---|---|---|
47
+| Public localhost URL | **Yes** | Yes | Yes | Yes |
48
+| Self-hostable | **Yes** | Enterprise only | No | Yes |
49
+| Open source | **MIT** | No | Client only | Apache 2.0 |
50
+| Custom domain | **Yes** | Paid plans | Yes | Yes |
51
| End-to-end tenant TLS | **Yes** | No | No | No |
52
| SNI hiding (ECH) | **Yes** | No | No | No |
53
| MITM self-probe | **Built-in** | No | No | No |
32
-| Multi-hop routing | **Yes** | No | No | No |
54
| Multi-relay failover | **Yes** | Managed | Built-in | No |
34
-| Self-hostable | **Yes** | Enterprise only | No | Yes |
35
-| Custom domain | **Yes** | Paid plans | Yes | Yes |
36
-| Raw TCP port routing | **Yes** | Paid plans | No | Yes |
37
-| UDP routing | **Yes** | Yes | Yes | Yes |
38
-| Open source | **MIT** | No | Client only | Apache 2.0 |
55
+| Multi-hop routing | **Yes** | No | No | No |
56
| Account required | **No** | Yes | Yes | No |
57
+| Native x402 payments | **Yes** | No | No | No |
58
59
## Quick Start
60
86
--http-route /api=http://127.0.0.1:3001 \
87
--http-route /=http://127.0.0.1:5173
88
89
+# Require Sui USDC x402 payment before proxying a route
90
+portal expose --name paid-app \
91
+ --http-route "/paid=http://127.0.0.1:3001 GET:0.01" \
92
+ --http-route /=http://127.0.0.1:5173 \
93
+ --x402-pay-to 0x...
94
+
95
# Raw TCP port (Minecraft, databases, SSH)
96
portal expose localhost:25565 --name minecraft --tcp
97
99
portal expose 3000 --multi-hop-depth 3
100
```
101
102
+See [CLI Reference](cmd/portal-tunnel/README.md) for the full route syntax and
103
+[API Reference](docs/src/routes/api-reference/+page.md#payments) for the x402
104
+helper endpoints.
105
+
106
### Keep tunnels running with Portal Agent
107
108
Use `portal agent run` when tunnels should keep running outside your terminal.
129
docker compose up
130
```
131
104
-For public deployment with DNS automation (ACME), TCP/UDP port ranges, and relay policy, see [Deployment](docs/src/routes/deployment/+page.md).
132
+For public deployment with DNS automation (ACME), TCP/UDP port ranges, and relay
133
+policy, see [Deployment](docs/src/routes/deployment/+page.md).
134
135
## How End-to-End Encryption Works
136
137
```text
138
Browser
110
- → Relay SNI router (reads only routing token, forwards raw bytes)
111
- → Reverse session
112
- → Portal tunnel (performs TLS handshake locally, derives session keys)
113
- → Local service
139
+ -> Relay SNI router (reads only routing token, forwards raw bytes)
140
+ -> Reverse session
141
+ -> Portal tunnel (performs TLS handshake locally, derives session keys)
142
+ -> Local service
143
```
144
116
-1. The relay accepts the incoming connection and reads only the TLS ClientHello for SNI-based routing.
117
-2. It forwards the raw encrypted stream over the reverse session without terminating TLS.
118
-3. The Portal tunnel on your side completes the TLS handshake locally. Session keys are derived on your machine.
119
-4. For relay-hosted domains, the tunnel obtains certificate signatures via `/v1/sign`, using the relay only as a keyless signing oracle. The relay signs handshake digests but never receives session keys.
120
-5. After the handshake, the relay continues forwarding ciphertext without access to plaintext.
121
-
122
-When ECH is enabled, the relay also cannot see the actual tenant hostname. It routes by an opaque token derived from the tunnel identity, while the real SNI stays inside the ECH-protected ClientHello.
145
+1. The relay accepts the incoming connection and reads only the TLS ClientHello
146
+ for SNI-based routing.
147
+2. It forwards the raw encrypted stream over the reverse session without
148
+ terminating TLS.
149
+3. The Portal tunnel on your side completes the TLS handshake locally. Session
150
+ keys are derived on your machine.
151
+4. For relay-hosted domains, the tunnel obtains certificate signatures via
152
+ `/v1/sign`, using the relay only as a keyless signing oracle. The relay signs
153
+ handshake digests but never receives session keys.
154
+5. After the handshake, the relay continues forwarding ciphertext without access
155
+ to plaintext.
156
+
157
+When ECH is enabled, the relay also cannot see the actual tenant hostname. It
158
+routes by an opaque token derived from the tunnel identity, while the real SNI
159
+stays inside the ECH-protected ClientHello.
160
161
## How Multi-Hop Routing Works
162
163
```text
164
Browser
128
- → Entry relay (sees only the opaque route hostname)
129
- → Middle relay (sees only the next-hop token)
130
- → Exit relay (sees only the reverse session token)
131
- → Portal tunnel
132
- → Local service
165
+ -> Entry relay (sees only the opaque route hostname)
166
+ -> Middle relay (sees only the next-hop token)
167
+ -> Exit relay (sees only the reverse session token)
168
+ -> Portal tunnel
169
+ -> Local service
170
```
171
135
-Each relay in the chain knows only its immediate neighbors. No single relay holds the full path. Tenant TLS still terminates only on your side — no relay in the chain receives tenant TLS plaintext.
172
+Each relay in the chain knows only its immediate neighbors. No single relay
173
+holds the full path. Tenant TLS still terminates only on your side, so no relay
174
+in the chain receives tenant TLS plaintext.
175
176
## Public Relay Registry
177
181
https://raw.githubusercontent.com/gosuda/portal-tunnel/main/registry.json
182
```
183
145
-Tunnel clients include this registry by default. If you operate a public Portal relay, open a pull request to add your relay URL to `registry.json`.
184
+Tunnel clients include this registry by default. If you operate a public Portal
185
+relay, open a pull request to add your relay URL to `registry.json`.
186
187
## Documentation
188
204
205
## License
206
167
-MIT License — see [LICENSE](LICENSE).
207
+MIT License - see [LICENSE](LICENSE).