# Understanding the gstack Security Model for Localhost-Only HTTP Servers

> Discover the gstack security model for localhost-only HTTP servers. Learn about its four layers, including loopback binding and token validation, to prevent DNS rebinding attacks.

- Repository: [Garry Tan/gstack](https://github.com/garrytan/gstack)
- Tags: deep-dive
- Published: 2026-05-15

---

**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`](https://github.com/garrytan/gstack/blob/main/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:

```typescript
// 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`](https://github.com/garrytan/gstack/blob/main/browse/src/server.ts), this logic verifies the request source before including sensitive data in the response:

```typescript
// 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`](https://github.com/garrytan/gstack/blob/main/browse/src/server.ts) immediately rejects unauthorized requests:

```typescript
// 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`](https://github.com/garrytan/gstack/blob/main/browse/src/url-validation.ts) (around line 889) explicitly permits `localhost` and loopback IPs while rejecting non-loopback addresses and dangerous schemes:

```typescript
// 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`](https://github.com/garrytan/gstack/blob/main/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`](https://github.com/garrytan/gstack/blob/main/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`](https://github.com/garrytan/gstack/blob/main/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`](https://github.com/garrytan/gstack/blob/main/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`](https://github.com/garrytan/gstack/blob/main/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.