Microsoft Dev Tunnels provider plugin for OpenACP — expose local ports to the internet via the devtunnel CLI.
- Dev Tunnels integration — Uses Microsoft's
devtunnel hostCLI to create tunnels - Auto-start — Automatically starts the tunnel when the OpenACP API server is ready
- URL change detection — Monitors for URL changes after reconnects and emits events so other plugins automatically pick up the new URL
- Interactive install wizard — Validates CLI installation and authentication during setup
- Anonymous access — Configurable
--allow-anonymousflag (default: on) for webhook endpoints and viewer links - Configurable — Protocol, expiration, cluster region, and port options
- Security hardened — No shell interpolation, strict input validation, bounded buffers
- OpenACP CLI
>= 2026.0.0 - Node.js 18+
- Microsoft Dev Tunnels CLI (
devtunnel) - A Microsoft or GitHub account (for
devtunnel user login)
openacp plugin install @hahnfeld/devtunnel-providerThis launches an interactive wizard that validates your CLI installation and authentication.
npm install @hahnfeld/devtunnel-provider
# or
pnpm add @hahnfeld/devtunnel-providerBefore configuring the plugin you need the devtunnel CLI installed and authenticated.
-
Install the CLI:
# macOS brew install --cask devtunnel # Linux curl -sL https://aka.ms/DevTunnelCliInstall | bash # Windows winget install Microsoft.devtunnel
-
Log in (required — anonymous hosting is not supported by the CLI):
devtunnel user login
Or with GitHub:
devtunnel user login -g
Or with device code (when interactive browser login isn't possible):
devtunnel user login -d
For full details see the Dev Tunnels CLI reference.
If you installed via openacp plugin install, the wizard runs automatically. To re-configure:
openacp plugin configure @hahnfeld/devtunnel-providerThe wizard guides you through:
- CLI presence and authentication validation
- Port selection (specific port or auto-detect from API server)
- Anonymous access toggle
- Local server protocol hint (auto, HTTP, HTTPS — see Protocol)
- Optional tunnel expiration
Add the following to your plugin settings:
plugins:
devtunnel-provider:
enabled: true
port: null # null = auto-detect from API server, or specify e.g. 3100
allowAnonymous: true # Required for webhooks/viewer links
protocol: auto # auto | http | https
expiration: null # e.g., '4h', '2d', or null for session-lived
cluster: null # e.g., 'usw2', or null for auto-select| Field | Type | Default | Description |
|---|---|---|---|
enabled |
boolean |
true |
Enable the provider |
port |
number | null |
null |
Local port to tunnel. Null = auto-detect from API server |
allowAnonymous |
boolean |
true |
Allow unauthenticated access to tunnel URLs |
protocol |
string |
"auto" |
Local server protocol hint: auto, http, or https (see Protocol below) |
expiration |
string | null |
null |
Tunnel lifetime (e.g., 4h, 2d, max 30d). Null = deleted on exit |
cluster |
string | null |
null |
Preferred Azure region cluster (e.g., usw2) |
The protocol setting tells the Dev Tunnels relay what protocol your local server speaks — it does not affect the security of the public tunnel URL.
| Value | Meaning |
|---|---|
auto |
Let devtunnel detect the local protocol automatically (default) |
http |
Your local server speaks plain HTTP (the common case — Express, Fastify, Flask, etc.) |
https |
Your local server uses TLS locally (e.g., a self-signed cert on localhost) |
The public-facing tunnel URL is always HTTPS regardless of this setting. Traffic flows like this:
Internet ──HTTPS──▸ devtunnels.ms relay ──tunnel──▸ localhost (http or https)
Choosing http does not expose unencrypted traffic to the internet. It only describes the last hop on your own machine between the tunnel client and your local server.
- On startup, the plugin waits for the
api-server:startedevent - It spawns
devtunnel host -p <port> --allow-anonymousas a child process - It parses the public URL from the CLI output (
https://<id>-<port>.<cluster>.devtunnels.ms/) - The URL is available via
getPublicUrl()and thetunnel-provider:devtunnelservice - If the tunnel reconnects with a new URL, the plugin emits a
devtunnel-provider:url-changedevent
When a devtunnel host process reconnects after a network disruption, it may receive a new public URL. This plugin continuously monitors the tunnel output and:
- Detects the new URL via line-buffered stdout/stderr parsing
- Updates its internal state
- Emits a
devtunnel-provider:url-changedevent with{ oldUrl, newUrl }
Other plugins can subscribe to this event to update their webhook registrations, bot endpoints, etc.
| Event | Payload | Description |
|---|---|---|
devtunnel-provider:url-changed |
{ oldUrl, newUrl } |
Tunnel URL changed after reconnect |
devtunnel-provider:started |
{ publicUrl, port } |
Tunnel started successfully |
This plugin operates independently of OpenACP's built-in TunnelService. It registers itself as a standalone service (tunnel-provider:devtunnel) and manages its own lifecycle rather than integrating with the central tunnel registry.
This means:
addTunnel/listTunnels/stopTunnelcommands in the core tunnel system do not see Dev Tunnel instances- The plugin cannot be selected as a provider via the
tunnel.providerconfig option - Tunnel retry, keepalive, and persistence are handled by the plugin itself, not by
TunnelRegistry
Why: OpenACP's TunnelRegistry.createProvider() uses a closed factory — external providers cannot be registered into it. Adding support for plugin-provided tunnel providers would require a change to OpenACP core (e.g., a provider registration hook or a lookup into plugin services).
Planned: We'd like to contribute an extensible provider mechanism to OpenACP so that this plugin (and other third-party providers) can participate in the standard TunnelService lifecycle.
An important distinction: Dev Tunnels access control and OpenACP security are independent layers. Even when allowAnonymous: true (the default), your OpenACP instance is not unprotected. OpenACP enforces its own authentication and authorization on every incoming request — the tunnel is just a transport pipe.
Setting allowAnonymous: false adds a second layer of authentication at the Dev Tunnels relay (requiring a Microsoft/GitHub login to reach the URL at all), but this is not required for OpenACP to be secure. The default anonymous mode is the right choice for most users because:
- Webhook providers (Teams, Slack, etc.) cannot authenticate with Microsoft to reach your tunnel
- Viewer/share links need to work without requiring recipients to have a Microsoft account
- OpenACP already validates and authorizes every request that arrives through the tunnel
If you want defense-in-depth or are tunneling a service that has no auth of its own (i.e., not OpenACP), then allowAnonymous: false adds a useful extra gate.
This plugin is designed with security as a priority:
- No shell interpolation — All subprocess spawning uses
spawn()with argv arrays, nevershell: trueor string interpolation - Strict input validation — Port, protocol, expiration, and cluster values are validated against strict patterns before being passed as CLI arguments
- Binary path validation — Binary paths from
which/whereare verified to be absolute paths - Bounded buffers — Line buffers are capped at 1 MiB to prevent memory exhaustion from runaway subprocess output
- No credential exposure — Dev Tunnels uses the system keychain for authentication; no secrets are passed via CLI args or environment variables
| Command | Description |
|---|---|
/devtunnel |
Show current tunnel status |
/devtunnel auth |
Check devtunnel CLI authentication |
openacp plugin uninstall @hahnfeld/devtunnel-provider --purgeThe --purge flag removes all saved settings. After uninstalling, you may also want to:
devtunnel delete-all— Remove any persistent tunnelsdevtunnel user logout— Clear credentialsbrew uninstall --cask devtunnel— Remove the CLI (macOS)
# Install dependencies
pnpm install
# Build
pnpm build
# Watch mode
pnpm dev
# Run tests
pnpm testsrc/
├── index.ts # Plugin entry point & public exports
├── plugin.ts # Plugin factory (install wizard, configure, setup/teardown)
├── provider.ts # DevTunnelProvider (subprocess management, URL change detection)
├── types.ts # DevTunnelConfig interface
└── __tests__/
├── provider.test.ts # Provider unit tests (URL parsing, change detection, security)
└── plugin.test.ts # Plugin shape conformance tests
devtunnelCLI — Microsoft Dev Tunnels command-line tool@openacp/plugin-sdk— OpenACP plugin interface
MIT