DocsOperationsSecurity model

Security model

ngstone is built for a single operator, and its threat model is scoped accordingly — it takes tunneling's inherent exposure seriously without pretending to be a general-purpose ingress platform with tenant isolation.

Assets#

What's actually at stake if something goes wrong:

  • The laptop's local app, and by extension its LAN foothold
  • The auth token
  • The wildcard domain's reputation
  • Tunneled request and response data in transit

Anonymous attacker#

An anonymous attacker who has the public URL is exactly an anonymous HTTP client of the exposed local app — nothing more. They cannot reach the inspector (it's a terminal UI attached to your own process, not a network service — there's no port to reach), any other localhost port (only the configured target is ever dialed), or the control plane (token-gated). Whatever residual risk remains here is inherent to tunneling itself: if the local app is a debug server with a remote code execution bug, the tunnel exposes exactly that. Mitigations are a fast, byte-identical 404 for unknown or offline hosts (no enumeration oracle), per-IP rate limiting, and making this risk explicit in the docs rather than implying a false sense of isolation.

Leaked token#

What a leaked token grants — and doesn't

A leaked token grants: binding any non-denylisted subdomain, receiving that traffic, and serving phishing content under your domain. It does notgrant: reading other tunnels' traffic in flight, shell access to the edge, or control over certificates/DNS. Rotation is just a config change and a restart — set a new token_sha256 and every existing session dies at its next reconnect-auth.

Header spoofing#

Inbound Forwarded/X-Forwarded-* headers are stripped and re-stamped at the edge, and X-Real-IPis stripped explicitly since it isn't covered by the standard library's automatic delete. The local app can trust these headers only because the agent-side proxy is written to preserve them on the way through — the same stdlib behavior that strips spoofed headers on the edge would silently delete the legitimate ones on the agent if it used the same code path unmodified.

The inspector's attack surface#

There's no CSRF or DNS-rebinding story to tell here, because there's no HTTP endpoint to attack: the request inspector is a terminal UI (see Using the request inspector) that renders directly to your own process's stdout. It doesn't bind a port, doesn't serve an API, and isn't reachable from a browser tab — a malicious web page has nothing to drive.

Resource exhaustion#

Every buffer in the system has a stated cap, including the yamux receive windows — easy to omit, since concurrency × window is the real ceiling and neither number alone tells the whole story. No code path buffers an entire request or response body in memory. The unauthenticated surface on :4443is separately capped (concurrent handshakes, unauthenticated sessions) so a stranger can't hoard control-plane resources for the price of a bare TCP connect.

Out of scope#

Accepted gaps for a single-user v1:

  • Native gRPC and h2c backends — the tunnel carries raw HTTP/1.1 bytes inside each stream, which structurally cannot survive gRPC's trailers and full-duplex h2 semantics
  • Volumetric DDoS beyond the VPS's own capacity
  • Compromise of the VPS itself — it terminates TLS and sees plaintext, so it's treated as trusted infrastructure
  • OAuth-grade authentication in front of tunnels