npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@explita/tunnel

v0.2.1

Published

Secure reverse tunneling CLI & library with application-layer encryption (ECDH P-256 + AES-256-GCM) over WebSockets

Readme

@explita/tunnel

Client CLI and library for Explita Tunnel to securely expose local HTTP and WebSocket servers to the public internet with encrypted application-layer tunnel transport using ephemeral ECDH P-256 key exchange and AES-256-GCM, carried over TLS-secured WebSockets.

🚀 Installation

Global CLI

npm install -g @explita/tunnel
# or
pnpm add -g @explita/tunnel

Run via npx / pnpm dlx

npx @explita/tunnel 3000

💻 CLI Usage

Basic Usage

Expose a local server running on port 3000:

eta 3000

Request a custom subdomain:

eta 3000 --subdomain my-app
# or
eta 3000 -s my-app

[!NOTE] Subdomains are first-come, first-served. Custom subdomains are not permanently owned or reserved. While your tunnel session is active, the subdomain is locked exclusively to your connection. Once you disconnect, the subdomain is released immediately and can be claimed by anyone.

Connect with an explicit token (or use auto-saved token):

eta 3000 -s my-app -t my-secret-token

Connect to a custom tunnel server:

eta 3000 -S wss://tunnel.mycompany.com/_connect

🔒 HTTP Basic Authentication (-a, --auth)

Protect your public tunnel with username and password credentials. When enabled, incoming HTTP requests without valid credentials receive 401 Unauthorized with a WWW-Authenticate: Basic challenge:

eta 3000 -a "alice:secret123"
# or
eta 3000 --auth "alice:secret123"

🛡️ IP / CIDR Allowlisting (-i, --allow-ip)

Restrict tunnel access exclusively to specific developer IP addresses or third-party webhook gateways (e.g. Stripe, Shopify, GitHub). All other requests are rejected with 403 Forbidden:

# Allow only your office public IP
eta 3000 -i 203.0.113.50

# Allow a local or VPN subnet
eta 3000 -i 10.0.0.0/8

# Allow multiple IPs and webhook CIDRs (e.g. Stripe webhook ranges)
eta 3000 -i "54.187.174.169/32, 54.187.205.235/32, 192.168.1.0/24"

You can also specify allowIp directly in .etarc:

{
  "port": 3000,
  "subdomain": "my-webhook",
  "allowIp": [
    "54.187.174.169/32",
    "10.0.0.0/8"
  ]
}

Authentication & Tokens

Request and save an authentication token directly from your terminal:

# 1. Interactive: requests a 6-digit OTP and prompts directly in terminal
eta token -e [email protected]

# 2. Direct: verify with OTP code via flag
eta token -e [email protected] -v 123456

Once verified, your token is securely stored in ~/.explita/eta/config.json. From then on, running eta <port> is authenticated automatically!

eta token          # View currently saved token and email
eta token --clear  # Clear saved token

📁 Project Configuration (.etarc & eta init)

Instead of specifying flags on every run, configure project-level defaults in an .etarc file in your repository:

# Initialize with a port
eta init 3000

# Or include preferred subdomain and basic authentication
eta init 3000 -s my-app -a "alice:secret123"

This creates an .etarc file in your project root:

{
  "port": 3000,
  "subdomain": "my-app",
  "auth": "alice:secret123"
}

Once .etarc exists, simply run eta with zero arguments!

eta

Configuration Precedence

Explita Tunnel resolves settings in the following order:

  1. Command-line flags (--port, --subdomain, --auth, etc.)
  2. Project config (.etarc in the current working directory)
  3. Machine config (~/.explita/eta/config.json)
  4. Built-in defaults

[!NOTE] Project-specific settings like port, subdomain, and auth are strictly repository-level (.etarc) and are never written to your global machine config to prevent cross-project collisions.

⚙️ Configuration Management (eta config)

View and manage tunnel settings:

eta config list               # Display resolved project and global configuration
eta config path               # Show file paths for project and global configs
eta config get port           # Read a specific setting
eta config set port 8080      # Save setting to project .etarc (or --global)

📋 Complete CLI Flags & Commands Reference

Commands

| Command | Description | Example | | :-------------------- | :------------------------------------------------------------------- | :---------------------------- | | eta <port> | Start tunnel exposing local port | eta 3000 | | eta | Start tunnel using local .etarc configuration | eta | | eta init [port] | Initialize or update project .etarc file | eta init 3000 -s my-app | | eta config [cmd] | Manage local and global configuration (list, get, set, path) | eta config list | | eta token [options] | Request and verify 6-digit email OTP developer token | eta token -e [email protected] |

Options & Flags

| Flag | Long Option | Type | Default | Description | | :--- | :----------------------- | :----- | :------ | :-------------------------------------------------------------------------------- | | -p | --port <num> | Number | None | Local TCP port of your running web server or API (required unless in .etarc) | | -s | --subdomain <name> | String | Random | Request custom subdomain (3 to 32 characters, lowercase alphanumeric and hyphens) | | -a | --auth <user:pass> | String | None | Protect tunnel with HTTP Basic Authentication | | -i | --allow-ip <cidr> | String | None | Restrict tunnel access to specific IP addresses or CIDR ranges | | -t | --token <token> | String | Saved | Authentication token override (or via TUNNEL_TOKEN environment variable) | | -S | --server <url> | String | Gateway | Custom WebSocket tunnel gateway server URL | | -R | --no-reconnect | Flag | false | Do not automatically reconnect if the connection drops | | -E | --no-encrypt | Flag | false | Disable inner application-layer encryption (debug mode) | | | --inspect [port] | Number | 39755 | Enable local request inspector web UI (optional port override, e.g. --inspect 5000)| | | --no-inspect | Flag | false | Disable local request inspector web dashboard (runs in quiet/headless mode) | | -h | --help | Flag | - | Show help message and exit |

Interactive Terminal Hotkeys

While an active tunnel session is running in your terminal, the following keyboard shortcuts are available:

| Key | Action | Description | | :----------: | :--------- | :--------------------------------------------------------------------------------- | | r | Replay | Replays the most recently received HTTP request directly against your local server | | c | Clear | Clears the terminal output buffer without interrupting the active tunnel | | h | Help | Displays interactive keyboard shortcut help in the terminal |

🔍 In-Memory Request Inspector & Webhook Replay

Every active tunnel session comes with a zero-dependency, local web inspector running on http://127.0.0.1:39755 (auto-increments if the port is busy):

  • Real-Time Traffic: Live streaming of incoming HTTP requests and WebSocket frames via Server-Sent Events (SSE).
  • Deep Inspection: Inspect headers, query params, parsed JSON bodies, and raw request/response payloads.
  • Image Previews: Automatic transparency checkerboard previews for Content-Type: image/* (PNG, SVG, JPEG, WebP, GIF, AVIF, ICO, BMP).
  • One-Click Replay: Instantly resend any recorded webhook or API request directly to your local server without re-triggering external services.
  • Interactive Terminal Hotkeys:
    • Press r in the terminal to replay the latest request.
    • Press c to clear the terminal screen.
    • Press h to toggle keyboard shortcuts.

[!IMPORTANT] Strictly Local & Zero-Logging: The inspector dashboard and its request buffer run 100% locally in memory on http://127.0.0.1:39755. Requests, headers, and payloads are captured directly by your local CLI agent and never pass through or get stored on the remote tunnel server. Nothing is written to disk or sent to external telemetry.

To disable the web inspector UI, use --no-inspect (or set "inspect": false in .etarc). To customize the port, use --inspect [port] (e.g. eta 3000 --inspect 5000 or "inspect": 5000 in .etarc).

🌐 Multi-Tenant & Nested Subdomains (-- separator)

Testing multi-tenant SaaS applications, tenant subdomains, or preview environments locally is seamless with Explita Tunnel.

When you register a base tunnel subdomain:

eta 3000 -s myapp
# Public URL: https://myapp.tunnel.explita.ng

Any request prefixed with a double hyphen (--) automatically routes to your active tunnel session:

  • https://tenant1--myapp.tunnel.explita.ng ➔ routes to your local server
  • https://acme--myapp.tunnel.explita.ng ➔ routes to your local server
  • https://docs--acme--myapp.tunnel.explita.ng ➔ routes to your local server

Why -- instead of dots?

Public wildcard TLS certificates (*.tunnel.explita.ng) strictly cover one dot level under RFC 6125 standards. The double-hyphen convention allows any number of nested tenant levels to be fully covered by the standard wildcard certificate without requiring separate On-Demand TLS certs, while preserving normal hyphenated names like my-app or acme-corp.

Injected Context Headers

Explita Tunnel automatically attaches routing context to forwarded requests and WebSockets so your middleware or application logic can easily inspect the calling tenant:

| Header | Example Value | Description | | :--------------------------- | :--------------------------------- | :------------------------------- | | x-explita-subdomain | tenant1--myapp | The full requested subdomain | | x-explita-parent-subdomain | myapp | Your registered tunnel subdomain | | x-explita-subdomain-prefix | tenant1 | The nested tenant/prefix | | x-explita-tenant | tenant1 | Convenience alias for the tenant | | x-explita-subdomain-parts | tenant1 (or docs,acme) | Comma-separated nested levels | | x-forwarded-host | tenant1--myapp.tunnel.explita.ng | Original requested host | | x-explita-request-id | uuid | Unique trace ID for the request |

Example in Next.js / Express Middleware:

export function middleware(request: Request) {
  // Read tenant from Explita header
  const tenant = request.headers.get("x-explita-tenant");

  if (tenant) {
    console.log(`Handling request for tenant: ${tenant}`);
  }
}

⚙️ CLI Options

Tunnel Flags (eta <port>)

| Flag | Shorthand | Default | Description | | ------------------------- | --------- | -------------------- | ---------------------------------------------------------------------------- | | --port <number> | -p | (positional) | Local port to expose (e.g. 3000, 8080) | | --subdomain <name> | -s | auto-generated | Request a custom subdomain (3 to 32 chars, first-come first-served) | | --server <url> | -S | $TUNNEL_SERVER_URL | Tunnel server WebSocket URL (defaults to wss://tunnel.explita.ng/_connect) | | --token <token> | -t | $TUNNEL_TOKEN | Authentication token for the tunnel server | | --inspect [port] | — | 39755 | Enable local web inspector UI and optionally set custom port | | --no-inspect | — | false | Disable local web inspector UI dashboard (runs in quiet/headless mode) | | --no-reconnect | -R | false | Do not automatically reconnect if the connection drops | | --no-encrypt | -E | false | Disable inner AES-256-GCM encryption (send plaintext JSON) | | --help | -h | — | Show help information |

Token Commands (eta token / eta login)

eta token [options]
eta login [options]

| Flag / Command | Shorthand | Default | Description | | ----------------- | --------- | -------------------- | ----------------------------------------------------------- | | eta token | — | — | Show currently saved token, email, and config file location | | --email <email> | -e | — | Your email address to request a 6-digit verification code | | --verify <code> | -v | — | 6-digit OTP code to verify and save token (alias: --otp) | | --clear | — | — | Clear saved token and email from local configuration | | --server <url> | -S | $TUNNEL_SERVER_URL | Tunnel server URL (defaults to https://tunnel.explita.ng) | | --help | -h | — | Show token command help |

🔒 Security & Verification

Explita Tunnel employs defense-in-depth: while connections are carried over TLS/WSS, an additional application-layer encryption layer runs inside the tunnel stream.

Client ephemeral P-256 key
          │
          │ ECDH (prime256v1)
          ▼
     Shared secret
          │
          │ HKDF-SHA-256 ("explita-tunnel-e2ee")
          ▼
   256-bit session key
          │
          ▼
      AES-256-GCM (12-byte random IV + auth tag)

Every session establishes an ephemeral ECDH P-256 shared secret, derives a 256-bit key via HKDF-SHA-256, and encrypts all HTTP requests, response chunks, headers, and WebSocket frames with authenticated AES-256-GCM.

1. Session Fingerprint Verification

When connected, the client and server bind the key exchange and display a cryptographic SHA-256 session fingerprint:

Session Fingerprint = SHA-256("explita-tunnel-v1" || client_pubkey || server_pubkey)
Explita Tunnel v0.2.0
——————————————————————————————————————————————
✓ Authenticated • Token active
✓ Key exchange complete • ECDH P-256 + AES-GCM (256-bit)

Public URL:  https://my-app.tunnel.explita.ng
Forwarding:  http://localhost:3000
Fingerprint: SHA256:7a:91:4f:2c:81:d3:05:e2:...

Incoming Traffic Stream:
200 OK GET /api/auth/session 12ms
101 SW WSS /ws/live-stream [Encrypted Frame]
200 OK POST /webhooks/stripe 34ms
502 Bad Gateway GET /api/users 3ms (ECONNREFUSED :3000)

You can compare the client and server fingerprints to verify that both sides derived matching keys for the exact same key exchange participants, eliminating Man-in-the-Middle concerns.

📦 Programmatic Usage

You can embed the tunnel client directly inside Node.js applications or testing suites:

import { TunnelAgent } from "@explita/tunnel";

const agent = new TunnelAgent({
  port: 3000,
  subdomain: "my-app",
  server: "ws://localhost:39755/_connect",
  token: "my-token",
  reconnect: true,
  encrypt: true,
});

// Start the tunnel
const tunnel = await agent.start();
console.log(`Public URL: ${tunnel.url}`);

// Stop when done
process.on("SIGINT", () => {
  agent.stop();
  process.exit(0);
});

🔌 WebSocket & Dev Server Support

The agent includes full duplex WebSocket proxying out of the box. Hot Module Replacement (HMR) and live-reloading work seamlessly with modern web dev frameworks:

  • Next.js (Turbopack / Webpack HMR)
  • Vite
  • Astro / Nuxt / SvelteKit / Remix
  • Custom WebSocket servers (ws, Socket.IO, GraphQL subscriptions)

⚖️ Production Limits & Server Guardrails

To protect shared gateway infrastructure against abuse, the server applies the following operational limits:

| Guardrail | Limit | Description | | :---------------------- | :--------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- | | Concurrent Tunnels | 5 active per token | Connecting a 6th active tunnel will fail with MAX_TUNNELS_EXCEEDED. | | Request Rate Limit | 100 req/s per tunnel | Requests above 100 req/s receive HTTP 429 Too Many Requests (Retry-After: 1). | | Max Request Payload | 25 MB | HTTP request bodies above 25MB are rejected with HTTP 413 Payload Too Large. | | Session Lifetime | 24 hours | Active connections are closed gracefully once a day (code 1000) so the agent automatically reconnects and rotates cryptographic keys. | | Subdomain Lock | 10 seconds | Atomic lock during connection prevents race conditions when registering subdomains. |

📄 License

MIT © Explita