Desktop Shell (Tauri)
Ghost is a desktop application, not a web app — it runs as a native window on your computer with full access to system features like the proxy configuration, certificate store, file system, and system tray. The desktop shell is built with Tauri v2, a framework that wraps a native WebView (the same rendering engine your browser uses) in a Rust process. This gives Ghost the appearance and capabilities of a native app while using React for the user interface.
The architecture has three layers: the Rust shell (Tauri) manages the window, native menus, system tray, and safety mechanisms. The Go backend (called the “sidecar”) runs the proxy, API server, database, and all business logic. The React frontend renders inside the WebView and communicates with the Go backend through HTTP and WebSocket — the same way a web app talks to its server, except everything runs on your local machine.
Architecture
Section titled “Architecture”What this diagram shows — how the three layers interact:
The Tauri shell (Rust) is the outer container that manages everything at the OS level. It creates the window, builds the native menu bar and system tray, runs the safety net watchdog, and handles auto-updates. Inside the window, the React application renders in a WebView — this is the UI the user interacts with. The React app communicates with the Go sidecar through HTTP (REST API) and WebSocket (real-time events). The Go sidecar is a separate process that Tauri spawns as a child — it runs the MITM proxy, serves the API, manages the database, and does all the heavy lifting. The system tray also talks directly to the Go API (to show proxy status and toggle controls). The safety net operates independently of both — if the Go sidecar crashes, it can disable the system proxy using native OS calls without needing the sidecar to be alive.
Sidecar Lifecycle
Section titled “Sidecar Lifecycle”The Go backend runs as a “sidecar” — a separate executable that Tauri manages as a child process. The binary is named ghost-engine and is bundled inside the application package.
Startup Sequence
Section titled “Startup Sequence”- Tauri’s
setuphook fires during application initialization - Spawns
ghost-engine --sidecar— the Go binary runs with the--sidecarflag, which tells it to print its configuration as JSON to stdout instead of logging to the terminal - Reads stdout line by line, waiting for a JSON object with three fields:
{ "api_port": 5565, "proxy_port": 4545, "token": "abc123..." }
api_port— the port the REST API and WebSocket server are listening onproxy_port— the port the MITM proxy is listening ontoken— an authentication token for API access
- 15-second timeout — if the sidecar doesn’t emit valid JSON within 15 seconds, startup fails and the app exits with code 1. This catches scenarios where the binary is missing, crashes on startup, or hangs.
- Stores state — the sidecar info and process handle are stored in Tauri’s managed state (
GhostState) so other parts of the app can access the port numbers and token - Navigates the WebView — redirects the window from
about:blank(its initial URL) to:- Debug mode:
http://localhost:5173?__ghost_token={token}&__ghost_api_port={api_port}(loads from the Vite dev server for hot module replacement) - Release mode:
http://localhost:{api_port}?__ghost_token={token}&__ghost_api_port={api_port}(loads from the Go binary itself, which serves the embedded frontend assets)
- Debug mode:
- Drains stderr — a background task reads the sidecar’s stderr output and forwards it to the console, prefixed with
[ghost-engine], for debugging
Shutdown Sequence
Section titled “Shutdown Sequence”When the user quits Ghost (or the window closes), the shutdown runs in this order:
- Disable system proxy natively — calls
disable_system_proxy_native()directly (native OS mechanism, does NOT depend on the Go sidecar being alive). This is the most important step — if the sidecar is already dead, this ensures the system proxy doesn’t keep pointing at a dead port. - Try API disable — attempts
POST /api/v1/proxy/system/disablewith the bearer token, in case the sidecar is still alive and can do a cleaner disable - Kill sidecar — takes the
CommandChildfrom the mutex and calls.kill()to terminate the Go process
Debug vs Release
Section titled “Debug vs Release”| Mode | WebView URL | Frontend Source | When Used |
|---|---|---|---|
| Debug | http://localhost:5173?token=... | Vite dev server with HMR (hot module replacement — changes appear instantly without page refresh) | During development |
| Release | http://localhost:{api_port}?token=... | Go-embedded static files (the frontend is compiled into the Go binary via go:embed) | Production builds |
Both modes pass the authentication token and API port as query parameters so the frontend JavaScript can discover the backend.
Window Configuration
Section titled “Window Configuration”| Property | Value | Why |
|---|---|---|
| Default size | 1280 × 800 | Comfortable for traffic lists with split pane inspector |
| Minimum size | 900 × 600 | Ensures the layout doesn’t break — sidebar + flow list + inspector need at least this much space |
| Title | ”Ghost” | — |
| Title bar style | Overlay with hidden title | The title bar area is transparent, allowing the command bar to extend into it. The window title text is hidden (the Ghost logo serves as the visual title). |
| macOS traffic lights | Position (16, 20) | The close/minimize/maximize buttons are positioned to align with the command bar height. The frontend pads the left side (pl-20, 80px) to avoid overlapping them. |
| Centered | Yes | Window appears in the center of the screen on first launch |
| Resizable | Yes | — |
Content Security Policy
Section titled “Content Security Policy”The WebView enforces a strict CSP that limits what the frontend can do:
default-src 'self';script-src 'self';connect-src http://localhost:* ws://localhost:*;style-src 'self' 'unsafe-inline';img-src 'self' data: blob:;font-src 'self' data:What this means:
- Scripts: Only from the app itself — no external JavaScript can be loaded or injected
- Connections: Only to localhost — the frontend can only talk to the local Go backend (HTTP and WebSocket), not to any external server
- Styles: From the app itself plus inline styles (needed for Tailwind’s runtime styling)
- Images: From the app, data URIs (base64-encoded images), and blob URLs (used for screenshots and generated content)
- Fonts: From the app and data URIs (the woff2 font files are bundled)
- No
unsafe-eval: Theeval()function and its equivalents are blocked
Native Menu Bar
Section titled “Native Menu Bar”Ghost creates a full native menu bar with 9 menus. On macOS, this appears at the top of the screen (as users expect from native apps). Custom menu actions emit a menu-action event to the frontend with the item’s ID, where the React native menu handler processes it.
Ghost (App) Menu
Section titled “Ghost (App) Menu”| Item | Accelerator | Notes |
|---|---|---|
| About Ghost | — | Shows version, copyright “Ghost — AI-Powered Traffic Intelligence” |
| Settings… | Cmd+, | Opens the settings modal |
| Hide | — | Native macOS hide |
| Hide Others | — | Native macOS hide others |
| Show All | — | Native macOS show all |
| Quit | — | Triggers the shutdown sequence |
File Menu
Section titled “File Menu”| Item | Accelerator | ID |
|---|---|---|
| New Session | Cmd+N | new_session |
| Export Session → | — | Submenu |
| HAR | — | export_har |
| JSON | — | export_json |
| CSV | — | export_csv |
| Postman Collection | — | export_postman |
| Import HAR… | — | import_har |
| Close Window | — | Native |
Edit Menu
Section titled “Edit Menu”| Item | Accelerator | ID |
|---|---|---|
| Undo | — | Native |
| Redo | — | Native |
| Cut | — | Native |
| Copy | — | Native |
| Paste | — | Native |
| Select All | — | Native |
| Command Palette | Cmd+K | cmd_palette |
| Clear Session Flows | — | clear_flows |
View Menu
Section titled “View Menu”| Item | ID |
|---|---|
| Request Composer | view_composer |
| AI Chat | view_chat |
| Map Rules | view_rules |
| Addons | view_addons |
| Breakpoints | view_breakpoints |
| Toggle Theme | toggle_theme |
Tools Menu
Section titled “Tools Menu”| Item | ID |
|---|---|
| Start/Stop Proxy | proxy_toggle_menu |
| Breakpoint Rules | tools_breakpoints |
| Manage Addons | tools_addons |
Test Menu
Section titled “Test Menu”| Item | Accelerator | ID |
|---|---|---|
| Bug Reports | Cmd+Shift+B | test_bug_reports |
| Test Scenarios | — | test_scenarios |
| Generate Bug Report | — | test_generate_report |
Certificate Menu
Section titled “Certificate Menu”| Item | ID | Notes |
|---|---|---|
| Install Certificate on this Mac/PC | cert_install_mac | Label is platform-conditional |
| Install Certificate on iOS → | — | Submenu |
| Physical Devices… | cert_ios_device | |
| Simulators… | cert_ios_simulator | |
| Install Certificate on Android → | — | Submenu |
| Physical Devices… | cert_android_device | |
| Emulators… | cert_android_emulator | |
| Export Certificate… | cert_export | |
| Certificate Status | cert_status |
Window Menu
Section titled “Window Menu”All native items: Minimize, Maximize, Fullscreen.
Help Menu
Section titled “Help Menu”| Item | ID |
|---|---|
| About Ghost | about_ghost |
System Tray
Section titled “System Tray”Ghost adds an icon to the operating system’s system tray (macOS menu bar, Windows notification area). The tray provides quick access to proxy controls without needing to open the main window.
| Item | Initial State | Action |
|---|---|---|
| Open Ghost | Enabled | Shows and focuses the main window |
| Proxy: Starting… | Disabled | Toggles proxy start/stop via the Go API. Label updates to “Proxy: Running” or “Proxy: Stopped” based on status. |
| System Proxy: Checking… | Disabled | Toggles system proxy enable/disable via the Go API. Label updates to “System Proxy: On” or “System Proxy: Off”. |
| Quit Ghost | Enabled | Calls app.exit(0), triggering the full shutdown sequence |
Tooltip: “Ghost — Traffic Intelligence”
Icon click: Clicking the tray icon (not the menu) shows and focuses the main window.
Status Poller
Section titled “Status Poller”A background task polls the Go backend every 3 seconds to update the tray menu:
- Calls
GET /api/v1/proxy/statuswith bearer auth to check proxy state - Calls
GET /api/v1/proxy/system/statusto check system proxy state - Emits
tray-updateevents to update menu item labels (e.g., “Proxy: Running” or “Proxy: Stopped”, “System Proxy: On” or “System Proxy: Off”) - Tracks
last_sysproxy_enabledfor the safety net
Safety Net
Section titled “Safety Net”The safety net is Ghost’s most important reliability feature. It prevents the catastrophic scenario where Ghost crashes or the Go sidecar dies while the system proxy is still pointing at Ghost’s port — which would break ALL internet connectivity on the user’s machine (every HTTP request would try to connect to a dead proxy port and fail).
How It Works
Section titled “How It Works”The status poller (described above) tracks consecutive failures when trying to reach the Go API. If the API becomes unreachable for 3 consecutive polls (approximately 9 seconds) AND the system proxy was last known to be enabled, the safety net activates:
- Calls
disable_system_proxy_native()— a Rust function that disables the system proxy using native OS mechanisms, without any help from the Go backend - Resets
last_sysproxy_enabled = falseto prevent repeated disable attempts
Platform Implementations
Section titled “Platform Implementations”macOS:
- Runs
networksetup -listallnetworkservicesto get all network interfaces - Filters out header lines (lines starting with
*) - For each service, runs 4 commands in deliberate order:
-setwebproxy {service} "" 0— clear HTTP proxy address-setsecurewebproxy {service} "" 0— clear HTTPS proxy address-setwebproxystate {service} off— disable HTTP proxy-setsecurewebproxystate {service} off— disable HTTPS proxy
Why address is cleared before state: Some VPN and security agents (like GlobalProtect) monitor proxy state changes and re-enable their own proxy settings. By clearing the address first, even if a VPN agent re-enables the proxy state, it points to an empty address rather than Ghost’s dead port.
Windows:
Sets ProxyEnable = 0 in the Windows registry at HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings using the winreg crate. This is the same registry key that the Windows “Internet Options” dialog uses.
Linux: No-op — Linux proxy configuration is fragmented across GNOME (gsettings), KDE (kwriteconfig), and environment variables, and there’s no single reliable mechanism to disable all of them.
Tauri Commands
Section titled “Tauri Commands”Three functions are exposed from Rust to the frontend JavaScript via Tauri’s IPC bridge:
| Command | Parameters | Returns | Purpose |
|---|---|---|---|
force_disable_system_proxy | None | Nothing | Emergency trigger for the safety net. Called by the frontend’s proxy store as a fallback when the Go API is unreachable. |
open_path | path: String | Result<(), String> | Opens a directory in the OS file manager (Finder on macOS, Explorer on Windows, xdg-open on Linux). Validates that the path exists and is a directory — rejects files and URLs. |
airdrop_cert | handle: AppHandle | Result<(), String> | macOS only. Fetches the .mobileconfig profile from the Go backend (http://localhost:{proxy_port}/ghost-profile.mobileconfig), writes it to a temporary file, then opens the native AirDrop sharing picker via NSSharingService (spawns a Swift subprocess). Used to quickly send the certificate profile to physical iOS devices over AirDrop. |
Auto-Update
Section titled “Auto-Update”Ghost checks for updates via the Tauri updater plugin, which downloads from a GitLab packages endpoint (Hepsiburada’s internal GitLab instance).
| Property | Value |
|---|---|
| Plugin | @tauri-apps/plugin-updater v2 |
| Endpoint | GitLab generic packages (/ghost-releases/latest/latest.json) |
| Windows install mode | Passive (installs without user interaction after download) |
| Check timing | 5 seconds after app launch (to avoid competing with initial data loading) |
| Check timeout | 30 seconds |
The frontend’s updater store tracks the full lifecycle: checking → available (with version info, release notes, date) → downloading (with 0-100% progress) → installed (triggers app relaunch).
Bundle Targets
Section titled “Bundle Targets”| Platform | Format | Install Scope | Notes |
|---|---|---|---|
| macOS | DMG | User | Standard drag-to-Applications installer |
| Windows | NSIS | Current user | No admin privileges required (currentUser scope) |
The ghost-engine Go binary is bundled as an external binary at binaries/ghost-engine inside the application package.
Plugins
Section titled “Plugins”| Plugin | Version | Purpose |
|---|---|---|
| tauri-plugin-shell | v2 | Sidecar process management (spawning ghost-engine), opening external URLs in the default browser |
| tauri-plugin-dialog | v2 | Native file save/open dialogs for export and import operations |
| tauri-plugin-fs | v2 | File system access for writing exports, scoped to $APPDATA/**, $DOWNLOAD/**, and $DESKTOP/** |
| tauri-plugin-updater | v2 | Auto-update from GitLab packages endpoint |
| tauri-plugin-process | v2 | Process management — allow-restart (for applying updates) and allow-exit |
Capabilities and Permissions
Section titled “Capabilities and Permissions”Tauri v2 uses a capability system to declare exactly what the frontend JavaScript is allowed to do. Ghost defines two capability files:
default.json — Main permissions for the app window:
- Core window management (set title, show, hide, close, minimize, maximize, focus, center, drag)
- Shell access restricted to the
ghost-enginesidecar only (cannot spawn arbitrary processes) - File writes scoped to AppData, Downloads, and Desktop directories only
- Dialog, updater, and process permissions
localhost.json — Special capability that grants IPC access to the http://localhost:* origin. This is necessary because in release mode, the WebView navigates to the Go backend’s localhost URL (not a Tauri custom protocol), and Tauri’s JS APIs (events, drag, dialog) need explicit permission to work from a remote localhost origin.
Application Identity
Section titled “Application Identity”| Field | Value |
|---|---|
| Product name | Ghost |
| Bundle identifier | com.ghost.app |
| Version | 0.1.0 |
| Rust crate | ghost-desktop (library: ghost_desktop_lib) |
| Rust edition | 2021 |
| Windows | Console window suppressed in release builds (windows_subsystem = "windows") |