{"id":47681621,"url":"https://github.com/gosuda/portal-tunnel","last_synced_at":"2026-06-01T07:00:49.991Z","repository":{"id":319824645,"uuid":"1079713761","full_name":"gosuda/portal-tunnel","owner":"gosuda","description":"self-hosted, trustless relay network for exposing localhost","archived":false,"fork":false,"pushed_at":"2026-05-26T14:04:25.000Z","size":94722,"stargazers_count":259,"open_issues_count":14,"forks_count":25,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-26T15:35:05.289Z","etag":null,"topics":["e2ee","end-to-end-encryption","go","nat-traversal","ngrok-alternative","onion-routing","relay","self-hosted","self-hosting","tor","trustless","tunneling"],"latest_commit_sha":null,"homepage":"https://gosuda.github.io/portal-tunnel/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gosuda.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-10-20T09:14:19.000Z","updated_at":"2026-05-22T09:17:24.000Z","dependencies_parsed_at":"2026-03-09T08:04:19.518Z","dependency_job_id":"7dee4d70-59a4-49a6-856b-48c882318114","html_url":"https://github.com/gosuda/portal-tunnel","commit_stats":null,"previous_names":["gosuda/relaydns","gosuda/portal","gosuda/portal-tunnel"],"tags_count":48,"template":false,"template_full_name":null,"purl":"pkg:github/gosuda/portal-tunnel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gosuda%2Fportal-tunnel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gosuda%2Fportal-tunnel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gosuda%2Fportal-tunnel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gosuda%2Fportal-tunnel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gosuda","download_url":"https://codeload.github.com/gosuda/portal-tunnel/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gosuda%2Fportal-tunnel/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33763655,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-01T02:00:06.963Z","response_time":115,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["e2ee","end-to-end-encryption","go","nat-traversal","ngrok-alternative","onion-routing","relay","self-hosted","self-hosting","tor","trustless","tunneling"],"created_at":"2026-04-02T14:00:35.162Z","updated_at":"2026-06-01T07:00:49.985Z","avatar_url":"https://github.com/gosuda.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Portal - The Trustless Relay Network for Localhost\n\n[English](./README.md) | [简体中文](./README.zh-CN.md)\n\n\u003cp align=\"center\"\u003e\u003cimg width=\"800\" alt=\"Portal Demo\" src=\"./portal.gif\" /\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\u003cb\u003eExpose local services to the public internet with zero trust in the relay operator.\u003c/b\u003e\u003cbr/\u003eNo port forwarding. No inbound firewall rules. No manual DNS setup. No surveillance.\u003c/p\u003e\n\n## Why Portal? The Trustless Advantage\n\nMost 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**.\n\n- **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.\n\n- **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.\n\n- **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.\n\n- **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.\n\n- **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.\n\n- **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.\n\n- **x402 Payments** — Turn APIs, content, and local web apps into payable internet endpoints without centralized billing gateway.\n\n## Comparison\n\n| | Portal | ngrok | Cloudflare Tunnel | frp |\n|---|---|---|---|---|\n| End-to-end tenant TLS | **Yes** | No | No | No |\n| SNI hiding (ECH) | **Yes** | No | No | No |\n| MITM self-probe | **Built-in** | No | No | No |\n| Multi-hop routing | **Yes** | No | No | No |\n| Multi-relay failover | **Yes** | Managed | Built-in | No |\n| Self-hostable | **Yes** | Enterprise only | No | Yes |\n| Custom domain | **Yes** | Paid plans | Yes | Yes |\n| Raw TCP port routing | **Yes** | Paid plans | No | Yes |\n| UDP routing | **Yes** | Yes | Yes | Yes |\n| Open source | **MIT** | No | Client only | Apache 2.0 |\n| Account required | **No** | Yes | Yes | No |\n\n## Quick Start\n\n### Expose a local service\n\n**macOS / Linux:**\n\n```bash\ncurl -fsSL https://github.com/gosuda/portal-tunnel/releases/latest/download/install.sh | bash\nportal expose 3000\n```\n\n**Windows (PowerShell):**\n\n```powershell\n$ProgressPreference = 'SilentlyContinue'\nirm https://github.com/gosuda/portal-tunnel/releases/latest/download/install.ps1 | iex\nportal expose 3000\n```\n\nPortal prints a public HTTPS URL for your local app instantly. More examples:\n\n```bash\n# Custom name and relay\nportal expose 3000 --name myapp --relays https://portal.example.com --discovery=false\n\n# Mount frontend and API behind one URL\nportal expose --name myapp \\\n  --http-route /api=http://127.0.0.1:3001 \\\n  --http-route /=http://127.0.0.1:5173\n\n# Require x402 payment before a local HTTP upstream receives traffic\nportal expose 3000 --name paid-api \\\n  --x402-facilitator-url https://portal.example.com/api/x402 \\\n  --x402-network eip155:8453 \\\n  --x402-price \"$0.001\"\n\n# Raw TCP port (Minecraft, databases, SSH)\nportal expose localhost:25565 --name minecraft --tcp\n\n# Three-hop route for maximum anonymity\nportal expose 3000 --multi-hop-depth 3\n```\n\n### Keep tunnels running with Portal Agent\n\nUse `portal agent run` when tunnels should keep running outside your terminal.\nIt runs as a local OS service, keeps every tunnel in one TOML config alive, and\nprovides a dashboard for relay and multi-hop management.\n\n```bash\nportal agent run --config config.toml\nportal agent dashboard --config config.toml\nportal agent restart\nportal agent stop\n\n# Foreground mode skips OS service installation.\nportal agent run --config config.toml --foreground\n```\n\nSee [Portal Agent](docs/src/routes/portal-agent/+page.md) for the config format.\n\n### Run your own relay\n\n```bash\ngit clone https://github.com/gosuda/portal-tunnel\ncd portal-tunnel \u0026\u0026 cp .env.example .env\ndocker compose up\n```\n\nFor public deployment with DNS automation (ACME), TCP/UDP port ranges, and relay policy, see [Deployment](docs/src/routes/deployment/+page.md).\n\n## How End-to-End Encryption Works\n\n```text\nBrowser\n  → Relay SNI router  (reads only routing token, forwards raw bytes)\n  → Reverse session\n  → Portal tunnel     (performs TLS handshake locally, derives session keys)\n  → Local service\n```\n\n1. The relay accepts the incoming connection and reads only the TLS ClientHello for SNI-based routing.\n2. It forwards the raw encrypted stream over the reverse session without terminating TLS.\n3. The Portal tunnel on your side completes the TLS handshake locally. Session keys are derived on your machine.\n4. 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.\n5. After the handshake, the relay continues forwarding ciphertext without access to plaintext.\n\nWhen 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.\n\n## How Multi-Hop Routing Works\n\n```text\nBrowser\n  → Entry relay  (sees only the opaque route hostname)\n  → Middle relay (sees only the next-hop token)\n  → Exit relay   (sees only the reverse session token)\n  → Portal tunnel\n  → Local service\n```\n\nEach 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.\n\n## Public Relay Registry\n\nPortal's official public relay registry is:\n\n```text\nhttps://raw.githubusercontent.com/gosuda/portal-tunnel/main/registry.json\n```\n\nTunnel 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`.\n\n## Documentation\n\n- [CLI Reference](cmd/portal-tunnel/README.md)\n- [Concepts](docs/src/routes/concepts/+page.md)\n- [Portal Agent](docs/src/routes/portal-agent/+page.md)\n- [Wallet and ENS](docs/src/routes/wallet-and-ens/+page.md)\n- [Security Model](docs/src/routes/security-model/+page.md)\n- [Architecture](docs/src/routes/architecture/+page.md)\n- [Deployment](docs/src/routes/deployment/+page.md)\n- [Configuration Reference](docs/src/routes/configuration/+page.md)\n\n## Contributing\n\n1. Fork the repository.\n2. Create a feature branch (`git checkout -b feature/amazing-feature`).\n3. Make the change with focused tests or docs.\n4. Open a pull request.\n\n## License\n\nMIT License — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgosuda%2Fportal-tunnel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgosuda%2Fportal-tunnel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgosuda%2Fportal-tunnel/lists"}