The Local Agent (Personal Mode)
How the bundled Rust sidecar runs SSH/Telnet/SFTP, the vault, AI, and SNMP locally over HTTPS with a token, stores everything in SQLite, and steps aside in Enterprise mode.
Overview
NetStacks Terminal is built with Tauri and ships as a two-process application. The window you see is a Tauri shell rendering a React frontend; the actual work — opening SSH/Telnet/SFTP sessions, encrypting your vault, talking to AI providers, polling SNMP — happens in a separate Rust sidecar called the Local Agent (the netstacks-agent binary).
In Personal Mode (the default, free, open-source configuration) the Local Agent runs on your own machine as a standalone backend. The frontend talks to it over HTTPS on 127.0.0.1 with a bearer token. Nothing leaves your computer except the connections you initiate to your own devices and any AI provider you explicitly configure.
In Enterprise Mode, the exact same Terminal shell talks to a customer-hosted Controller instead, and the Local Agent steps aside entirely. This page explains the Local Agent in depth and how the handoff to Enterprise Mode works.
The Rust backend is referred to as the Local Agent in Personal Mode. It is the same binary the app bundles as a Tauri sidecar (declared as an externalBin in the app config) — you never download or run it yourself; the Terminal spawns it on launch and shuts it down on exit.
What the Local Agent Does
The Local Agent is a full Axum HTTP/WebSocket server. Everything the Terminal can do in Personal Mode is implemented behind its API. The major responsibilities:
- SSH / Telnet / SFTP
- Opens and multiplexes interactive sessions, runs an SSH exec pool, manages jump hosts and tunnels, and serves a full SFTP file browser. Host keys are pinned on first use (TOFU); jump-host hops use
StrictHostKeyChecking=accept-new. - The credential vault
- Holds your SSH passwords and keys, API tokens, and SNMP communities, encrypted at rest with AES-256-GCM using a key derived from your master password via Argon2id. The vault crate (
netstacks-credential-vault) is open source and shared with the Controller. - AI assistant
- Proxies chat and command suggestions to whichever provider you configure — Anthropic (Claude), OpenAI, Ollama, OpenRouter, LiteLLM, or a custom OpenAI- or Gemini-compatible endpoint. A sanitizer layer scrubs credentials and secrets from prompts before they ever reach an external model.
- SNMP & discovery
- Runs SNMP polling, neighbor discovery, and topology building locally, and integrates with NetBox, LibreNMS, and Netdisco.
- Workspace services
- Hosts the code editor's language servers (LSP), Git integration, snippets, custom commands, scripts, and session recording.
All of this lives in a single binary. There is no external database server, message broker, or daemon to install — the Local Agent embeds SQLite and starts itself.
HTTPS + Token on Localhost
In sidecar (Personal) mode the Local Agent binds to the loopback interface only — 127.0.0.1 — and never to a routable address. The port is ephemeral by default (the OS assigns a free port), so there is no fixed, predictable port for another process to target. The agent prints its chosen port on stdout for the Tauri shell to read:
NETSTACKS_PORT=54213
NETSTACKS_TLS_CERT=LS0tLS1CRUdJTiBDRVJU...
NETSTACKS_AUTH_TOKEN=9f3c... # only printed when not supplied by the parentTwo independent protections guard that loopback listener: a TLS certificate (so the channel is encrypted even on localhost) and a bearer token (so only the Terminal that spawned the agent can call it).
The Self-Signed TLS Certificate
The Local Agent always serves over HTTPS, even on 127.0.0.1. On first launch it generates a self-signed certificate for localhost and 127.0.0.1 (CN NetStacks Local Agent), valid for ten years, and saves it alongside its config:
localhost.crt— the certificate (PEM)localhost.key— the private key (PEM, written with0600permissions on Unix)
These live in the app-config directory (see SQLite Storage for the exact paths). The cert is reused across launches and auto-regenerates when it nears expiry, so you never have to manage it. On Windows the installer can pre-create it via netstacks-agent --init-tls so the OS trust prompt appears during install rather than on first run.
Loopback traffic does not leave the machine, but encrypting it keeps the WebSocket and API streams — which carry session keystrokes and decrypted credentials in flight — off the local socket in plaintext, and lets the same code path serve remote workspace agents without a second transport.
The Per-Launch Auth Token
TLS proves who you are talking to; the auth token proves who is allowed to talk. Every request to the agent's API (except the health probe) must carry a 256-bit token as an HTTP Authorization: Bearer header. The Tauri shell generates this token, passes it to the agent on spawn via the NETSTACKS_AUTH_TOKEN environment variable, and includes it on every call.
Key properties of the token:
- 256-bit, random, per launch. A fresh token is minted for each agent process; it is not persisted to disk.
- Stripped from the environment. Immediately after reading it, the agent calls
remove_varonNETSTACKS_AUTH_TOKENso child processes (scripts, PTYs) cannot inherit it. - Constant-time comparison. The middleware compares the presented token against the expected one in constant time, so timing cannot leak it.
- Not logged. Request-tracing is pinned so URIs (including any WebSocket token query parameter) are not written on every connection.
The only unauthenticated route is /api/health (an exact-match exemption), which simply returns ok. It exists so the app — and the single-instance eviction logic — can confirm an agent is alive without holding the token. Every other endpoint returns 401 unauthorized without a valid bearer token.
# Health is the only open endpoint:
curl -sk https://127.0.0.1:54213/api/health
# -> ok
# Everything else requires the bearer token:
curl -sk https://127.0.0.1:54213/api/sessions
# -> {"error":"unauthorized"}
curl -sk https://127.0.0.1:54213/api/sessions \
-H "Authorization: Bearer $NETSTACKS_AUTH_TOKEN"
# -> [ ...your sessions... ]These examples illustrate the security model. In day-to-day use the Terminal frontend is the only client, and it holds the token in memory. There is no documented way to recover the token from outside the app — that is the point.
SQLite Storage & the Vault
All persistent state — sessions, profiles, folders, history, topologies, and the encrypted vault entries — lives in a single embedded SQLite database, netstacks.db. The agent creates and migrates it automatically at startup; there is no external database to run.
Database location
| Platform | Path |
|---|---|
| macOS | ~/Library/Application Support/netstacks/netstacks.db |
| Linux | ~/.local/share/netstacks/netstacks.db |
| Windows | %APPDATA%\netstacks\netstacks.db |
The database runs in SQLite's default WAL journal mode, so you may also see netstacks.db-wal and netstacks.db-shm sidecar files next to it. When backing up a copy, capture all of them together — or, better, use the atomic .backup approach below, which produces a single consistent file.
App-config directory (TLS cert + controller URL)
The agent's TLS cert and app-config.json live under the application identifier com.netstacks.terminal — a different directory from the database:
| Platform | App-config directory |
|---|---|
| macOS | ~/Library/Application Support/com.netstacks.terminal/ |
| Linux | ~/.local/share/com.netstacks.terminal/ |
| Windows | %APPDATA%\com.netstacks.terminal\ |
The vault: encrypted inside the database
Vault secrets (SSH passwords and keys, API tokens, SNMP communities) are not stored in plaintext. Each is encrypted with AES-256-GCM under a key derived from your master password with Argon2id. The .db file alone leaks nothing without the master password — and conversely, if you lose the master password, the encrypted entries are unrecoverable.
There is no recovery backdoor and no phone-home reset. Argon2id is intentionally slow, and the derivation happens entirely on your machine. Back up netstacks.db, but remember: the backup is only useful if you still know the master password that unlocks it.
Use SQLite's atomic backup so you don't capture mid-transaction state. Inside the quoted .backup argument, use $HOME (or a full path) — ~ does not expand there.
sqlite3 ~/Library/Application\ Support/netstacks/netstacks.db \
".backup $HOME/netstacks.db.backup"Lifecycle: Spawn, Ports, Eviction
The Local Agent's lifecycle is managed entirely by the Terminal:
- Spawn. On launch the Tauri shell starts the bundled
netstacks-agentsidecar, generating the auth token and passing it in the environment. - Bind. The agent loads or generates its TLS cert, opens its SQLite database, then binds
127.0.0.1on an OS-assigned ephemeral port and printsNETSTACKS_PORT. - Single-instance enforcement. A lockfile (
agent.lock) records the running agent's PID and port. A new launch reads it, probes/api/healthto confirm the holder is really a NetStacks agent, and only then evicts the orphan (SIGTERM, escalating to SIGKILL). A non-NetStacks process holding the port is never killed. - Parent-death watcher. Because Tauri's exit cleanup is unreliable on crash or SIGKILL, the agent watches its parent PID and self-terminates the moment it notices the Terminal is gone. No stray backends linger.
The agent binary refuses to run a server when launched directly (e.g. by double-clicking it). --version and --help work, but otherwise it prints a notice pointing you to the desktop app and exits cleanly. It is a component of the Terminal, not a standalone CLI you operate.
Remote workspace agents (the “--remote” cousin)
The same binary can be deployed to a remote Linux host to power a remote workspace. In that mode the Terminal copies the matching agent build to ~/.netstacks/netstacks-agent over SFTP and launches it with --remote. A remote agent binds 0.0.0.0 (so the Terminal can reach it across the network), generates a TLS cert with the host's IP/hostname added as SANs, and prints its token, cert, and port back over the SSH session for the Terminal to capture. This is still standalone — it is the Local Agent running elsewhere, not the Controller.
Enterprise Mode: No Local Agent Enterprise
Personal Mode and Enterprise Mode use the same Terminal shell. The difference is where the backend lives. The application mode is 'standalone' | 'enterprise', and Enterprise Mode is detected by the presence of a non-empty controllerUrl in app-config.json:
{
"controllerUrl": "https://controller.example.net:3000"
}When that key is present and non-empty, the Local Agent detects Enterprise Mode at startup, prints a short notice, and exits immediately — it never opens a listener:
Enterprise mode detected. Agent not needed - Terminal connects directly to Controller.
Exiting...From that point the Tauri shell talks directly to the customer-hosted Controller over TLS (default port 3000). The Controller — not your laptop — does the SSH proxying, holds the shared vault, and enforces RBAC, audit logging, scheduling, and plugins. The Terminal's reported connection mode flips from Local to Controller accordingly.
| Aspect | Personal Mode (Local Agent) | Enterprise Mode (Controller) |
|---|---|---|
| Backend | Bundled netstacks-agent sidecar | Customer-hosted Controller (Docker) |
| Bind / endpoint | https://127.0.0.1:<ephemeral> | https://<controller-host>:3000 |
| Storage | Local SQLite (single user) | PostgreSQL 16 + pgvector (shared) |
| Vault | Personal, per-machine, master-password | Shared, server-side, RBAC-scoped |
| Multi-user / audit / plugins | No | Yes |
The shared vault, RBAC, audit logs, scheduling, and plugins are Controller-only and require a commercial license. See Installation for deploying the Controller. Personal Mode is free and open source, with no tier gates on local features.
To switch a Terminal back to Personal Mode, remove (or clear) controllerUrl in app-config.json and restart; the Local Agent will start normally on the next launch.
No Phone-Home
The Local Agent does not call home. There is no telemetry, no usage beacon, no license check, and no remote configuration fetch. The license-validation module was removed entirely from the agent — it no longer holds a license-server URL or contacts any NetStacks service to run.
The only outbound network traffic the Local Agent ever makes is what you direct:
- Your device connections — the SSH / Telnet / SFTP / SNMP sessions you open to your own gear.
- AI provider calls — only if you configure an AI provider, and only to the endpoint you choose (including fully local options like Ollama). Secrets are scrubbed by the sanitizer before any prompt is sent.
- Integrations you enable — NetBox, LibreNMS, Netdisco, and similar, pointed at your infrastructure.
- The auto-updater — the Tauri shell (not the agent) checks
releases.netstacks.netfor signed updates; this is a frontend feature you can ignore.
Because the agent binds loopback-only and the source is open under Apache 2.0, you can audit exactly what it does. Watch its sockets with lsof / netstat, and you will see only the connections you initiated. See Source Code & Cryptography.
Code Examples
Handy commands for inspecting or backing up the Local Agent. Replace paths per OS.
Confirm an agent is alive (loopback health probe)
# The port is ephemeral; find it from the lockfile or process list.
curl -sk https://127.0.0.1:<port>/api/health
# -> okSee which port and PID the agent holds
# macOS / Linux: the lockfile stores "PID:PORT"
cat ~/.local/share/com.netstacks.terminal/agent.lock # Linux
cat "$HOME/Library/Application Support/com.netstacks.terminal/agent.lock" # macOSBack up the database (app stopped)
# Linux
cp ~/.local/share/netstacks/netstacks.db ~/netstacks.db.backupBack up the database (app running, atomic)
sqlite3 ~/.local/share/netstacks/netstacks.db \
".backup $HOME/netstacks.db.backup"Switch a Terminal to Enterprise Mode
{
"controllerUrl": "https://controller.example.net:3000"
}Switch back to Personal Mode
# Remove the controllerUrl (or set it empty), then restart NetStacks.
{
"controllerUrl": ""
}Q&A
- Is the Local Agent a separate program I have to install?
- No. It is bundled inside NetStacks Terminal as a Tauri sidecar and spawned automatically on launch. You never download, install, or start it yourself, and launching the binary directly just prints a notice and exits.
- What port does the Local Agent listen on?
- An OS-assigned ephemeral port on
127.0.0.1only — there is no fixed port. The agent printsNETSTACKS_PORTon startup and records it inagent.lock; the Terminal reads it from there. - Why is it HTTPS on localhost, and how do I trust the cert?
- The agent always serves TLS, using a self-signed cert for
localhost/127.0.0.1stored aslocalhost.crt/localhost.keyin thecom.netstacks.terminaldirectory. The Terminal trusts it via its fingerprint; you don't manage it manually. It auto-renews before expiry. - How is the API protected from other apps on my machine?
- Every endpoint except
/api/healthrequires a 256-bit bearer token that is generated per launch, passed to the agent in its environment (then stripped so children can't inherit it), compared in constant time, and kept out of logs. Without it, requests get401 unauthorized. - Where are my credentials stored, and are they encrypted?
- In the SQLite database (
netstacks.db), encrypted with AES-256-GCM under an Argon2id-derived key from your master password. The database file alone exposes no secrets without that password. - What happens to the Local Agent in Enterprise Mode?
- It exits at startup without opening a listener. The Terminal connects directly to the Controller (default port 3000), which provides multi-user SSH, the shared vault, RBAC, audit, scheduling, and plugins. Enterprise Mode is triggered by a non-empty
controllerUrlinapp-config.json. - Does the Local Agent phone home or check a license?
- No. The license-validation code was removed; the agent holds no license-server URL and sends no telemetry. The only outbound traffic is the device connections, AI calls, and integrations you configure. The auto-updater is a frontend feature, separate from the agent.
- Can the Local Agent run on a remote host?
- Yes — the same binary can be deployed to a remote Linux host with
--remoteto power a remote workspace. It binds0.0.0.0, adds the host to its TLS SANs, and reports its token/cert/port back over SSH. This is still the standalone agent, not the Controller.
Related
- Introduction — how the Terminal, Local Agent, and Controller fit together
- Installation — install the Terminal and deploy the Controller with Docker Compose
- Source Code & Cryptography — the open-source agent and the audited vault crate
- Credential Vault — master password, vault status, and unlocking
- LLM Configuration — choosing an AI provider and the secret-scrubbing sanitizer
- Connecting to Devices — SSH, Telnet, SFTP, host-key TOFU, and jump hosts