NetStacksNetStacks

Security & Architecture

Last updated: April 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 security@netstacks.net and we'll get you a real answer.

Architecture at a glance

The Terminal app you download is a single desktop binary. It ships as two pieces in one process:

  • 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 every outbound connection: SSH, SFTP, Telnet, SNMP, AI provider calls, license validation, integrations. The UI talks to the agent over a loopback HTTP/WebSocket socket bound to 127.0.0.1, 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 doesn't phone home, doesn't expose itself to other machines, and only initiates the outbound connections listed below.

What leaves your machine

In a fresh install with no AI provider, no license activation, and no third-party integrations configured, the agent makes zero outbound network connections. Every 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).

DestinationTriggered byWhat is sent
api.anthropic.comYou configure Claude as your AI provider and use the assistantYour prompt, recent terminal output, device metadata, sanitized command results, your API key
api.openai.comYou configure OpenAI as your AI provider and use the assistantSame as above
openrouter.aiYou configure OpenRouter as your AI providerSame as above
Custom LLM endpoint (Ollama, LiteLLM, your own)You configure itSame as above, sent to the URL you provided
www.netstacks.netYou activate or refresh a paid licenseYour license key, email, and a machine fingerprint (see below)
releases.netstacks.netThe app checks for an update on launch (can be disabled)Current version and platform, no account data
api.macvendors.comYou perform a MAC OUI lookupThe MAC address you're looking up
Your NetBox / LibreNMS / NetdiscoYou configure the integrationYour API token, queries you initiate
Your SMTP serverYou enable email notificationsEmail content and SMTP credentials

The Personal (free) tier never contacts our license server, because there is no license to validate.

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/com.netstacks.terminal/
  • Linux: ~/.local/share/netstacks/
  • Windows: %APPDATA%\NetStacks\

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 per-vault random salt. The master password is never written to disk.
  • 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.
  • Master-password verification uses a constant-time comparison so failed unlocks do not leak timing information. Failed attempts are rate-limited with exponential backoff. Master passwords must be at least 12 characters at setup time.
  • The SQLite file (and its WAL/SHM siblings) is created with mode 0600 and its parent directory with mode 0700, regardless of your umask.
  • Database queries are exclusively parameterized — there is no string-concatenated SQL anywhere in the agent.
  • 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 PBKDF2 actually buys you something — a passphrase, not a short password.
  • 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. The supported algorithm set is the modern set: Ed25519 and ECDSA host keys, Curve25519 / ECDH key exchange, ChaCha20-Poly1305 and AES-GCM ciphers, SHA-256/SHA-512 HMACs. RFC 8332 (rsa-sha2-256/512) is supported for devices that still need RSA host keys.

Host key verification

First connection to a device follows trust-on-first-use (TOFU): the host key fingerprint is shown to you and pinned in your ~/.ssh/known_hosts file (the standard OpenSSH format — the same file your ssh client uses). On subsequent connections, a key mismatch produces an error and the connection is rejected. This is identical behavior to standard ssh.

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 on the same SSH transport with the same host-key verification.

What the AI assistant can and cannot do

The AI assistant is not autonomous infrastructure. It is a chat surface backed by your chosen LLM provider, with a small, fixed set of tools the model can call. Here is the complete list.

What it can do

  • Query your local device inventory (names, IPs, vendor, role, site) — read-only, no remote calls.
  • Run read-only commands over an existing SSH session you opened — restricted to show, display, get, list, ping, traceroute, trace and similar. Anything starting with configure, commit, write, delete, clear, no, reload, rollback, set, edit is blocked at the tool layer.
  • Help you build and execute Methods of Procedure (MOPs) — these are still subject to the same read-only filter for AI invocations; configuration changes go through the explicit MOP execution flow that you trigger.
  • Send email through your configured SMTP, if you've set one up.

What it cannot do

  • It cannot read your vault or extract credentials.
  • It cannot run arbitrary shell commands on your machine.
  • It cannot push configuration to a device — write commands are blocked at the tool layer, not just discouraged in a prompt.
  • It cannot dial network endpoints other than the LLM provider you configured.

What requires your explicit approval

When the assistant wants to invoke a tool that has any side-effect outside reading your inventory or running a read-only command, the request pauses for you to approve or reject. This applies to writing or editing files, sending email through your configured SMTP, and any tool surfaced from a connected MCP server. Approvals time out after ten minutes; a rejected call returns “user rejected” to the model rather than executing.

What gets sent to your LLM provider

When you talk to the assistant, your prompt, the recent conversation, the device metadata that's relevant to your question, and the output of any read-only 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 (we're actively expanding patterns for newer Cisco IOS forms such as algorithm-type scrypt and secret sha512). 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. 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: Builds are signed with our Authenticode certificate. We're in the process of layering the same minisign update-bundle verification used on macOS on top of Authenticode signing for Windows.
  • Linux: AppImage and deb/rpm artifacts are signed with the same minisign key as macOS update bundles.
  • Update manifests and binaries are served over HTTPS from releases.netstacks.net.

License and machine fingerprint

Paid tiers are tied to a license key and a machine fingerprint derived from CPU, disk serial, and OS install identifiers. Before transmission the fingerprint is hashed (SHA-256) — the license server only ever sees the digest, not the underlying values.

License entitlements come back as a JWT signed with a 2048-bit RSA key. The corresponding public key is pinned inside the app, so the agent verifies the signature locally on every check rather than trusting whatever the server returns. A tampered or replayed license fails verification client-side.

The Personal (free) tier has no license activation and sends no fingerprint anywhere.

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.

  • Tauri webview hardening. Tightening the Content-Security-Policy to remove unsafe-inline/ unsafe-eval, dropping the global Tauri object, and scoping shell capabilities to a small allowlist rather than the broad default scope.
  • Loopback API hardening. Replacing the permissive CORS configuration with a strict origin allowlist (Tauri origin only), moving the WebSocket auth token from the URL query string to a header, and adding an Origin check on the WebSocket upgrade — together closing the cross-site-WebSocket-hijacking class of attack.
  • Vault auto-lock on inactivity. The vault already zeroizes its key on explicit lock and on process exit; an inactivity-based auto-lock timer is the next step.
  • Host-key handling on proxied paths. Direct SSH connections enforce TOFU pinning against ~/.ssh/known_hosts; we're aligning jump-host and other proxied-SSH paths with the same flow rather than delegating to OpenSSH defaults.
  • 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.
  • Session audit log. Optional encrypted session recording is on the roadmap so teams that need a forensic trail of who accessed which device, when, and what they ran have one.
  • AI assistant — disclosure and dry-run. Mutating tool calls (write, email, MCP) already require explicit per-call approval. On top of that we're 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 newer Cisco IOS forms (algorithm-type scrypt, secret sha512) and other vendor-specific formats, plus expanding our test corpus with real running-configs.
  • Update bundle signing parity. macOS and Linux update bundles are minisign-signed and verified inside the app; the same minisign verification is rolling out for the Windows update channel on top of Authenticode signing.
  • Outbound TLS pinning. Defense-in-depth on the license-server connection — pinning the server's public key so license validation 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 mitmproxy or a similar tool on your machine, configure NetStacks to use it, and use the app for an hour. The hostname list above is exhaustive.
  • Local bind: lsof -iTCP -sTCP:LISTEN -P | grep -i netstacks (macOS / Linux) shows the agent bound to 127.0.0.1 only.
  • Host keys: connect to a device, then check ~/.ssh/known_hosts — the entry NetStacks writes is in standard OpenSSH format and interchangeable with your regular ssh client's entries.
  • 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 security@netstacks.net 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.