Release notes

See what changed in each release, why it matters, and whether there are install, compatibility, or security notes before you update.

All releases on GitHub

v0.3.3

Sep 23, 2026

Windows executables and the desktop installer are signed, so Windows no longer warns that the publisher is unknown. Connecting the MCP server to a client no longer means installing netscli first and writing the configuration by hand: npx netscli serve works, and each release carries a one-click bundle for clients that support them.

Added

  • Windows executables and the desktop installer are signed. Both netscli builds and the .msi now carry an Authenticode signature from a Certum certificate, timestamped so it keeps verifying after the certificate expires. Windows has been showing an unknown-publisher warning on every download since the first release, and SmartScreen treats an unsigned installer from a low-reputation domain as something to discourage rather than merely flag.
  • Two new ways to connect the MCP server, so it no longer has to be a hand-written config file. Until now the only route was installing netscli, finding where it landed, and writing the JSON yourself.

npx netscli serve now works: netscli is on npm as a small launcher plus one prebuilt binary per platform, and npm fetches only the one that matches your machine. This is the form every MCP client's documentation already uses.

There are also .mcpb bundles on each release, one per platform. A client that supports MCP bundles installs one in a single action — the binary is inside, so nothing else is needed. The install prompt carries a switch for the local-network default described below, which is the first time that choice has been visible to the person making it rather than buried in an environment variable.

Anyone who already has netscli installed should keep pointing their client at it. That copy is the version you chose, there is only one of it, and it is the only one of the three that can capture packets — libpcap and Npcap cannot be shipped inside an npm package or a bundle.

Documentation

  • The MCP page now says what the local-network default is and why. The server has refused targets outside your own networks since 0.3.1, and the only description of that lived in a source comment, so the first anyone heard of it was an error message naming a variable. It is now written down next to the reason: this is the one surface where a scan can be requested by something a model read rather than by the person at the keyboard.

v0.3.2

Sep 21, 2026

Repairs the Linux packaging. The AUR desktop package installed a file that could not start, and the AppImage aborted before opening a window on any host with a recent Mesa. A desktop window that opens blank now recovers by itself, netscli with no arguments no longer hangs where there is no terminal, and discover names devices from mDNS when reverse DNS comes back empty.

Added

  • Discover names hosts from mDNS when reverse DNS cannot. Consumer routers do not serve PTR records for their own DHCP clients, and appliances ignore LLMNR and NetBIOS, so reverse lookup returned nothing for exactly the devices someone opened the app to identify. Those devices announce their names over mDNS constantly, and netscli has shipped an mDNS browser all along as a separate operation that discover never consulted. It does now, filling only the blanks, so nothing that resolves today changes. On one ordinary /24 that named 3 more of 26 hosts, taking 21 named to 24. Results carry a hostname_source of reverse or mdns, since a name from a device's own announcement is a different kind of claim from one in DNS.
  • The desktop app says the MCP server exists. Someone who only ever opens the app had no way to learn that netscli ships an MCP server, let alone connect an agent to one. There is now a panel that looks for a netscli binary and gives you the client configuration to paste, including what to do when it cannot find one — the desktop installers do not carry the CLI.

Fixed

  • netscli with no arguments no longer hangs when there is no terminal. With no subcommand it opens the TUI, which needs a terminal to draw on and read from. Without one it did not fail, it blocked forever: raw mode was entered and the runtime then waited on input that could never arrive. So netscli | head, or netscli from a script or a CI job, ran until something killed it. It now prints what --help prints and exits 0. The TUI is unchanged wherever there is a terminal.
  • The docs site lost its navigation and its theme switch between 800px and 1152px wide, and the search button sat stranded beside the wordmark. The header links — Features, Install, FAQ, Docs, Changelog, GitHub — and the light/dark control were both hidden across that range, on the understanding that the mobile menu carried them from there down. The button that opens that menu only appears below 800px, so for 352px of width there was nothing to press and no way to reach any of it. The docs sidebar is not a substitute: it lists the pages of the docs and carries five of those six links nowhere.

The search button had a second fault behind it. The width at which the links hide moved from 900px to 1152px and two rules that depended on that number stayed put, so the layout seam ended up on a hidden element, which takes no part in the layout. Search fell back to the left with up to 861px of empty bar beside it.

A new check measures where the header's controls sit at seventeen widths. Neither fault was visible to the existing accessibility, contrast or performance gates, because both are about position rather than markup, colour or speed.

  • netscli-gui-bin on the AUR installed a desktop app that could not start. The PKGBUILD did not set options=('!strip'), and strip is in makepkg's default options. An AppImage is the AppImage runtime — an ordinary static ELF — with a squashfs image appended after everything the ELF headers describe, so stripping it rewrote the file from its section table and threw the appended image away. What reached /usr/bin was the 944,632-byte runtime out of a 79 MB download, and running it said only "This doesn't look like a squashfs image". The package built, installed and verified its checksum at every step, because the truncation happened after the checksum was checked. Reported in #377.
  • The Linux desktop AppImage no longer aborts on hosts with a newer Mesa. It failed with Could not create default EGL display: EGL_BAD_PARAMETER before any window appeared. The AppImage carried its own copies of nine display-stack libraries — the wayland client stack, libxkbcommon, and part of the xcb/X11 stack — and put them ahead of the host's, so the host's Mesa was made to talk to the wayland client library from the machine the release was built on. Those libraries are now removed from the image after it is built. Reported in #378 against v0.2.6 on Mesa 26.2.2; v0.3.1 bundled the same nine.
  • A desktop window that opens black or blank now recovers on the next launch. WebKitGTK's hardware compositing can fail against a driver that only partly supports it, and it fails silently: the window opens, nothing paints, and there is nothing on stderr to go on. Seen on virtual machines using vmwgfx. The app now marks each launch and clears the mark once the UI has actually drawn a frame, so a launch that never drew one is noticed by the next, which turns hardware compositing off and says why.

This could not be a setting in the app. Every GUI preference lives in the webview's localStorage, and the webview is the part that is not rendering, so someone looking at a blank window cannot reach any of it. Alongside the automatic recovery there are now --disable-gpu-compositing and --gpu-compositing flags, which are remembered across launches. Linux only: the other two platforms use a web engine with neither the fault nor the setting. Also reported in #378.

  • Text in the desktop app's result tables can be selected again. Both the table and its wrapper set user-select: none, so a port, MAC address, vendor string or banner could not be dragged over with the mouse — and those values are on screen precisely so they can go somewhere else. The only route out was the detail pane. Selecting rows is a click, not a drag, so nothing was gained by it. Reported in #417.
  • The website's release notes lost their paragraph breaks, and the fade over a long entry read navy rather than matching the page. Both on the changelog page.

Changed

  • The website and docs got another pass. The install section's two controls line up and its alternatives stopped shouting; the hero badge shows the released version; the interfaces are shown rather than described; the README says only what a README can and its TUI screenshots work again; the comparison with nmap and the other scanners is fairer in both directions. Docs pages carry structured data, and the docs shell picked up a Lighthouse gate and two fixes it found.

Security

  • Three open advisories cleared. rustls 0.23.40 → 0.23.45 (RUSTSEC-2026-0285, medium), which was in 0.3.1's lockfile and so is in the binaries that release produced. The other two are the website's build dependencies rather than anything in a release artifact: adm-zip ≤0.6.0 (GHSA-vwc7-r8mq-g2x9 and GHSA-7q85-xj36-vmfc, high) and devalue <5.9.1 (GHSA-9rgm-9g3h-6x36, moderate).

v0.3.1

Sep 11, 2026

The desktop app is redesigned around a denser diagnostic workspace, with reorderable tabs, right-click tab management and a refreshed icon. Port scans return richer status data on every interface, probe concurrency is configurable everywhere, and the website and docs were rebuilt alongside.

The headline is the desktop app. Everything listed under it is new to anyone upgrading from 0.2.6 — the old dashboard-style GUI is gone, and what replaces it is a different application rather than a revision of that one. The rest is additive work in the core, CLI and MCP server, and a long tail of fixes. The recurring theme in that tail is code that reported success while doing nothing.

Added

  • The desktop app is a diagnostic workspace. NetsCLI Desktop replaces the earlier dashboard with a native-like shell built around operation tabs. It runs the same netscli-core operations as the CLI, TUI and MCP server, so its results match the rest of the tool. What it does:
    • Operation tabs you can reorder by drag or keyboard, and close individually, to either side, or all at once from a right-click menu.
    • Sortable and filterable result tables, row detail panes, and a preview of the CLI command each run is equivalent to.
    • Save a whole workspace of results to a file and reopen it later; export CSV or JSON, whole or selection-only. Exported cells are escaped against spreadsheet formula injection, since banners and hostnames are chosen by the scanned host.
    • Light and dark themes, and a settings dialog covering probe concurrency, default and traffic interfaces, IPv4/IPv6 display preference, history and save behaviour, and notification preferences.
    • Full keyboard operation, and a refreshed icon matching the site's brand.
  • Clear the ARP table, then discover. A chevron beside Run offers a cache-flushing variant on discover, sweep and the ARP tab — the tools where a stale neighbour entry changes the answer. Clearing needs administrator rights; when it fails the run is suppressed rather than quietly returning the stale entries it was meant to drop.
  • Richer port scan results across every interface. Port scans now report additive status and detail fields (open, closed, filtered, error, latency, banners, HTTP metadata, TLS metadata, and raw previews where available) while keeping the older open, port, service, and error fields intact for compatibility.
  • User-configurable probe concurrency. The CLI and MCP server already accepted concurrency limits; the desktop app and TUI settings now expose the same control, so probes can be reduced on fragile networks or raised within the core safety cap.

Changed

  • netscli scan --json now reports every port, not just the open ones. Filtering to open ports made "all closed", "all filtered" and "every probe errored" the same empty array, so a script could not tell a clean scan from a host that refused every probe. Each entry carries open and status, so callers that want only open ports can filter for them.
  • The MCP server now scans only local networks by default. This is the one surface driven by a model rather than by the person at the keyboard, so the instruction to scan a third party can arrive from a web page or a file someone else wrote — and the packets leave from your machine and your IP. RFC1918, loopback, link-local and the carrier-grade NAT range overlay networks use are allowed; set NETSCLI_MCP_ALLOW_PUBLIC_TARGETS=1 to reach past them.
  • Scan results returned to a model are capped. The full probe response (raw) is no longer included and banners are truncated, both being bytes chosen by the scanned host.
  • Tool failures are returned as MCP isError results rather than JSON-RPC errors, so a failed scan no longer reads to a client as a broken server.
  • CLI and TUI scan output reflects the richer status data. Human output stays concise, but scanned ports can show closed, filtered and error states with latency where available, instead of only emphasising open ports.
  • Windows install guidance prefers Winget for the desktop app. Winget's manifest review and installer hash verification make it the recommended Windows path; direct GitHub Windows installers remain unsigned and may show warnings until code signing is added.
  • Linux/macOS install docs clarified. mDNS is the default pure-Rust capability in published builds; packet capture remains the optional workflow depending on libpcap/Npcap.
  • The website and docs were rebuilt. A consistent shell, unified code and table styling, clearer search, and a layout swept across six widths and both themes. The brand accent moved from a teal-green that read blue in small text to one that reads green at any size.

Fixed

  • Pinging your own machine no longer reports 100% loss. On Windows, ping 127.0.0.1 — and the machine's own LAN address — timed out while the system ping answered immediately. Raw ICMP sockets need administrator rights, so an ordinary run fell back to TCP probes on ports 80/443/22 and concluded a host was down when nothing answered; and a Windows raw socket does not observe traffic to an address the host owns. Windows now sends echoes through the IP Helper API, which needs no privileges and reaches local addresses. Discover and sweep both start from a ping sweep, so both returned nothing for any range covering this host.
  • IPv6 hosts can be pinged. ping ::1 reported total loss because IPv6 had no ICMP path at all and fell through to the same TCP probe. Windows now uses Icmp6SendEcho2.
  • netscli arp --clear, --add and --delete no longer claim to have changed the table when they have not. On Windows these print "The requested operation requires elevation" and then exit 0, so checking the exit status alone reported success while nothing was touched — an ordinary run printed "ARP entry added for 192.0.2.77", and "ok": true in JSON, having done nothing. All three now share one check and exit non-zero with the reason. --clear also errors on platforms where it was never implemented, instead of reporting a cleared table.
  • Discover reports devices the OS already knows about. Results merge probe replies with the neighbour table, so a device that answers ARP but not ICMP is no longer missing. Each host records whether it was found by probe or by neighbour, since a stale neighbour entry can outlive the device.
  • Safety limits were enforced in Ops but not in the engines. The scan, sweep, discover and inspect engines are public API re-exported at the crate root, and called directly they applied no subnet, port or concurrency cap — 0.0.0.0/0 collected 4,294,967,294 addresses into a Vec before sending a packet. Every engine now enforces its own limits. (#198)
  • Safety limits that only one caller was applying. SweepEngine::sweep validates its port list instead of trusting the caller and silently returning "no open ports"; mDNS browse duration, ping -c and packet captures given a packet count but no duration all gained the core-side ceiling they were documented to have.
  • Port 0 was rejected only by the MCP surface. Now rejected everywhere. (#164)
  • MCP server handled one request at a time. The read loop awaited each handler before parsing the next line, so a slow scan blocked every other request on the connection, including cancellation. Handlers now run concurrently under a semaphore. (#169)
  • Reading the ARP table blocked a runtime worker. On Windows and macOS it shells out to arp and waits on the child process; three callers invoked it straight from async code. With MCP handlers capped at 16 concurrent, sixteen of these could stall every worker — including the one reading stdin, so no further request could even be parsed. Moved to a blocking thread. (#196)
  • Four ways an MCP client could wedge or kill the server: no overall request deadline, permits acquired after spawning rather than before, a single invalid UTF-8 byte on stdin terminating the process, and client disconnect cancelling nothing.
  • The MCP server no longer says it returned everything while truncating. A capped result reported the byte count as its item count, so a 40,000-row scan that returned 11,518 rows said returned: 40000, total: 40000 beside truncated: true.
  • Packet captures fetched as a background MCP job are bounded like every other result. get_pcap_capture_result was routed before the limits that strip and truncate remote text, so the one result made entirely of bytes off the wire was the one that skipped them.
  • The concurrent packet-capture limit could be bypassed by calling the blocking capture tool, which never registered a job.
  • discover_network with no arguments failed on a host whose interface carries a /8, because the substituted default exceeded the /16 cap.
  • A database written by a newer netscli is refused rather than read. The version check treated a future schema as "already migrated", so an older build queried tables it had never seen.
  • netscli trace no longer prints router-supplied hostnames unsanitised. Hop names come from PTR records controlled by whoever runs those routers, and this was the last plain-text output path without the terminal-safety pass every other one had.
  • TUI mis-measured wide characters, so CJK and emoji in a remote-supplied hostname or banner pushed box borders out of alignment. (#173)
  • Panic paths in the core, and silent corruption in the OUI generator. (#172)
  • winget publishes the version number, not the tag. Both package manifests were passed the tag including its v, which the action only strips when the input is left empty. v0.2.2 through v0.2.6 are already in the public catalog that way, so winget show netscli reports a version this project never issued. Upgrades still work — winget normalises a leading v when comparing — and this release fixes what is displayed. The publish job now asserts MAJOR.MINOR.PATCH rather than trusting the strip, because winget-pkgs accepted all five without complaint and a merged manifest is permanent.
  • AUR packages are published against a re-hashed asset. Both AUR jobs took the published .sha256 sidecar on trust rather than downloading the asset and hashing it, which is the circular check the release scripts exist to prevent; the other registries already did this correctly.
  • The Windows installer verifies Npcap before running it. install.ps1 downloaded the Npcap installer from an overridable URL and launched it elevated with nothing checked; it now verifies the Authenticode signature and signer, and refuses to run an unsigned or unexpected binary.
  • install.sh no longer claims success before installing libpcap. A user who asked for capture support could read "Installed successfully" and get a binary that cannot capture.

Website

  • Small grey text was unreadable on card surfaces. The docs footer, built-with row and mobile section labels use --sl-color-gray-3, much of it at 12px. It had been raised once to clear 4.5:1 against the page background, but nothing checked it against the slightly darker card surface, where it sat at 4.44:1.
  • Muted text, and several status colours, were below the readability bar. Eight colour tokens failed WCAG AA (4.5:1) against surfaces they are actually painted on — the mint accent failed as text on every light surface, down to 3.85:1. All 84 foreground/surface combinations now clear 4.5:1, and a check keeps them there.
  • The install guide has the verification steps the landing page promises. It advertised checksums and Sigstore signatures and linked to a page with none of them on it.
  • The site claimed packet capture in builds that do not ship it, and advertised a version that was never released. (#194, #208)
  • The interface coverage table no longer conflates separate things. Rows that merged setup with doctor, or reading the ARP table with changing it, are split, and dashes that meant "not applicable" say which.
  • The FAQ answers two questions people actually search for, from Search Console data rather than guesswork: whether there is a netscan command, and whether this replaces nmap and has a terminal UI.

v0.2.6

May 6, 2026

Installed GUI builds now identify themselves correctly, and the Windows title-bar controls work. Also completes the CLI/TUI refactors that make future interface changes easier to review and test.

Fixed

  • GUI: in-app version display was stuck at 0.1.0. A stale APP_VERSION constant in App.tsx powered both the bottom-bar version readout and the About dialog, but it never got bumped alongside package.json, tauri.conf.json, or the workspace Cargo.tomls. Caught by a Winget moderator on microsoft/winget-pkgs#368471: the v0.2.4 build correctly reported 0.2.4 to the Windows registry (Tauri pulls ProductVersion from tauri.conf.json), but users saw 0.1.0 in the GUI itself. Wired APP_VERSION to read package.json at build time via Vite's define so the in-app display auto-syncs every release going forward.
  • GUI: title-bar buttons (close, minimize, maximize) didn't work on Windows. Tauri 2's deny-by-default permission system requires explicit core:window:allow-close/minimize/maximize/unmaximize/start-dragging grants; the app was missing its capabilities config entirely. Added src-tauri/capabilities/main.json. (#62)

v0.2.5

May 5, 2026

Closes a DNS resolver security advisory. Windows subnet detection is fixed, so discovery and sweep find real LAN hosts again, and package publishing now covers GUI installers.

Security

  • hickory-resolver 0.24 → 0.26 closes RUSTSEC-2026-0119: CPU exhaustion during message encoding due to O(n²) name compression in hickory-proto. The DNS lookup tab and any inspect/discover that resolves hostnames are no longer reachable through the vulnerable encoding path. The 0.26 builder pattern (TokioResolver::builder_tokio()) replaces the deprecated TokioAsyncResolver::tokio constructor; see PR #55 for the source migration. The .cargo/audit.toml ignore added in #52 was removed once the bump landed.

Fixed

  • GUI: discover/sweep returned only a single host on Windows. Root cause: detect_default_ipv4_subnet iterated ipconfig::Adapter::prefixes() and grabbed the first IPv4 entry, but that list contains the host's own /32, broadcast /32, multicast /4, link-local /16, and the network /24. Windows reports the host /32 first, so the "subnet" was a single IP. New helper pick_ipv4_subnet_from_prefixes filters to network-shaped prefixes (length 1..=30, not multicast, not link-local) and truncates host bits, matching the Linux path. 5 unit tests added that run on every CI platform via cfg(any(windows, test)). (#59)
  • GUI: dashboard "Recent Scans" rendered with wrong colors / not as list rows. .history-item is a <button> (for keyboard accessibility) but the CSS didn't reset user-agent button styles. WebView2 on Windows applied Win32 chrome (color: ButtonText, centered text, content-fit width, system button font), breaking the inherit chain for child labels. Explicit reset added. (#59)

Changed

  • Dependencies (all transitive, no API surface impact):
    • crossterm 0.27 → 0.28 + tui-textarea 0.4 → 0.7 had to land together — tui-textarea 0.7 hardcodes crossterm = "0.28". (#58)
    • mdns-sd 0.13 → 0.19 — adapt to the new ScopedIp::to_ip_addr() accessor in netscli-core/src/mdns.rs. (#58)
    • clap 4.5.60 → 4.6.1 (#43), pcap 1.3 → 2.4 (#45), clap_mangen 0.2.33 → 0.3.0 (#46), dialoguer 0.11.0 → 0.12.0 (#48), dirs 5.0.1 → 6.0.0 (#51), tokio 1.52.1 → 1.52.2 + clap_complete 4.6.2 → 4.6.3 (#57).
  • ratatui 0.29 → 0.30 deferred: tui-textarea has no version yet that supports ratatui 0.30 (latest 0.7 still pins ratatui 0.29). Tracked via @dependabot ignore on the closed PR #49.

Added

  • Release pipeline GUI automation. publish.yml extended with 4 parallel jobs that publish the GUI bundles to Homebrew Cask, Scoop extras (netscli-gui.json), Winget (fstubner.netscli.gui), and AUR (netscli-gui-bin) on every tagged release. (#54, #53, #56)

v0.2.4

May 3, 2026

v0.2.3 built the GUI installers but never attached them. Publishing is repaired here, along with the AUR deploy action that was blocking Linux packages.

Fixed

  • GUI bundle path in release.yml's GUI matrix was rooted at apps/netscli-gui/src-tauri/target/${TARGET}/release/bundle/. Cargo workspaces actually use the workspace-root target/ directory regardless of which subcrate's directory cargo was invoked from, so Tauri's bundle output lives at target/${TARGET}/release/bundle/. v0.2.3 built the .deb / .dmg / .msi correctly but the collect step found an empty bundle dir and skipped everything; sigstore-sign then failed trying to sign nothing.
  • AUR deploy action (KSXGitHub/github-actions-deploy-aur) was pinned to @v2.7.0 (April 2024), which has a bash: --command: invalid option regression in its container entrypoint. Bumped to @v4.1.3 (current stable, same input shape).

Notes

  • CLI release shipped: 44 assets, sigstore-signed, on the v0.2.3 release page.
  • Homebrew, Scoop, Winget, and crates.io all updated to 0.2.3.
  • AUR is still on the previous version (failed to push).
  • 0 GUI installers attached to v0.2.3 release.

v0.2.3

May 3, 2026

GUI installer builds move again once the Tauri JavaScript and Rust versions agree, and the AUR packaging handoff is fixed. CLI packages were already usable from v0.2.2; the GUI artifacts needed these pipeline fixes.

Fixed

  • Tauri version skew broke all 4 GUI installer builds in v0.2.2's release matrix. The Cargo.toml constraint tauri = "2.0.0" resolved to tauri 2.9.5, but npm @tauri-apps/api: ^2 resolved to 2.10.1. Tauri's CLI rejects same-major different-minor as a version mismatch. Loosened the Rust constraint to tauri = "2" and ran npm update --save so both sides land on the same minor (currently 2.11.0). Verified with a local npm run tauri build producing NetsCLI_0.2.3_x64_en-US.msi cleanly.
  • AUR publish job in publish.yml failed on v0.2.2 with a confusing bash: --command: invalid option error from the deploy action's internals. Root cause: rendered PKGBUILD was written to /tmp/ PKGBUILD, but the KSXGitHub/github-actions-deploy-aur action runs in a Docker container that only mounts $GITHUB_WORKSPACE — files in /tmp are invisible inside the container. Render now writes to packaging/aur/PKGBUILD (workspace-relative) before handoff.

Notes

  • CLI binaries shipped successfully on v0.2.2 — cargo install, brew install netscli, and scoop install netscli all give v0.2.2.
  • v0.2.2 GitHub release has CLI assets but no GUI installers.
  • AUR netscli-bin was last bumped to v0.2.0; it'll catch up to v0.2.3 directly.

v0.2.2

May 3, 2026

This is a release-pipeline recovery build. It refreshes Cargo.lock so locked release builds can run reproducibly after dependency bumps, giving package-manager users a working replacement for the failed v0.2.1 artifacts.

Fixed

  • Cargo.lock was out of sync with Cargo.toml at the v0.2.1 tag — tokio 1.52.1 (bumped in #17) requires socket2 >= 0.6.3 transitively, but Dependabot only regenerated the direct-dep entries in the lock. CI's lint paths use cargo build (no --locked) so this slipped through; release.yml uses --locked to guarantee reproducible builds, and all 17 release builds for v0.2.1 failed at the lockfile check.
  • 0.2.2 regenerates the lockfile so socket2 0.6.3 is recorded alongside the existing 0.5.10. No application code changes.

Notes

  • Released to crates.io but the GitHub release page has no attached binaries (release.yml never produced any). cargo install netscli works because cargo regenerates the lockfile per-user; downloads from the GitHub release / package managers should use 0.2.2.
  • 0.2.1 is left in place as crates.io history rather than yanked.