Understanding the gstack Security Model for Localhost-Only HTTP Servers

The gstack security model for localhost-only HTTP servers relies on four complementary defense layers: loopback network binding, conditional auth-token exposure, endpoint-level token validation, and strict URL validation to prevent DNS rebinding attacks.

The gstack project (garrytan/gstack) runs a lightweight HTTP daemon—referred to as the browse server—that enables local browser automation and terminal interactions. Because this service handles sensitive bootstrap tokens and privileged operations, it implements a rigorous gstack security model for localhost-only HTTP servers designed to withstand both accidental port forwarding and intentional remote attacks. The implementation spans several TypeScript files in the browse/src/ directory, each addressing a specific vector of exposure.

Network Binding to Loopback Interfaces

The first line of defense is physical network isolation. The server creates its listener using Node.js’s net.createServer() and explicitly binds to the loopback interface only.

In browse/src/server.ts, the server initialization code specifies the host as 127.0.0.1 (IPv4) or ::1 (IPv6), ensuring that no packets from external network interfaces reach the daemon:

// browse/src/server.ts – creation of the listener
const srv = net.createServer();               // uses net.createServer to avoid Bun.serve race
srv.listen({ host: '127.0.0.1', port: 0 });    // bound to loopback, any free port

This configuration guarantees that even if the host machine’s firewall is misconfigured or the port is forwarded via SSH tunnels, the service remains unreachable from remote addresses because the operating system never routes external traffic to a loopback-bound socket.

Conditional Auth-Token Exposure Control

To prevent token leakage during accidental exposure, the /health endpoint implements strict origin checking before returning the bootstrap auth token. Located at lines 1430–1445 of browse/src/server.ts, this logic verifies the request source before including sensitive data in the response:

// inside the /health handler
if (browserManager.getConnectionMode() === 'headed' ||
    req.headers.get('origin')?.startsWith('chrome-extension://')) {
  // safe – include the token
  responseBody.token = authToken;
}

If the request does not originate from localhost or a trusted chrome-extension:// origin (used in headed mode), the server omits the token from the JSON payload. This means that even if a developer accidentally exposes the port via ngrok or a reverse proxy, remote attackers cannot retrieve the credentials required to authenticate with privileged endpoints.

Endpoint-Level Authentication with validateAuth

All state-mutating routes enforce mandatory token verification through the validateAuth middleware. Privileged endpoints such as /pty-session, /connect, and /token require a valid root token presented in the Authorization header.

For example, the /pty-session handler at lines 1476–1482 of browse/src/server.ts immediately rejects unauthorized requests:

// browse/src/server.ts – /pty-session handler
if (!validateAuth(req)) {
  return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 });
}

Without the correct token, the server returns HTTP 401 or 403, ensuring that unauthenticated clients cannot spawn PTY sessions or establish WebSocket connections, even if they bypass network-level restrictions.

URL Validation to Block DNS Rebinding

The final layer protects against DNS rebinding attacks that attempt to trick the server into accessing internal cloud-metadata services. The URL validator in browse/src/url-validation.ts (around line 889) explicitly permits localhost and loopback IPs while rejecting non-loopback addresses and dangerous schemes:

// browse/src/url-validation.ts – part of validateNavigationUrl()
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
  throw new Error('Blocked: scheme ...');
}
const hostname = normalizeHostname(parsed.hostname.toLowerCase());
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') {
  // allowed – loopback hosts are explicitly accepted
  return url;
}

By allowing only localhost, 127.0.0.1, and ::1, the validator prevents malicious web pages from forcing the browser agent to navigate to cloud-metadata endpoints (such as 169.254.169.254) or external hosts that could leak information.

Summary

  • Loopback binding in browse/src/server.ts ensures the server accepts connections only from 127.0.0.1 or ::1, making remote access impossible at the network level.
  • Token exposure control at the /health endpoint (lines 1430–1445) returns the bootstrap token only to localhost or trusted Chrome extension origins.
  • Endpoint authentication via validateAuth guards privileged routes like /pty-session and /connect, requiring a valid root token in the Authorization header.
  • URL validation in browse/src/url-validation.ts blocks navigation to non-loopback hosts, mitigating DNS rebinding and cloud-metadata access attempts.

Frequently Asked Questions

How does gstack prevent the auth token from leaking through the /health endpoint?

The /health handler in browse/src/server.ts (lines 1430–1445) inspects the request’s origin and browserManager.getConnectionMode(). It includes the bootstrap auth token in the response only if the request comes from localhost or carries a chrome-extension:// origin header used in headed mode. Remote requests receive the health status without the sensitive token.

What happens if I accidentally expose the gstack server via ngrok or SSH tunneling?

While the network binding restricts the socket to loopback, accidental tunneling could expose the HTTP interface externally. However, the token-exposure control layer detects non-localhost origins and withholds the bootstrap token. Additionally, all privileged endpoints require a separate root token via validateAuth, so attackers still cannot spawn sessions or execute commands without possessing valid credentials.

Which privileged endpoints require authentication in gstack?

Routes that mutate state or create sessions—such as /pty-session, /connect, and /token—all invoke the validateAuth function to verify the Authorization header against the stored root token. These handlers return HTTP 401 or 403 if the token is missing or incorrect, as implemented throughout browse/src/server.ts (e.g., lines 1476–1482).

How does gstack protect against DNS rebinding attacks?

The URL validator in browse/src/url-validation.ts normalizes hostnames and explicitly allows only localhost, 127.0.0.1, and ::1 while rejecting cloud-metadata addresses and non-loopback IPs. This ensures that navigation commands issued by the automation layer cannot be redirected to internal cloud APIs or external malicious hosts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →