Security & Architecture
Last updated: October 2026
Why this page exists
NetStacks is a young product, and we're asking network engineers to point it at production gear. That deserves a straight answer about what the app does, what it doesn't do, what data leaves your machine, and the limitations we're still working on. This page is that answer. Where we're still improving something, we say so by name rather than dressing it up.
If you're evaluating NetStacks against a security policy and need something this page doesn't cover, email [email protected] and we'll get you a real answer.
Architecture at a glance
The Terminal app you download installs as two processes that run side by side on your machine:
- UI shell — a Tauri-packaged webview rendering the React frontend. It draws windows; it does not touch the network or your devices directly.
- Local agent — a Rust sidecar that owns the outbound connections: SSH, SFTP, Telnet, SNMP, AI provider calls and integrations. The UI talks to the agent over HTTPS and WebSocket on
127.0.0.1, on a random port, authenticated with a 256-bit token generated fresh on every launch.
There is no NetStacks-hosted backend that your agent depends on for normal operation. The optional Controller is a separate, self-hostable service — if you don't deploy one, the Terminal works fully standalone, and your credentials and device data live only on your machine.
Trust boundary. Everything inside the agent process is in one trust domain — we don't claim the UI can't influence the agent. We claim the agent collects no telemetry, doesn't expose itself to other machines, and only initiates the outbound connections listed below. The only automatic contact with NetStacks is the update check — never usage data or analytics.
What leaves your machine
In a fresh install with no AI provider and no third-party integrations configured, the app makes only two kinds of outbound connection on its own: the update check and a download of the public MAC-vendor (OUI) list. Every other destination below is gated behind a feature you explicitly configure or an action you explicitly take. You can verify with any local network capture tool (e.g. mitmproxy, Little Snitch, or tcpdump).
| Destination | Triggered by | What is sent |
|---|---|---|
api.anthropic.com | You configure Claude as your AI provider and use the assistant | Your prompt, recent terminal output, device metadata, sanitized command results, your API key |
api.openai.com | You configure OpenAI as your AI provider and use the assistant | Same as above |
openrouter.ai | You configure OpenRouter as your AI provider | Same as above |
| Custom LLM endpoint (Ollama, LiteLLM, your own) | You configure it | Same as above, sent to the URL you provided |
releases.netstacks.net | The app checks for an update a few seconds after launch | Current version and platform, no account data |
standards-oui.ieee.org | Automatically on first start, then every 30 days | Nothing — it downloads the public MAC vendor list |
api.macvendors.com | A MAC lookup the local vendor list can't answer | The MAC address you're looking up |
files.pythonhosted.org / PyPI | You install the Python language server, or run a script that declares dependencies | Package download requests only |
| Your Git hosting (GitHub, GitLab, Gitea, Bitbucket) | You add a Git account or push/pull a workspace | Your access token and the repository data you push or pull |
| Your NetBox / LibreNMS / NetStacks-Crawler | You configure the integration | Your API token, queries you initiate |
| Your SMTP server | A background AI agent sends an email you approved | Email content and SMTP credentials |
No telemetry — ever. The desktop app has no account and no license check. The only thing it contacts at NetStacks automatically is the update check, which fetches a small manifest for your platform and version from releases.netstacks.net. NetStacks does not collect, derive, or transmit usage data, feature analytics, command history, device inventory, or any other telemetry, and there is no mechanism in the product to do so. This is a permanent design commitment, not a current default.
Local data and the credential vault
Device credentials, API keys, integration tokens, and SSH key passphrases live in a local SQLite database in your platform's standard application-data directory:
- macOS:
~/Library/Application Support/netstacks/netstacks.db - Linux:
~/.local/share/netstacks/netstacks.db - Windows:
%LOCALAPPDATA%\netstacks\netstacks.db
How the vault works
- Each secret is individually encrypted with AES-256-GCM (authenticated encryption) using a fresh, random 96-bit nonce per record.
- The encryption key is derived from your master password using Argon2id (memory-hard KDF) with a random salt. The master password is never written to disk (on macOS, if you turn on Touch ID unlock, it is kept in your login keychain).
- The derived key lives in a memory region that is explicitly zeroized when the vault is locked or the holding object is dropped — not just left to the allocator.
- Failed unlocks are rate-limited: after five wrong attempts, unlocking pauses for 60 seconds. Master passwords must be at least 12 characters at setup time.
- On macOS and Linux the SQLite file is created with mode
0600and its parent directory with mode0700, regardless of your umask. - Database queries that carry your data are parameterized.
- When the vault is locked, the derived key is zeroized; the database file on disk remains encrypted at the record level regardless of whether the vault is unlocked.
What we recommend you do
- Run NetStacks on a system with full-disk encryption enabled (FileVault, BitLocker, LUKS). The vault protects against file-copy theft; full-disk encryption protects against device theft generally.
- Pick a master password long enough that Argon2id actually buys you something — a passphrase, not a short password. There is no password recovery: a forgotten master password means resetting the vault.
- If you store the vault on a folder that auto-syncs to a cloud service (iCloud Drive, OneDrive, Dropbox), be aware that you're trusting that service to hold the encrypted blob. We do not place the vault in those folders by default.
SSH, Telnet, and SFTP
SSH is implemented on russh, a maintained Rust SSH library. Modern algorithms are preferred: Ed25519 and ECDSA host keys, Curve25519 / ECDH key exchange, ChaCha20-Poly1305 and AES-GCM ciphers. RFC 8332 (rsa-sha2-256/512) is supported for devices that still need RSA keys, and older algorithms remain available for legacy gear.
Host key verification
First connection to a device follows trust-on-first-use (TOFU): the SHA-256 host key fingerprint is shown to you, and once you trust it the key is pinned in your ~/.ssh/known_hosts file. If a device later presents a different key, the connection stops with a “host key has changed” warning and only continues if you explicitly trust the new key. Pinned keys can be reviewed and revoked under Settings → Trusted Hosts.
Telnet
Telnet is supported because real networks still have devices that only speak Telnet, and we're not going to pretend otherwise. Telnet is unencrypted: passwords cross the wire in plaintext, and anyone in the network path can read them. If your policy forbids Telnet, don't enable it.
SFTP
SFTP runs over SSH, using the same credential profiles as your terminal sessions.
What the AI assistant can and cannot do
The AI assistant is not autonomous infrastructure. It is a chat panel backed by your chosen LLM provider, with a set of tools the model can call. Every tool can be switched off per mode under Settings → AI → AI Tools.
What it can do
- Read your session list and the recent output of your terminal tabs.
- Run commands on devices — typed into an open terminal tab where you can watch them, or over a background SSH connection using the session's saved credentials. In the desktop app every AI device command passes a read-only filter in the local agent: only commands starting with
show,display,get,list,ping,tracerouteortraceare allowed, and configuration, write, reload, erase and shell-escape commands are refused. - Read and save documents, call the API Resources you have set up, annotate topologies and draft MOPs.
- In the Auto Pilot mode, run local shell commands, subject to a built-in deny list (
rm,sudo,kill,shutdownand more) that you can extend. - Call tools from MCP servers you connect yourself; each discovered tool starts disabled until you enable it.
Configuration changes
The assistant can only send configuration commands to a device after you turn on AI Configuration Mode under Settings → AI. In the desktop app that requires your master password and switches itself off after five minutes. On a Controller, the administrator decides whether it is available at all.
What it cannot do
- It cannot read your vault or extract stored credentials.
- It cannot push configuration to a device while AI Configuration Mode is off — write commands are refused by the agent, not just discouraged in a prompt.
What requires your explicit approval
In the Ask permission mode, terminal commands, background SSH commands, local shell commands, console commands and file writes pause for you to approve or reject before they run; a rejected call returns “user rejected” to the model rather than executing. Background agent tasks always ask before writing files, sending email or calling an MCP tool, and a request that is not answered within ten minutes is rejected.
What gets sent to your LLM provider
When you talk to the assistant, your prompt, the recent conversation, recent terminal output, the device metadata that's relevant to your question, and the output of any commands the model chose to run all flow to the provider you configured. We do not proxy this through NetStacks servers — it goes from your machine to the provider over TLS.
The sanitizer is applied to both your messages and to the output of every tool call before either reaches the conversation history sent to the provider. It redacts known credential patterns: Cisco enable secret, type-7 passwords, TACACS / RADIUS keys, SNMP communities, SSH private-key blocks, AWS access keys, Palo Alto XML credentials, and generic password = … forms. It is best-effort and pattern-based — it cannot guarantee that every secret in every vendor's non-standard or recently introduced output format is caught. You can add your own patterns and test them under Settings → AI. If you're running show running-config on a device whose configuration you consider extremely sensitive, treat the AI assistant the way you'd treat any third-party tool that sees that output.
Device names and IPs are not redacted by default — the assistant is more useful when it knows what device it's looking at. You can switch on redaction of IPv4 and IPv6 addresses, MAC addresses, hostnames and usernames, and if your environment treats topology itself as sensitive, use a self-hosted LLM (Ollama, your own LiteLLM) instead of a SaaS provider.
Application integrity
- macOS: Builds are signed with our Apple Developer ID and notarized by Apple. Update bundles are additionally signed with a minisign key, and the public key is pinned inside the application; the in-app updater refuses any bundle that doesn't verify against it.
- Windows: The installer is Authenticode-signed. Update bundles carry the same minisign signature, verified by the in-app updater against the pinned public key.
- Linux: The AppImage update bundle carries the same minisign signature, verified by the in-app updater.
- Update manifests and binaries are served over HTTPS from
releases.netstacks.net.
Licensing
The desktop app has no license key, no activation and no machine fingerprint. Apart from the update check, it sends nothing to NetStacks.
Only the self-hosted NetStacks Controller is licensed. It activates its license key against www.netstacks.net, verifies the RSA-signed license token it gets back, and revalidates the license once a day. The request carries the license key or license ID — nothing about your devices, users or usage.
Known limitations and what we're working on
We'd rather you read this section than discover any of it yourself. None of these are showstoppers for evaluation — they are the items on our active hardening backlog. Items that have shipped are folded into the sections above instead of being listed here.
- Vault auto-lock on inactivity. Today the vault locks when you click Lock Vault and whenever the app restarts; an inactivity-based auto-lock timer is the next step.
- Telnet UX. Adding a prominent unencrypted-protocol warning in the connection UI and a session log entry that makes the cleartext nature of the session unmissable.
- AI assistant — disclosure and dry-run. Adding a first-use disclosure of exactly what data leaves your machine and a dry-run mode that shows you the sanitized prompt before it's sent.
- Sanitizer coverage. Extending the redaction patterns to more vendor-specific formats, plus expanding our test corpus with real running-configs.
- Outbound TLS pinning. Defense-in-depth on the app's outbound connections, such as the software update channel — pinning the server's public key so the connection cannot be intercepted by a rogue CA in the system trust store.
We'll move items off this list as they ship.
Verifying any of this yourself
- Egress: run
mitmproxyor a similar tool on your machine, configure NetStacks to use it, and use the app for an hour. Apart from your own devices and the services you configured, you should only see the destinations listed above. - Local bind:
lsof -iTCP -sTCP:LISTEN -P | grep -i netstacks(macOS / Linux) shows the agent bound to127.0.0.1only (unless you deploy a remote agent to a Linux host yourself). - Host keys: connect to a device, then check Settings → Trusted Hosts or
~/.ssh/known_hostsfor the pinned key. - Vault file: the SQLite file at the path listed above can be opened with any SQLite tool; credential rows are stored as ciphertext blobs, not plaintext.
Reporting a security issue
Please email [email protected] with anything that looks like a security problem — including issues you can't fully reproduce. We'll acknowledge within two business days, work with you on a fix, and credit you in the changelog if you'd like that. Please give us a reasonable window to ship a fix before public disclosure.