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.tsensures the server accepts connections only from127.0.0.1or::1, making remote access impossible at the network level. - Token exposure control at the
/healthendpoint (lines 1430–1445) returns the bootstrap token only to localhost or trusted Chrome extension origins. - Endpoint authentication via
validateAuthguards privileged routes like/pty-sessionand/connect, requiring a valid root token in theAuthorizationheader. - URL validation in
browse/src/url-validation.tsblocks 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →