Introduction
Setting up a web development or production environment on Windows has traditionally been a fragmented experience. You need Apache or Nginx for reverse proxying, OpenSSL for certificates, Docker for containers, PostgreSQL for databases, and a collection of security tools to harden your stack. Each tool has its own installer, configuration format, and update mechanism.
ProxyStack eliminates this complexity by packaging everything into a single portable download. Extract it to any folder, run ProxyStackGUI.exe, and you have a fully functional web stack with a modern dark-themed GUI. No installation, no registry changes, no admin privileges required. A cross-platform CLI ships alongside it for Linux servers, sharing the same proxystack.json configuration format and core service layer as the Windows GUI.
Since our last deep-dive article at v14.2.0, ProxyStack has gone through several rounds of hardening. The headline change in v14.6.0 wasn't a new feature category, it was turning two features that already existed in the UI (Zero Trust mTLS and Team-based RBAC) into things that actually change what happens on the wire, plus a genuine plugin execution runtime, real alert monitoring, and REST API hardening added in the releases leading up to it. This article covers what's real, what changed, and where the honest limits of each feature are.
Since that v14.10.0 revision, the product has grown a second axis entirely: a Security Center that unifies antivirus, VPN mesh, and process-behavior monitoring behind one framework; a real Process Reputation engine that scores every managed app's process tree and can detonate a suspicious sample in either of two real sandbox tiers, up to a genuine VM-isolated tier; a first-party-branded VPN mesh (ProxyStackVPN, with NetBird as a second selectable backend) that now joins cross-platform from Linux and macOS, not just Windows; real per-country geo-blocking; and a dedicated trust-and-reliability pass that found and fixed several bugs serious enough to block a fresh install outright. The sections below cover all of it with the same honesty bar as the rest of this article: what's real, what's still a stated boundary, and what was caught by live testing rather than assumed to work.
The Problem We Solved
Windows developers and system administrators face a unique set of challenges that Linux users don't encounter. The ecosystem is fragmented: IIS handles some things, Apache handles others, and getting them to coexist requires careful port management. SSL certificate management on Windows is notoriously painful. Docker Desktop works but adds significant overhead. And security tooling? Most of it is built for Linux first, Windows second.
We identified several pain points that ProxyStack addresses:
- Installation fatigue - Every tool requires its own installer, PATH configuration, and service registration. ProxyStack requires zero installation.
- Configuration sprawl - Apache/Nginx configs, SSL certs, Docker Compose files, database connection strings, and security policies are scattered across the filesystem. ProxyStack centralizes everything in one folder with one JSON config.
- Security as an afterthought - Most development stacks don't include security tooling. ProxyStack ships with OWASP compliance, WAF, security scanning, and Zero Trust mTLS built in.
- Compliance burden - Meeting SOC2, GDPR, PCI-DSS, or HIPAA requirements typically requires expensive third-party tools. ProxyStack includes evidence-based compliance reporting for all four frameworks.
- Portability - Moving a development environment between machines usually means hours of reconfiguration. ProxyStack runs from a USB drive if needed, and the same config format follows you to the Linux CLI.
Architecture & Design
ProxyStack's Windows GUI is built on .NET 8 with Windows Forms. The Linux/Windows CLI is a separate .NET 8 console application that shares a common class library, ProxyStack.Core, with the GUI, so configuration loading, saving, Nginx config generation, and Sentinel agent path resolution are implemented once and used by both.
Design Principles
- Portable first - Everything runs from the application directory. No system-wide changes, no registry entries, no Windows services.
- Configuration as code - All settings live in
proxystack.json. The GUI reads and writes this file. You can version-control it, share it via .psxt templates, or edit it directly. Every save is an atomic write with a rolling backup (last 30 kept inbackups/config-history/), so a crash mid-write can't corrupt your config. - Security by default - Security headers are enabled out of the box. The WAF ships with 14 rules active, with real inline blocking on Nginx. The compliance dashboard shows your security posture immediately.
- Offline capable - Internet is only needed for Let's Encrypt certificates, update checks, plugin marketplace installs, and SentinelAI. Everything else works offline.
- Say what's real - We've found and fixed a number of features in this codebase that had a finished-looking UI but no logic behind them. Rather than quietly patch those and move on, we document them (see Being Honest About What Isn't Finished).
Technology Stack
Runtime: .NET 8 (Windows Forms GUI, cross-platform CLI)
Web Servers: Apache 2.4 and Nginx 1.26 (bundled, selectable per install)
SSL: OpenSSL (bundled), win-acme (bundled, Let's Encrypt)
Database: PostgreSQL 14 (portable, optional)
Encryption: Windows DPAPI (CurrentUser scope) for the Secrets Vault
Certificates: X509Certificate2 (.NET), OpenSSL CLI
Config Format: JSON (proxystack.json, zero-trust.json, secrets.json, team.json)
Build: dotnet publish -c Release -r win-x64 / linux-x64
Core Infrastructure
Apache and Nginx Reverse Proxy
ProxyStack manages a bundled Apache 2.4 or Nginx 1.26 instance, selectable per install. The GUI generates the server config from your proxystack.json configuration. Each site can be a reverse proxy, a static file server, or a hybrid with per-path routing.
Key capabilities include TLS 1.2/1.3 with SNI for multi-domain support, WebSocket proxying, SPA fallback for single-page applications, and automatic certificate path detection. When you add a site with SSL enabled but no cert paths, ProxyStack scans the certs/ folder for matching files based on domain name patterns.
App Runner
The Apps view manages backend processes with health checks and auto-restart. A feature is sub-processes: you can define multiple commands per app (e.g., npx supabase start as a sub-process alongside npm run dev). Sub-processes start before the main command and are killed together when the app stops.
Docker & PostgreSQL
The Docker view provides full container lifecycle management: start, stop, restart, remove, logs, inspect, and exec. Docker Compose up/down is supported for multi-service deployments. For PostgreSQL, ProxyStack offers a portable installation that doesn't require Docker, with data in postgres/data/ and a built-in Database Console for direct SQL queries.
The Security Suite
ProxyStack ships as a comprehensive security platform, not just a reverse proxy. Several sidebar views each address a different aspect of application security:
Compliance (OWASP Top 10 2025)
10 automated checks against the OWASP Top 10 2025 standard. Security score 0-100 with color-coded category cards. Export as CSV or TXT.
WAF (Web Application Firewall)
14 built-in rules covering SQLi, XSS, path traversal, command injection, SSRF, XXE, and more. 3 operating modes. Custom rule editor. ModSecurity export.
Scanner (Security Scanner)
Quick Scan (11 checks) and Full Scan (18 checks) covering TLS, headers, permissions, secrets, ports, containers, and databases. Severity-weighted scoring with remediation guidance.
Secrets Vault (DPAPI-Encrypted Store)
8 categories, 5-second auto-hide reveal, 30-second clipboard clear, one-click rotation with 32-char cryptographic random, .env export.
Reports (Compliance Reporting)
SOC2 Type II (9 controls), GDPR (6 articles), PCI-DSS v4.0 (8 requirements), HIPAA (5 safeguards) = 28 total checks. Evidence-based with CSV/TXT export.
Zero Trust (mTLS & Network Segmentation)
Mutual TLS with CA/server cert generation, certificate-based auth policies, network segmentation zones, trust score 0-100. As of v14.6.1, enabling mTLS actually changes what both your Nginx and Apache sites serve, not just what the dashboard displays - and the auth policies and segmentation zones are enforced too.
Security Center (Unified Provider Framework)
One dashboard for antivirus (Windows Defender and a real, bundled ClamAV), VPN mesh health, and process reputation, each registered as an independently toggleable provider rather than one monolithic setting - the same pattern used elsewhere in the app for Nginx vs. Apache. Real since v14.9.0, with ClamAV and Headscale VPN going from honest stubs to fully real in v14.10.0.
Process Reputation & Sandbox Detonation
Live WMI process-tree monitoring for every ProxyStack-managed app, SHA256 hash reputation via a local allowlist/blocklist plus hash-only VirusTotal lookups, explicitly-gated full sample submission, and two real sandbox detonation tiers - a low-integrity isolated process and, as of v14.16.0, a genuine VM-isolated tier via a customer-supplied VirtualBox guest. Policy enforcement (kill, quarantine, block, isolate network) is opt-in and off by default.
VPN Mesh (ProxyStackVPN & NetBird)
A Headscale-coordinated mesh (self-compiled for Windows since v14.10.0) now runs a first-party-branded client - ProxyStackVPN, a self-compiled Tailscale daemon under our own name - with NetBird available as a second, independently selectable backend. Cross-platform mesh join has worked from Linux and macOS since v14.17.0, not just Windows.
Geo-Blocking
Real per-country CIDR blocking (data from ipdeny.com) compiled into a native Nginx geo map or an Apache Require not ip list - replacing a Classic UI checkbox whose own tooltip admitted it had no effect on live traffic.
Zero Trust: Real mTLS Enforcement
This is the headline fix of v14.6.0. Before this release, the entire Zero Trust view (mTLS toggle, auth policies, network segmentation) wrote to config/zero-trust.json, and nothing else in the codebase ever read that file. You could flip "Require client certificate" to on, restart your sites, and traffic would flow exactly as before. The toggle was cosmetic.
ProxyStack still generates real certificate material even before enforcement was wired up:
- CA Certificate - RSA 4096-bit, 10-year validity, self-signed root CA for your organization
- Server Certificate - RSA 2048-bit, 2-year validity, signed by the CA, with auto-generated Subject Alternative Names for all configured site domains
What changed in v14.6.0 is that NginxConfigGenerator started reading zero-trust.json directly when building each site's config. If mTLS is enabled, "require client certificate" is set, and the CA certificate file actually exists on disk, every HTTPS site gets real ssl_client_certificate, ssl_verify_client on, and ssl_verify_depth directives on the next Reload or Restart. Requests without a valid client certificate are rejected by the web server itself, not by application code.
v14.6.1 closed the two gaps we flagged at launch. ApacheConfigGenerator now emits the equivalent directives (SSLCACertificateFile, SSLVerifyClient require, SSLVerifyDepth), so Apache-mode installs get the same real mTLS enforcement Nginx installs already had. And Certificate Auth Policies (CN/OU/Issuer/Fingerprint/SAN matching) plus Network Segmentation zones are now enforced too, not just saved to a file nothing reads.
- Certificate Auth Policies compile to
SSLRequireexpressions on Apache andif ($ssl_client_...)checks on Nginx. Stock Nginx has no variable exposing a client cert's SAN entries the way Apache'sSSL_CLIENT_SAN_DNS_0does, so SAN Match policies are enforced on Apache only - we skip them on Nginx rather than silently matching the wrong thing. OU Match also has no dedicated Nginx variable, so it falls back to a substring check against the full subject DN there. - Network Segmentation zones become IP allow-list entries, merged with whatever the Security tab's IP whitelist already has. Only zones with direction "Inbound" or "Both" are enforced - an "Outbound" zone describes traffic the server itself initiates, and nothing in a request-time access-control directive can restrict that.
<Location>/<LocationMatch> semantics and Nginx's location-block structure don't map cleanly onto the free-text glob the UI accepts for that field, so we chose to under-scope (apply everywhere) rather than build fragile per-path matching that could silently misroute traffic. A network segment's "allowed ports" field is stored but not yet used to restrict enforcement to specific ports either. We'd rather ship enforcement that's real with a clearly stated boundary than one that quietly does less than the UI implies.
Security Headers Are Also Enforced Now
The same v14.6.0 fix applied to a second long-standing gap: the Security tab's HSTS, X-Frame-Options, X-Content-Type-Options, and X-XSS-Protection checkboxes, plus global IP allow/deny lists, were previously read only by the compliance-scoring views, never by the config generator itself. Turning on HSTS in Settings never made your server actually send a Strict-Transport-Security header. Both the Nginx and (as of v14.6.1) Apache generators now read config.Security and emit the corresponding headers and access-control directives for real.
Fixing this also surfaced a real bug in the existing Nginx IP whitelist: an allow-only list emitted allow lines with no trailing deny all, and Nginx's access module permits a request when nothing matches it - so a "whitelist" only ever added exceptions, it never actually excluded anyone. That's fixed now too, for both the global Security tab list and per-site allow lists.
Trust Score
The Trust Score (0-100) provides a quick visual indicator of your Zero Trust posture, calculated from mTLS enabled, CA cert configured, server cert configured, client verification set to "require," auth policies defined, and segmentation zones defined. As of v14.6.1, every one of those factors now corresponds to something the reverse proxy actually enforces, on both Apache and Nginx.
Opt-In RBAC: Real Enforcement, Not Just Labels
The Team view has existed for several releases, letting you add users with a role label like Administrator, User, or ReadOnly. Until v14.6.0, that role label was purely informational. Any user, regardless of role, could remove a site, reveal a secret, or prune Docker volumes. The compliance checker even had a bug where a fully configured team never satisfied its own "RBAC configured" check, because Team wrote to proxystack.settings.json while compliance looked for team.json.
v14.6.0 introduces PermissionService, a small, opt-in enforcement layer:
- The Team tab now has an "Acting as" dropdown, letting you select which configured user the current GUI session represents.
- If no team is configured, or no "Acting as" user is selected, every action remains fully unrestricted. Single-user installs are never locked out by a feature they never set up.
- Once a team and current user are both configured, seven specific actions are gated behind the Administrator role: removing a site, removing an app, revealing a secret in the vault, deactivating your license, and pruning Docker images, volumes, or networks.
The Plugin Runtime and Marketplace
ProxyStack's plugin system went through the same "looks real, isn't" history as Zero Trust. Before v14.5.0, the Advanced tab's plugin marketplace listed 20 fictional plugins, and clicking "Install" wrote a manifest file that nothing ever executed.
Starting in v14.5.0, plugins are real. Each plugin ships a plugin.json manifest declaring which lifecycle hooks it subscribes to: server.started, server.stopped, config.changed, or a periodic schedule.tick. PluginManager triggers the appropriate hook automatically when you start or stop services, or on a timer, and runs the plugin's entry script (PowerShell, Python, batch, or a compiled executable) with a 20-second timeout so a broken plugin can't hang the app. Every run is logged per-plugin and viewable from the Advanced tab.
Installing a plugin from the hosted marketplace at proxystack.iamvcholdings.com/downloads/plugins/catalog.json downloads a ZIP and verifies its SHA256 hash against the catalog entry before extracting it. You can also install your own plugin from a local ZIP the same way.
Six starter plugins ship with every download, all disabled by default:
- Uptime Pinger - periodically checks configured sites and logs response time and status
- Cert Expiry Notifier - warns when an SSL certificate is approaching expiration
- Deploy Webhook Notifier - posts a Slack/Discord message when a deploy-related config change happens
- Brute-Force IP Blocker - parses access logs for repeated auth failures and blocks offending IPs
- Config Git History - commits
proxystack.jsonchanges to a local git repo for version history - Daily Ops Digest - once a day, posts a summary of site count, soonest cert expiry, disk space, and recent error rate
The updater is aware of plugins too: when installing a new ProxyStack version over an existing install, files under plugins/ are only skipped if they already exist, so new starter plugins are delivered without clobbering a plugin you've already enabled and configured.
Alerts That Actually Monitor
Settings → Alerts & Notifications has supported Slack and Discord webhooks for a while, but until v14.5.2 the only code path that ever called AlertService.SendAlertAsync was the manual "Test Alert" button. Nothing monitored your actual server state.
As of v14.5.2, a 30-second health check timer runs alongside the app. It tracks whether you deliberately stopped a service, and only fires a "down" alert if a service that was expected to be running has unexpectedly stopped, so clicking Stop yourself never triggers a false alarm. Alerts are also now wired into StartAllServices(), StopAllServices(), and RestartApache() directly. That last one mattered because Restart previously had its own separate implementation that silently skipped both plugin hooks and alerts, a divergent code path we found and fixed in the same release.
Discord alerts (both the built-in ones and the webhook plugins) post proper embed cards with title, color, fields for host/status/time, and a timestamp footer, rather than a single line of plain text, while remaining Slack-compatible from the same webhook URL.
OWASP Compliance in Depth
The OWASP Top 10 is the most widely recognized standard for web application security. ProxyStack's Compliance view maps each of the 10 categories to specific, measurable checks against your live configuration.
For example, A02: Cryptographic Failures checks whether TLS is enabled on all configured sites and whether certificate paths are valid. A03: Injection verifies that WAF rules for SQL injection, XSS, and command injection are active. A09: Logging Failures confirms that access and error logging are enabled.
Each check produces a Pass or Fail result with a specific reason. The overall security score is calculated as a weighted average, giving more weight to critical categories like injection and access control. Results can be exported as CSV or TXT.
WAF: Request-Level Protection
The Web Application Firewall's rule engine inspects incoming HTTP requests against 14 pattern-matching rules covering SQL injection, XSS, path traversal, command injection, SSRF, file inclusion, XXE indicators, header/request splitting, log injection, sensitive file access, user-agent anomalies, scanner/prober paths, and disallowed HTTP methods. Three operating modes are available: Detect Only, Block & Log, and Block Silent. The full rule set can also be exported as a ModSecurity-compatible .conf file for a separately installed ModSecurity module.
As of v14.7.0, Nginx installs get real inline blocking, not just a threat log. A shared rule engine (ProxyStack.Core) is now the single source of truth for what a rule matches, used identically by three places: the Nginx config generator, the background log monitor, and a new "Test WAF Rule" tool in the WAF view. Every enabled Block-mode rule that compiles as a regex and doesn't contain a pattern Nginx would misread as a variable reference gets compiled into config/waf-nginx.conf as a native if (...) { return 403; } condition, included from every site's server block. We verified this against a live, sandboxed Nginx instance rather than trusting the generated syntax: a ' OR '1'='1 SQL injection tautology and a request to /.env both returned 403, a plain GET / stayed at 200, and switching the WAF to Detect Only mode and regenerating immediately dropped both back to 200.
The background log monitor (rewritten as WafRuntimeMonitor, replacing the old detection-only engine) still tails every *access.log file every 60 seconds and runs the same shared engine against anything the inline path can't cover, writing real events to logs/waf-threats.jsonl. Request targets are matched with encoding-tolerant patterns (literal whitespace, "+", and "%20" are all treated as equivalent) since Nginx never decodes $request_uri/$args and there's no inline decode step available to it.
A real false positive shipped in the default rule set and reached production before it was caught: modern Chrome's frozen User-Agent suffix reports its build/patch as .0.0.0, so any Chrome major version ending in a zero (110, 120, 130...) spells out the literal substring 0.0.0.0 in its User-Agent string. WAF-010's SSRF rule matched that substring unanchored, so roughly 1 in 10 real Chrome visitors got a genuine 403 on any site with default WAF rules enabled - not a synthetic test case, a customer's live 403 report on two production sites. Fixed in v14.17.2 with digit-boundary guards ((?<!\d)0\.0\.0\.0(?!\d)) that reject the pattern when it's embedded in a longer digit run, while a real SSRF payload like http://0.0.0.0/admin still matches correctly - verified live by replaying the exact previously-failing request. Existing installs self-heal: the rule engine recognizes the old default pattern on load and rewrites it to the fixed version, but leaves a customer's own custom edit to that rule alone.
Secrets Management Done Right
ProxyStack's Secrets Vault uses Windows DPAPI with the CurrentUser scope: secrets are encrypted with your Windows user credentials, there's no master password to manage, and they can only be decrypted by the same user on the same machine. It's the same mechanism Chrome and Windows Credential Manager use.
The vault is designed with security-conscious UX: Reveal shows a secret for exactly 5 seconds before auto-masking, Copy clears the clipboard after 30 seconds, Rotate generates a new 32-character cryptographically random value, and Export .env generates a standard dotenv file. Eight categories help organize secrets: General, Database, API Key, OAuth, SSL/TLS, Cloud, Service, and Internal. Revealing a secret is one of the seven actions gated by RBAC when a team is configured.
Enterprise Compliance Reporting
ProxyStack automates evidence-gathering for four major frameworks: SOC2 Type II (9 controls covering access control, encryption, logging, incident response, change management, and backups), GDPR (6 articles covering encryption, access control, audit logging, data minimization, breach notification, and right to erasure), PCI-DSS v4.0 (8 requirements), and HIPAA (5 safeguards), for 28 checks total.
Each check evaluates your real configuration, reading proxystack.json, checking file permissions, verifying TLS settings, and examining logging configuration. The evidence text shows exactly what was found.
Automated Security Scanning
Quick Scan (11 checks) covers TLS protocol versions, security header presence, directory permissions, secrets exposure, and basic network security. Full Scan (18 checks) adds open network ports, Docker container security, database security, and deeper configuration review. Each finding includes a severity level, description, and remediation guidance, and results persist to logs/security-scan.json.
Security Center: Unified Threat Visibility
Security Center (v14.9.0) is a provider framework, not a single feature: antivirus, VPN mesh, and process reputation each register as an independent ISecurityProvider with its own status card, the same "each backend is its own toggleable card" convention already established elsewhere in the app (Nginx and Apache coexist as separate config generators, never a single mode switch). At launch, the antivirus provider (Windows Defender, queried live via MpCmdRun/WMI) was real; ClamAV and Headscale VPN shipped as honest stubs, clearly labeled "not yet implemented" rather than faked as working.
As of v14.10.0, both of those stubs are real. ClamAvAntivirusProvider bundles a real ClamAV engine and runs actual scans with freshclam-updated signatures (a manual "Update Signatures Now" button was added in v14.13.1, since the only prior update path was an unattended 24-hour background timer with no way to trigger one on demand). HeadscaleVpnProvider stood up a self-compiled Headscale coordinator - Headscale ships no official Windows binary at all, so this meant building it from source, in pure Go with no CGO dependency, specifically to get a working Windows server binary that doesn't exist upstream.
Process Reputation & Real Sandbox Detonation
Process Reputation shipped across five releases (v14.11.0-v14.13.0) as five distinct, independently-verified phases, each building on the last rather than landing as one large, hard-to-verify feature.
Phase 1: Real Process-Tree Monitoring
ProcessReputationProvider walks the real Windows process tree via WMI (Win32_Process) for every ProxyStack-managed app and its children/grandchildren - .NET's own Process class exposes neither parent PID nor command line on Windows, so this required the same WMI technique already used elsewhere in the codebase for hardware ID generation. Each newly-seen process gets a real SHA256 hash, Authenticode publisher (if signed), and a deterministic score built from a fixed rule set: a risky interpreter (PowerShell, cmd, wscript, certutil, rundll32, and a dozen more) appearing as a grandchild-or-deeper of a managed app, known LOLBin command-line patterns (encoded PowerShell, Squiblydoo regsvr32, hidden-window flags), and unsigned binaries running from temp-like paths. Verified live: a real three-level cmd -> cmd -> powershell -nop -w hidden chain correctly triggered both the depth-based rule and the LOLBin pattern match.
Phase 2 & 3: Hash Reputation and Gated Sample Submission
Two IFileReputationSource implementations: a local allowlist/blocklist, and a real hash-only VirusTotal lookup (GET /files/{sha256}) that never uploads anything. A hash VirusTotal has never seen returns Unknown, never fabricated as Clean - verified live with a real never-seen hash. Full sample submission (uploading the file's actual bytes) exists as a separate method behind an explicit "Submit Sample for Analysis..." button with a privacy warning requiring a manual Yes; nothing in the automatic scan path ever calls it.
Phase 4: Two Real Sandbox Detonation Tiers
IsolatedProcessSandboxAnalyzer (v14.12.0-v14.13.0) launches the sample at Windows Low Mandatory Integrity Level - confirmed via whoami /groups from inside the sandboxed process itself, not inferred from an API return code - inside a scoped, Low-integrity-writable temp directory, with a best-effort firewall block and a real 15-second monitoring window for new child processes, file writes, registry Run-key persistence, and outbound TCP connections attributed to the exact watched PID(s).
As of v14.16.0, a second, stronger tier exists: VmSandboxAnalyzer detonates a sample inside a genuinely separate OS - a customer-provisioned VirtualBox guest VM, orchestrated via VBoxManage (the same "shell out to a real CLI tool" convention already used for ClamAV and Headscale). ProxyStack never bundles or licenses the guest OS; the customer supplies their own Windows VM with Guest Additions and a clean baseline snapshot. TieredSandboxAnalyzer prefers the VM tier when it's configured and verified working, and silently falls back to the process tier otherwise, so no existing install's behavior changes by default.
VBoxManage command lines as raw strings; a directory path ending in a backslash before a closing quote got silently corrupted by Windows' own command-line quoting rules, and the sample failed to launch with no clear error. Fixed by building every invocation on ProcessStartInfo.ArgumentList instead. Separately, a naive system-wide TCP snapshot diff on the process tier produced false positives from unrelated background traffic - fixed by attributing each connection to its real owning PID via GetExtendedTcpTable. And the VM tier's network detection missed a connect-then-immediately-close probe (the realistic shape of a C2 check-in) until v14.17.0 added continuous 2-second polling across the whole monitoring window instead of a single before/after snapshot pair.
Phase 5: Real Policy Enforcement
ProcessEnforcementService can Kill, Quarantine (move + JSON sidecar, mirroring ClamAV's quarantine convention), Block Execution (a real-time Win32_ProcessStartTrace watcher that kills future launches from the same path), Isolate Network (a real Windows Firewall outbound block rule), and Suspend/Resume (via P/Invoke, since .NET's Process class has no built-in suspend). Automatic enforcement stays opt-in - the default policy only warns - and the two Administrator-gated actions were verified live at full strength under real elevation: a real firewall rule added and confirmed via netsh advfirewall firewall show rule, and the real-time watcher killing a relaunched blocked binary within 2.5 seconds.
VPN Mesh: ProxyStackVPN & NetBird
The mesh's coordinator (Headscale, self-compiled for Windows since v14.10.0) was always ours, but every joining node originally ran the official, unmodified Tailscale client - the software actually connecting nodes together was third-party-branded. v14.15.0 closed that gap: tailscale/tailscaled are self-compiled from the same open-source BSD-3-Clause daemon Tailscale itself ships (only their GUI tray wrapper is closed-source), with two small source patches so the Windows service registers as "ProxyStackVPN" and the TUN adapter shows as "ProxyStackVPN" in Task Manager and Network Connections, instead of "Tailscale."
NetBird is registered as a second, independently selectable IVpnProvider - the same "separate card per backend" pattern as ClamAV/Defender. Only NetBird's client is bundled; its self-hosted Management/Signal/Coturn stack is AGPL-3.0-licensed (confirmed by reading the actual upstream LICENSE file, not just a GitHub summary tag), so ProxyStack never runs it as a service on a customer's behalf, the same arm's-length relationship the app already has with Docker.
As of v14.17.0, Cluster's mesh-join flow is genuinely cross-platform: the CLI (Linux/macOS) now serves the same /api/cluster/mesh/invite and /join endpoints the Windows GUI's ApiServer already had, installing a self-compiled Linux build of ProxyStackVPN as a real systemd unit rather than a background child process that would die the moment proxystack serve restarted. Live-verified end-to-end: a WSL2 Ubuntu node joined a real Headscale coordinator, confirmed both by the client's own assigned mesh IP and, independently, by the coordinator's own node list showing the peer online with a used pre-auth key.
tailscale up has no --unattended flag at all (confirmed via its own --help, not documentation), and its auth-key flag is --auth-key, not --authkey. Separately, HeadscaleVpnProvider's generated config bound to 127.0.0.1 only - a real gap that had existed since mesh join first shipped in v14.10.0, invisible until an actual cross-machine join was attempted, since MeshInvite was already (incorrectly) advertising the real LAN IP to nodes that could never reach it. All three fixed and re-verified live.
Per-Country Geo-Blocking
A new GeoBlockingService (v14.17.0) downloads and caches per-country CIDR data from ipdeny.com - free, no account or license key, redistributable per their own published terms. NginxConfigGenerator compiles the cached ranges into a real geo map plus a per-site if guard (a hash lookup, not a linear list of thousands of deny lines, since a large country's aggregated zone can genuinely be that big); ApacheConfigGenerator adds the same ranges to its existing Require not ip deny list. This replaces a Classic UI checkbox whose own tooltip already admitted "checking this has no effect on live traffic" - real, live-verified with a real downloaded country zone and an isolated nginx -t syntax check.
The Trust & Reliability Pass
Between v14.17.1 and v14.17.7, a series of real user reports (a live production 403, a customer's fresh-install crash, a screenshot of a repeating Windows error dialog) drove a dedicated pass re-verifying things that had shipped but were never tested the way a real customer would actually hit them.
- REST API remote reachability -
ApiServer'sHttpListenerbound tohttp://localhostonly, which rejects any request whoseHostheader isn't literally "localhost" - meaning every real Cluster call between two separate machines could never have worked, only a machine calling itself. Now binds all interfaces where possible, with an honest fallback message when it can't. - The Modern UI never started the API server at all - the only code path that ever instantiated
ApiServerwas the legacy Classic UI, and its checkbox state was never even persisted. Fixed with a real Settings toggle on the default (Modern) UI. - Two fresh-install crashes, both invisible to prior testing because every dev-machine verification already had the missing files available system-wide: v14.17.5 fixed a
System.Managementassembly load crash on first launch (a requiredruntimes/folder was never part of the release ZIP), and v14.17.7 fixed a repeatingVCRUNTIME140.dll was not foundcrash from the bundled PostgreSQL binaries (the VC++ runtime DLLs were never bundled alongside them, despite the exact same fix already existing for ClamAV). - License Deactivate was local-only - it reset the app's local state but never called the license-api's own unbind endpoint, silently leaving a key locked to the old machine's hardware ID server-side. Now calls the real endpoint first.
- CLI start/stop/restart/reload had likely never worked against a real Linux system-package nginx or apache2 install, for either web server - the binary-lookup and config-generation code was written entirely around Windows' portable-bundle folder layout. Four compounding path/config bugs, found one at a time and each live-verified against a real WSL2 Ubuntu distro with both web servers actually starting real master+worker processes serving the CLI-generated config.
Secure Project Import: Sandboxed Drag-and-Drop Deploy
v14.18.0 adds a drop zone to the Dashboard and Apps views: drag a .zip or .7z archive containing a project onto it (or use the Browse button), and ProxyStack extracts it, normalizes a single-wrapping-folder archive (the shape every "Download ZIP" export from GitHub produces) down to the real project root, and detects what it is. The differentiator isn't the convenience of drag-and-drop itself; it's that executable stacks get dry-run through the existing Process Reputation sandbox before ProxyStack ever presents them as trustworthy, and the real extracted folder is never what gets executed - only a disposable copy is.
v14.19.0 broadens what gets recognized after reviewing how Jade Hosting - a "drop a ZIP, any language" competitor - positions itself publicly. Detection now covers Static, Node.js, Bun, Python, Go, .NET, Java, Ruby (Rails-aware), PHP (Laravel-aware), Rust, Deno, Docker Compose, and a raw Dockerfile as a broad fallback for anything else with its own build recipe. Every one of those beyond Static and the two container-based paths still routes through the sandbox before the Add App dialog opens prefilled. This is a real, tested first competitive slice - it is deliberately not a claim of full buildpack/Nixpacks/Railpack "any repo, no marker needed" parity; a repository with none of the markers above and no Dockerfile can still come back Unrecognized, and that boundary is stated on purpose rather than papered over.
Two Phases, Not One
Vetting runs as two separate phases through IsolatedProcessSandboxAnalyzer: an install/build phase (network left open, since a real npm install or pip install needs it) followed by a runtime smoke test (the guessed start command, scored more strictly - a running app should behave like a server, not a package installer). Each phase independently snapshots registry Run keys and scheduled tasks before/after, watches for new child processes via WMI's Win32_ProcessStartTrace, and monitors file writes both inside the disposable copy and across the real Windows Startup folders plus the top level of %TEMP%/%AppData%/%ProgramData% - the common places a dropped implant would try to persist.
Capture Everything, Classify - Don't Suppress
Every observation from both phases is recorded, never filtered out of the evidence trail. What changes by phase is scoring: an npm/node/pip/python child process during the install phase, or a file write under node_modules/.venv/build output inside the disposable copy, is tagged as expected noise and contributes zero to the score - but it's still in the record, visible via the risk banner's "View sandbox details" link, not hidden. Encoded or hidden PowerShell commands, known LOLBin interpreters (certutil, rundll32, regsvr32, mshta, and others), registry or scheduled-task persistence, and command-line references to sensitive host paths are scored regardless of phase. A write escaping the disposable copy is always captured; it's scored high whenever it lands in the Startup folder or is an executable file dropped anywhere, and still flagged - just weighted lower - for a plain, non-executable file landing in a commonly-active system location, since ordinary background activity from other software on the same host would otherwise flood the signal with noise unrelated to the sandboxed project.
Never the Real Folder
The archive extracts into deployments/<slug>/ - a real, permanent, update-preserved location. But every sandbox run first copies that project into its own disposable temp directory and only ever executes the copy; the real deployments/ folder is read-only from the sandbox's perspective for the entire process. Extraction itself rejects absolute paths, drive-letter paths, leading-slash paths, .. traversal, NTFS alternate-data-stream names, and duplicate-normalized destinations - checked per entry, before any bytes are written - and a real-bytes-written size cap (not just the archive's own declared metadata) catches a tampered archive mid-extraction with full cleanup on failure.
Prefilled, Never Auto-Started
A successful detection opens the same Add Site / Add App dialog you'd use by hand, prefilled with the guessed command, port, and working directory, plus a risk banner showing the score, phase-separated observation counts, and which sandbox tier actually ran. Nothing is saved, started for real, or set to autostart without an explicit Save click. A package.json doesn't always mean "run this as a server" - a project that looks like a React/Vite/frontend build now offers a choice instead of just a warning: build it from a disposable copy (npm install && npm run build) and open Add Site prefilled against the dist/build/out output, or run it as a long-lived App the way it did before. Docker Compose and Dockerfile projects skip the process sandbox entirely - Docker's own container isolation is the boundary there, not this feature's job to duplicate. Compose imports also preflight the rendered config (parsing every published host port and checking real local bind availability) before ever calling up, so a port conflict comes back as a specific, readable message instead of a raw Docker failure.
Docker Compose Becomes a Managed Object, Not a One-Shot Action
A Compose import used to be exactly that - one import, one up, then it was just another set of containers in the generic Docker list. v14.19.0 gives imported Compose projects their own tab: persisted project records with their services, images, build contexts, and published ports; start/stop/restart/down and log actions scoped to the project rather than individual containers; and a version history with source and config snapshots that supports rolling back to a prior import. This is a foundation, not a finished management layer - richer per-service actions and deeper build-context inspection are tracked in roadmap-extras.md, and rollback currently only covers Compose imports, not yet Sites, Apps, or Dockerfile imports.
Every import writes a permanent record to logs/deploy-imports.jsonl - archive hash, detected type, guessed commands, sandbox tier, score, verdict, and every individual observation from both phases - regardless of whether the user ultimately saved the result. An unrecognized project type or a failed extraction still gets logged, not silently dropped.
FileSystemWatcher scoped only to the disposable sandbox copy is structurally blind to writes anywhere else - a command that successfully dropped a file outside the copy produced zero observation and a clean score, until dedicated watchers were added for the Startup folders and the common system-noise locations described above. Separately, ProcessStartInfo.ArgumentList - normally the safer choice for launching a process - actively mis-launches a cmd.exe /c command that itself contains embedded quotes (a real risk for any guessed command referencing a quoted path), failing with a generic "syntax is incorrect" error that would have looked like the guessed command itself was simply wrong; fixed by passing the command as a raw argument string instead, the one deliberate, documented exception to this codebase's usual quoting rule. A full end-to-end pass through the actual assembled GUI (not just the underlying services) found the sandbox-analyzer wiring was reaching two of the three views that needed it too early in startup, before the analyzers even existed - the exact same class of ordering bug already fixed once for the existing Process Reputation re-check button, caught here before it could ship a second time. And a final whole-feature security pass found the disposable-copy step itself had no guard against a symlink or NTFS junction inside an extracted archive - its name can look like an ordinary file or folder, so path validation alone doesn't catch it, and .NET's own recursive directory enumeration follows reparse points with no built-in guard, meaning the copy step could have been made to try to copy an entire drive (or hang on a self-referential junction) before the sandboxed process ever ran. Fixed with a manual recursive walk that checks every entry's attributes before ever recursing into it, live-verified against a real junction pointing at a large directory and a real self-referential junction - both skipped in milliseconds, neither followed.
PORT environment variable and substituting it directly into the generated Flask CLI / manage.py runserver / vite --port commands, verified against real saved-port values that differed from the source defaults. Separately, a dropped Docker Compose project correctly detected its own nested compose file path but the GUI still popped the manual file-picker dialog instead of using it - the detected path was only ever wired to prefill the picker's filename field, not to skip it - and a shared refresh-in-progress flag let the stats timer and the container-list timer block each other, so a Compose Up could genuinely succeed while the container list kept showing nothing. Both fixed and reverified against a real running container.
AI Runtime Manager: Local LLM Hosting, First-Class
v14.19.0 adds a new AI Runtime Manager: ProxyStack scans your real hardware (CPU cores, AVX2/FMA/AVX512 support, RAM, disk, GPU vendor and VRAM, Docker/WSL2/Vulkan availability), recommends models from a curated catalog that will actually run well on it, detects or installs llama.cpp/Ollama, downloads a hash-verified model, and starts a real local OpenAI-compatible server - managed the same way a Site or App already is, with Start/Stop/Restart, live health checks, logs, and a generated per-deployment API key. It's in the GUI's new "AI Runtime" sidebar view and the CLI's proxystack ai ... command group, and it works on both Windows and Linux - the Linux path is verified end-to-end against a real WSL2 Ubuntu instance, including a real NVIDIA GPU detected through nvidia-smi passthrough, not just assumed to work because the Windows path does.
Eleven Curated, Hash-Verified Models
The catalog started at 3 models and grew to 11 for this release - still a deliberately small, hand-picked list rather than open Hugging Face browsing (that remains a real idea for a future, separate decision, not something folded in here). Every entry's SHA256 came from a real HTTP request against the actual Hugging Face file - not trusted from a listing page - and covers every practical tier: a tiny/fastest option for constrained hardware (TinyLlama 1.1B), small 1.5-3B general models including one with a genuinely long 128K-token context window (Llama 3.2 3B), two different 7B-class general models for comparison (Qwen2.5 7B and Mistral 7B v0.3), a dedicated code-generation model at two sizes (Qwen2.5-Coder 1.5B and 7B), and 9B/14B-class larger models (Gemma 2 9B, Qwen2.5 14B, Phi-4) for when RAM/VRAM allows it. Each entry also carries a real, model-specific recommended-use-case note and a speed-expectation hint, separate from the live, hardware-aware speed estimate the Recommended Models table already computes for your specific machine.
Security Center Watches It Too
A new AiRuntimeSecurityProvider gives every AI Runtime deployment the same real, continuously-checked scrutiny every other Security Center card gets: whether a server is bound to every network interface instead of just localhost, and separately, whether it's actually reachable on the LAN right now (a netstat-verified check, not just a config read); whether its API key is genuinely being enforced, checked with a real live HTTP request rather than just confirming a key exists in the vault; a deployment marked "Running" whose process has actually died; an unusually large log file; orphaned duplicate server processes; a Windows ACL check on the models folder; drift between the installed llama.cpp binary and what ProxyStack's own installer put there; and a few more real, checked conditions. Every finding carries a plain-language explanation and a concrete fix.
PROXYSTACK_LLM_API_KEY, while the real llama-server binary only reads LLAMA_API_KEY - confirmed against the binary's own --help output. Any client that could reach the port could call it with zero authentication, on any bind address, not just a remote-exposed one. Fixed and live-verified: a request with no key now gets a real 401 "Invalid API Key"; the correct key gets a real 200 with a real completion.
dotnet publish's own top-level copy of System.Management.dll is always a 72KB placeholder with no real Windows implementation - the real 312KB one only exists in a runtime-specific subfolder the build script already copied separately, but the wrong placeholder copy was also ending up at the top level, and having it there was enough to make hardware detection's WMI-based RAM check silently fail and report 0MB of RAM on a fresh install. And the model catalog file itself, llm-catalog.json, was never being copied into either the Full or Lite release package at all - a fresh install from either one would find zero models to recommend or download, even though every other part of the feature worked correctly. Both were only visible by actually testing the rebuilt release ZIP in isolation, not the already-working development environment; both are fixed in build-release.ps1 now.
The Cross-Platform CLI
ProxyStackCLI is a separate, cross-platform companion for Linux servers (and Windows Server Core / CI pipelines) that reads and writes the exact same proxystack.json as the GUI. In the run-up to v14.6.0, we audited the CLI for the same "looks wired up but isn't" pattern that Zero Trust and RBAC had, and found several real gaps:
- Config load/save had been hand-rolled with its own JSON I/O instead of using the shared
ConfigServicethe CLI already referenced, meaning it had none of the atomic-write or backup protection the GUI has. It now delegates toConfigServicedirectly. cloud statuswas reading the wrong JSON property name and always reported the web server as unset.- Cluster node promotion (
/api/cluster/promote) previously hardcoded the node's role instead of persisting it, so a promoted secondary would report itself as "secondary" again after a restart. It now reads and writesconfig/node-role.json, matching a fix made to the GUI's own API server for the same bug. - Sentinel agent path resolution was a separate, less complete copy of the GUI's resolver and was missing the extensionless
sentinel-agentbinary name used on Linux and macOS. Both copies now check for it.
The CLI stayed frozen at v14.7.0 for several releases while GUI-only features shipped, then got rebuilt twice in quick succession: v14.17.0 added the cross-platform mesh-join endpoints (see VPN Mesh above), and v14.17.1 fixed start/stop/restart/reload, which - per the first-ever manual command-by-command test against a real apt-installed nginx and apache2 - had likely never worked on a genuine Linux system-package install for either web server, for as long as Linux support had existed. The CLI's version now tracks independently of the GUI's, bumping only on releases that actually touch CLI-relevant code.
Auto-Update & Integrity
The update process: the Dashboard fetches the update manifest over HTTPS, compares versions, downloads the update ZIP with a progress bar, verifies its SHA256 hash, then hands off to ProxyStackUpdater.exe, which waits for the GUI to exit, backs up current binaries, extracts new files, and restarts. If anything fails, automatic rollback restores from the backup.
User data is never overwritten: configuration files, certificates, logs, backups, database data, secrets, and plugin config are all preserved across updates.
VirusTotal Verified
Every ProxyStack release is submitted to VirusTotal for scanning by dozens of antivirus engines.
The most recently scanned release (v14.19.0) has the GUI EXE and Updater EXE clean, while the GUI DLL has one vendor flag currently under review. Not every intermediate version between v14.10.0 and v14.19.0 was individually re-scanned (several shipped substantial engine/architecture changes and were scanned fresh rather than assumed clean by association; smaller point releases in between generally were not) - full VirusTotal reports for the latest scanned build are linked on the download page for independent verification.
The Modern GUI
ProxyStack's GUI is a native Windows Forms application with a custom dark theme. The modern UI (default) uses a sidebar navigation pattern with 22 views, while the classic UI (via --classic flag) provides a traditional tabbed interface.
The sidebar views are organized by function:
- Infrastructure - Dashboard, Sites, Apps, PostgreSQL, Docker, Certificates
- Monitoring - Monitor, Observability
- Security - Security, Security Center, Compliance, WAF, Scanner, Secrets Vault, Reports, Zero Trust
- Collaboration - SentinelAI, Team, Cluster
- Configuration - Advanced, Logs, Settings
Every view includes a console panel at the bottom that shows real-time log output, making it easy to monitor operations without switching to a separate log viewer.
Being Honest About What Isn't Finished
Across many full audit passes since (the GUI, the GUI plus CLI, a follow-up dedicated to Zero Trust and WAF, a dedicated pass to make WAF blocking real, a Process Reputation build spanning five separately-verified phases, and most recently a trust-and-reliability pass triggered by real user-reported bugs), we've kept finding the same recurring pattern in this codebase: a feature ships with a complete-looking UI, a settings panel, maybe a list view, and looks done, but the button's underlying method is either a no-op or is never called by anything except its own manual test button. Every instance we've found and specifically called out in this article has a real fix behind it: the plugin marketplace, Zero Trust mTLS (both engines), security header enforcement, Certificate Auth Policies, Network Segmentation, opt-in RBAC, alert monitoring, several CLI gaps, real inline WAF blocking on both engines, the REST API's remote reachability, License Deactivate's server-side unbind, and token-purchased feature unlocks that had never once reached a real desktop install.
That doesn't mean every corner of the app is airtight - it means we're not currently sitting on a known instance of this pattern that we've chosen not to fix. Apache's WAF support is a good example of a fix with a real, stated boundary rather than a complete one: it detects and logs, it doesn't block, by design, because building Apache-side inline enforcement was out of scope for this pass, not because we ran out of ideas for how. Certificate Auth Policies and Network Segmentation similarly enforce site-wide rather than honoring the UI's per-path Target field or per-zone port field. The isolated-process sandbox tier detonates on the host at reduced integrity, not inside a VM, unless the stronger VM tier is configured. NetBird's self-hosted server stack genuinely needs a public domain to stand up, so it hasn't been live-tested end-to-end here. Those boundaries are described in full where each feature is covered above, rather than left for someone to discover by testing.
Performance & Portability
- Startup time - The GUI launches in under 2 seconds on modern hardware
- Memory usage - Lightweight GUI process footprint; Apache/Nginx and PostgreSQL run as separate processes
- No background services - Nothing runs when ProxyStack is closed (unless you enable auto-start)
- Fully portable - Copy the folder to a USB drive, another machine, or a cloud VM. All paths are relative.
The .psxt template system makes it easy to share complete stack configurations between team members. Templates bundle the configuration JSON along with base64-encoded SSL certificates, so a teammate can import a template and have an identical environment running in seconds.
What's Next
With mTLS enforcement, RBAC, the plugin runtime, real alert monitoring, inline WAF blocking on both engines, Process Reputation with two real sandbox tiers, a first-party-branded VPN mesh, geo-blocking, and cross-platform mesh join all now shipped and live-verified, the roadmap items that defined the last several releases are largely closed out. Near-term work is focused on: a live end-to-end test of NetBird's self-hosted server stack once a real public-domain environment is available to test against; broadening RBAC's coverage past its current seven gated call sites if demand warrants it; continued expansion of the plugin ecosystem; and closing the remaining CLI-to-GUI feature gaps (Cluster's mesh-join REST orchestration, for instance, still only runs its full one-click flow on Windows, even though the underlying VPN mesh mechanics are cross-platform).
Ready to Try ProxyStack?
Download the portable Windows build or the Linux CLI, extract, and run. No installation required.
Download ProxyStack (latest version)Resources
- ProxyStack Homepage - Features, downloads, and changelog
- Full Documentation - Complete reference for all sections
- Getting Started Guide - From download to running proxy in 5 minutes
- FAQ - Frequently asked questions
- SentinelAI - AI-powered endpoint security monitoring