Designing a Secure BrowserStack Local Tunnel for Internal Service Testing
An architecture note that outlines the requirements, minimal design, trust boundaries, operational checks, failure modes, and design‑change triggers for using BrowserStack Local to test localhost or internal services.
26 Jul 2025, 11:32 UTC

Requirements
Functional requirements
The goal is to enable automated tests running in BrowserStack cloud browsers to reach a web service that is only accessible from the developer’s machine or a private CI agent (e.g., a local dev server on localhost:3000). The tunnel must:
- Forward traffic only for explicitly listed host/port pairs.
- Work with HTTP, HTTPS, and WebSocket protocols.
- Be startable from a command line, npm script, or language binding so it can be inserted into CI pipelines.
- Provide observable status (connected/disconnected, request counts) in the BrowserStack dashboard.
Non‑functional requirements
- Security: Traffic must be encrypted in transit; the tunnel must not expose internal services beyond the whitelist.
- Reliability: Automatic reconnection on transient network blips; test failures should be immediate when the tunnel drops so frameworks can retry or fail fast.
- Operational simplicity: Minimal dependencies, clear logging, and ability to rotate the BrowserStack access key without redeploying the tunnel binary.
Smallest suitable design
The design that satisfies the above with the fewest moving parts is a single outbound TLS tunnel process (the BrowserStack Local binary) that:
- Authenticates to BrowserStack using the user’s access key.
- Negotiates a TLS session to BrowserStack’s tunnel endpoint (
bs-local.com:443). - Maintains a bidirectional stream that forwards only the TCP connections matching the
--hostarguments supplied at startup (e.g.,localhost,3000). - Streams connection‑status, request‑count, and error logs to the BrowserStack dashboard via the same TLS channel.
- Exposes no inbound listening ports; all traffic flows outbound from the host where the binary runs.
Because the tunnel is unidirectional outbound, the trust boundary is simple: the process trusts BrowserStack to terminate the TLS connection correctly and to enforce the whitelist; the internal service trusts only the local machine (loopback) and does not need to authenticate the tunnel.
Trust and data boundaries
Boundary 1 – User machine ↔ BrowserStack
This is the only network crossing point. The tunnel initiates an outbound HTTPS connection to BrowserStack, authenticates with the access key, and then multiplexes test traffic over that channel. No inbound connections are accepted from BrowserStack, so the user machine does not need to expose any ports to the internet.
Boundary 2 – Tunnel ↔ Internal service
Inside the user machine, the tunnel forwards traffic to the loopback address or a specific host/port pair defined at start‑up. The internal service sees connections originating from 127.0.0.1 (or the specified host) and therefore applies its usual localhost security policies (e.g., same‑origin, CORS). No additional authentication is added by the tunnel.
Data flow
Test steps in the remote browser issue an HTTP request to http://localhost:3000/path. The BrowserStack cloud sends that request over the WebDriver/WebSocket channel to the tunnel, which decodes it, opens a TCP socket to 127.0.0.1:3000, forwards the request, and returns the response. All payloads travel inside the initial TLS tunnel, so they are encrypted end‑to‑end between the user machine and BrowserStack.
Operational checks
To confirm the tunnel is correctly configured and operating:
- Start the tunnel with a valid access key and a narrow whitelist. Example (run on the CI agent or developer workstation):
Required permission: ability to execute the binary and open outbound TCP port 443 to./BrowserStackLocal --key $BROWSERSTACK_ACCESS_KEY \ --local-identifier ci-build-$(date +%s) \ --host localhost,3000bs-local.com. No root privileges are needed. - Verify startup: The console should emit a line similar to "Connected. Local identifier: ci-build-...". The BrowserStack dashboard, under Automate → Local Testing, lists the tunnel as “Connected” with the same identifier.
- Run a test: Execute a Selenium/WebDriver test that targets the BrowserStack remote URL (
hub-cloud.browserstack.com/wd/hub) and attempts to fetchhttp://localhost:3000/health. The test should receive a 200 OK from the local service. - Observe logs: In the dashboard, the tunnel’s request count increments with each HTTP/WS request. Errors (e.g., connection refused) appear instantly, allowing the test framework to treat them as failures.
- Health‑check script (optional): A simple cron or pipeline step can query the tunnel’s status via the BrowserStack REST API (
GET /automate/builds//local_tunnels) and alert if the tunnel is not “Connected” for more than a configurable threshold (e.g., 30 seconds).
Failure modes
- Network outage: If the outbound connection to BrowserStack drops, the tunnel attempts exponential‑backoff reconnection. While disconnected, any test that relies on localhost fails immediately with a network error; the test framework should treat this as a failure or retry according to its retry policy.
- Authentication failure: An invalid or revoked access key causes the tunnel to exit with an error message like "Access key invalid". No forwarding occurs, and all tests fail. Rotating the key requires updating the secret in the CI store and restarting the tunnel.
- Over‑permissive whitelist: Specifying
0.0.0.0/0or a broad range (e.g.,localhost,0-65535) could allow the tunnel to forward traffic to unintended internal services, exposing them to the public internet via BrowserStack. The mitigation is to always enumerate exact host/port pairs and to review the whitelist in code review. - Process crash: If the binary crashes (e.g., due to an OS signal), the tunnel disappears and tests fail. Using a process supervisor (systemd, supervisord, or a CI step that restarts on non‑zero exit) mitigates this.
- Proxy/firewall block: Corporate outbound proxies that block
bs-local.com:443prevent tunnel establishment. The remedy is to add an outbound allow‑list rule or to configure the tunnel to use an HTTP CONNECT proxy via the--proxy-hostand--proxy-portflags (if the corporate proxy supports CONNECT to port 443).
Conditions that would change the design
The minimal outbound‑tunnel design remains appropriate as long as the following hold:
- Only outbound connectivity from the test host to BrowserStack is required.
- The set of services to be tested is static enough to be enumerated as host/port pairs at tunnel start‑up.
- Protocols needed are limited to HTTP, HTTPS, and WebSocket (the tunnel’s current support).
- No inbound traffic from BrowserStack to the internal service is needed (e.g., for webhooks or reverse‑AJAX).
If any of these conditions change, the design must be revisited:
- Need for inbound connections (e.g., testing a webhook endpoint that BrowserStack must call). This would require a different approach, such as exposing a public ngrok‑style tunnel or deploying a reverse‑proxy that terminates TLS inbound from BrowserStack.
- Dynamic service discovery where the host/port set cannot be known up front. One could run a side‑car service that periodically updates the tunnel’s whitelist via the BrowserStack Local API, but this adds complexity and introduces a window where the whitelist may be stale.
- Protocol beyond HTTP/S/WebSocket (e.g., raw TCP, FTP, or HTTP/2 over cleartext). The current Local binary only multiplexes HTTP‑based traffic; for other protocols a generic TCP tunnel (e.g., ssh -R) or a VPN would be necessary.
- Strict corporate outbound proxy that only allows CONNECT to a whitelist of domains. If
bs-local.comcannot be reached, the tunnel cannot be established; the design would need to shift to a VPN‑based solution or a dedicated gateway that is allowed by the proxy. - Regulatory requirement to terminate TLS inside the corporate network. If end‑to‑end encryption to BrowserStack is disallowed, one would need to deploy a local TLS termination proxy that re‑encrypts traffic to BrowserStack, adding another trust boundary.
Practical verification steps
After deploying the tunnel in a pipeline, you can confirm correct operation without relying on vendor claims:
- Check the tunnel’s stdout for the "Connected" line and note the local identifier.
- In the BrowserStack dashboard, verify that the tunnel appears under the expected build/session with status “Connected”.
- Run a simple curl from the test container:
curl -v http://localhost:3000/health(this will go through the tunnel if the test is configured to use the BrowserStack remote endpoint). A 200 response confirms forwarding. - Stop the tunnel (
Ctrl+Cor kill the process) and re‑run the same curl; you should see a connection refusal or timeout, proving that forwarding ceases when the tunnel is down. - Optionally, run
tcpdump -i any port 443on the host to see only outbound TLS packets tobs-local.comand no inbound traffic on the forwarded ports.
These steps provide observable evidence that the tunnel is active, that traffic is correctly forwarded, and that it stops when the process ends—fulfilling the operational checks defined in the architecture.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.