lecoder-tunnel
v0.1.0
Published
Secure tunneling CLI — wraps cloudflared, tailscale, and portless with hardened security middleware. Fixes every wormhole vulnerability by design.
Downloads
98
Maintainers
Readme
lecoder-tunnel
Secure tunneling CLI — wraps cloudflared, tailscale, and portless with hardened security middleware.
The Problem
AI engineers pay $20–200/month for coding agents and expose local development servers to the internet via tunnels — ngrok, wormhole, cloudflared quick tunnels. These tools make sharing easy, but they were not designed as security boundaries.
We audited wormhole and found 3 critical and 5 high severity vulnerabilities in default mode:
- The inspector CORS header is set to
*, meaning any website can silently exfiltrate all traffic through the tunnel - Zero path filtering — your
/.env,/.git, and AWS credentials are one URL away - Error messages include
localhost:3000and full system paths, leaking your infrastructure to anyone who triggers an error - No body size limit — a 10 GB request body can OOM-crash your service
- Auth tokens never expire and are never verified with HMAC
- No rate limiting on any endpoint
These are not edge cases. They are the default behavior.
The Solution
lecoder-tunnel is a security middleware layer that sits between your tunnel backend and your local service. It fixes all of these vulnerabilities by default, without requiring configuration changes.
You install one tool. You get a hardened tunnel.
Internet → [cloudflared / tailscale / portless] → [lecoder-tunnel proxy :random] → [your service :port]The proxy lives on a random OS-assigned port. Your tunnel backend connects to the proxy, not your service directly. Every request passes through the security middleware stack before it reaches your code.
Quick Start
# Install globally
npm i -g lecoder-tunnel
# Expose port 3000 with security hardening (default settings)
lecoder-tunnel expose 3000
# Require an API key for all requests
lecoder-tunnel expose 3000 --key
# Limit to specific paths
lecoder-tunnel expose 3000 --allow-paths "/api/**,/health"
# See all detected tunnel backends
lecoder-tunnel status
# Show your Tailscale mesh (read-only)
lecoder-tunnel mesh
# Manage API keys
lecoder-tunnel keys --generate
lecoder-tunnel keys --list
lecoder-tunnel keys --revoke <id>When you run expose, the CLI:
- Starts a security proxy on a random port
- Generates an API key if
--keywas passed (shown once, then only the hash is stored) - Starts your chosen tunnel backend pointed at the proxy
- Prints the public URL and a full security status table
- Cleans up everything when you press Ctrl+C
What Gets Protected
| Attack | Without lecoder-tunnel | With lecoder-tunnel |
|--------|----------------------|---------------------|
| Fetch /.env, /.git, /.aws | Exposed to anyone with the URL | Blocked with 403 |
| Send a 10 GB request body (OOM DoS) | Service crashes | Rejected with 413, 10 MB limit |
| CORS * lets any site exfiltrate traffic | All requests silently stolen | Same-origin by default, explicit allowlist only |
| Error response includes localhost:3000 or /Users/arya/... | Internal paths leaked to attacker | Scrubbed — generic "Internal Server Error" |
| Brute force API with no throttle | Unlimited | 60 req/min per IP, sliding window |
| Unauthenticated tunnel access | Anyone with the URL can use it | API key required with --key |
| Stack traces in 500 responses | Full stack frames sent to caller | Logged locally only, never sent to client |
All protections are on by default except API key auth, which is opt-in with --key.
CLI Reference
lecoder-tunnel expose <port>
Expose a local port through a secure tunnel.
Options:
--backend <name> Tunnel backend: cloudflared (default)
--subdomain <name> Request a specific subdomain (backend-dependent)
--key Generate and require an API key for this tunnel
--allow-paths <globs> Comma-separated allowlist, e.g. "/api/**,/health"
--deny-paths <globs> Comma-separated denylist, e.g. "/admin/**,/.env"
--max-body-size <size> Max request body size (default: 10mb)
--rate-limit <n> Requests per minute per IP (default: 60)
--cors <origins> Comma-separated allowed CORS origins
--no-security Disable all security middleware (not recommended)Path patterns support three forms:
/foo— exact match/foo/*— single-level wildcard (matches/foo/bar, not/foo/bar/baz)/foo/**— recursive wildcard (matches/foo,/foo/bar,/foo/bar/baz)
The default deny list blocks: /.env, /.git/**, /.aws/**, /.ssh/**, /node_modules/**, /.docker/**.
lecoder-tunnel mesh
Show Tailscale mesh status in a table. Read-only — never modifies your Tailscale state.
No options.lecoder-tunnel keys
Manage API keys for secured tunnels.
Options:
--generate Generate a new API key
--list List all API keys (with status, prefix, last-used date)
--revoke <id> Revoke an API key by IDAPI keys use the lt_ prefix followed by 64 hex characters. Only the SHA-256 hash is stored on disk — the raw key is shown exactly once at generation time.
Keys are accepted via X-API-Key header or Authorization: Bearer <key>.
lecoder-tunnel status
Show detected backends and their install status.
Options:
--json Output as JSONChecks for: cloudflared, tailscale (funnel), portless, wormhole.
Security Middleware Stack
Every request through the proxy passes through this stack in order:
- Body Limiter — Rejects requests with
Content-Lengthexceeding the limit (default 10 MB) before the body is read. Returns 413. - Error Sanitizer — Catches all unhandled errors. Logs the real error to stderr locally. Sends only
{"error":"Internal Server Error"}to the caller. Also scrubs sensitive strings (home paths,localhost:3000, stack frames) from successful responses. - CORS — Sets
Access-Control-Allow-Originonly for explicitly listed origins. Default is no CORS headers — same-origin requests only. Wildcard*is supported but never the default. - Rate Limiter — Per-IP sliding window. Tracks request timestamps in memory. Returns 429 with a
retryAftervalue when the limit is hit. - API Key Auth — Optional. Checks
X-API-KeythenAuthorization: Bearer. Validates against stored SHA-256 hashes. UpdateslastUsedAton success. - Request Filter — Checks path against deny list, then allow list. Deny wins. Returns 403.
Configuration
Global config lives in ~/.lecoder-tunnel/ with owner-only permissions (mode 0700).
~/.lecoder-tunnel/
config.json # Default settings (mode 0600)
keys.json # API key hashes (mode 0600)
tunnels.json # Active tunnel stateDefault config.json:
{
"defaultBackend": "cloudflared",
"proxy": {
"maxBodySize": "10mb",
"rateLimit": 60,
"cors": []
},
"security": {
"defaultDenyPaths": [
"/.*",
"/.env",
"/.git",
"/.ssh",
"/__pycache__",
"/node_modules"
],
"requireApiKey": false
}
}Edit this file to change defaults. CLI flags override config values per-session.
Installing a Backend
lecoder-tunnel wraps existing tunnel tools. Install at least one:
cloudflared (default, recommended)
# macOS
brew install cloudflared
# Linux
curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.debTailscale
# macOS
brew install tailscale
# Linux
curl -fsSL https://tailscale.com/install.sh | shVerify detection:
lecoder-tunnel statusTesting
bun test # 84 tests, all passing
bun run lint # TypeScript check
bun run build # Build for npm distTests cover: body limiter (edge cases, all size units), error sanitizer (path scrubbing, JSON sanitization, stack trace removal), request filter (glob matching, deny/allow priority), API keys (generate, validate, revoke, hash-only storage), CORS middleware, rate limiter, and cloudflared backend process detection.
Security Audit Origin
This project was born from a security audit of wormhole (MuhammadHananAsghar/wormhole), an otherwise excellent ngrok alternative. We found 3 critical and 5 high severity issues in its default configuration. Rather than file a bug report and move on, we built the security layer we wished existed for every tunnel tool.
See SECURITY.md for the full vulnerability table with CWE references and how each issue is addressed.
Credits and Acknowledgments
- Wormhole (MuhammadHananAsghar/wormhole) — An excellent ngrok alternative whose security gaps motivated this project. The audit findings here are responsible disclosure, not criticism of the project's goals.
- cloudflared (Cloudflare) — Powers the default tunnel backend. Cloudflare's zero-config quick tunnels are genuinely good. lecoder-tunnel adds the security layer they don't include.
- Tailscale — Mesh networking integration (read-only). The
meshcommand surfaces your Tailscale device status without touching your configuration. - Portless (vercel-labs/portless) — Local dev URL support, detected and reported in
status. - Hono — The HTTP framework powering the security proxy. Lightweight, fast, and works natively in Bun.
- Built by Arya Teja (@aryateja2106) as part of the LeCoder open source initiative by LeSearch AI.
License
MIT — see LICENSE.
