App Architecture
Ghost’s frontend is a single-page React 19 application with no router — there are no separate “pages” or URL paths to navigate between. Everything lives on one screen. The traffic list is always visible, and every other feature (AI chat, map rules, addons, settings) appears as an overlay panel that slides in from the right side. When you select a flow, the traffic list shrinks and a detail inspector appears next to it. This design ensures you never lose sight of the traffic — the data you’re working with is always right there.
Think of it like a desktop email client: the inbox (traffic list) is always on screen, clicking an email (flow) opens a preview pane beside it, and toolbars (slide-over panels) open on the side without navigating away. There’s no back button, no page transitions, no loading screens between views.
Component Tree
Section titled “Component Tree”What this diagram shows — how the entire UI is structured as a tree of components:
The App component is the root of everything. When Ghost first launches, it checks whether the first-run setup has been completed (installing the root certificate and enabling the proxy). While checking, it shows a pulsing ghost logo. If setup hasn’t been completed, it shows the SetupWizard. Once setup is done, it renders the AppShell — the main application layout.
The AppShell has three permanent zones stacked vertically: the CommandBar at the top (38px tall, containing the logo, session picker, mode toggle, proxy button, search bar, and toolbar icons), the main content area in the middle (which includes the left sidebar, the traffic/content view, and an optional slide-over panel), and the StatusBar at the bottom (28px tall, showing proxy status, flow rate, connection health, and resource usage).
The main content area is wrapped in a DropZone that accepts HAR and JSON file drops for session import. Inside it, the left sidebar (240px wide, collapsible) shows either the ScopePanel (domain navigator, app list, device list) in QA mode or the FindingsPanel (security findings and target configuration) in security mode.
The center area uses a single-slot routing system (activePanel in the UI store). When activePanel is null, the default traffic view is rendered. When it’s set to a panel ID (e.g., 'comparison', 'inspector', 'attack-planner'), the corresponding component from PANEL_COMPONENTS replaces the traffic view entirely. Only one exclusive panel can be active at a time — mutual exclusion is inherent because there’s a single slot. Each exclusive panel has a <BackButton /> that shows the previous panel’s label (e.g., ”← Attack Planner” or ”← Traffic”) and restores it via a closure-based snapshot.
Panel definitions live in stores/panel-registry.ts. Adding a new exclusive panel requires: (1) an entry in the registry, (2) a component in PANEL_COMPONENTS, (3) calling openPanel(id), and (4) adding <BackButton /> to the panel header.
The traffic view has a FilterStrip at the top (preset buttons, content type pills, and a visual query builder) and either a full-width flow list (when no flow is selected) or a resizable split panel (flow list at 55% + flow inspector at 45%) when a flow is selected.
On the right side, a SlideOver panel can appear as an overlay (420px default width, resizable from 320px to 70% of the viewport). Only one slide-over is visible at a time — opening a new one replaces the current one.
Several overlay components float above everything: the settings modal, about dialog, setup guide, breakpoint editor, bug report overlay, comparison flow picker, and command palette.
Layout Structure
Section titled “Layout Structure”┌──────────────────────────────────────────────────────────────────┐│ CommandBar (38px) ││ [logo] [session] [QA|Security] [proxy] | [search + filters] | [toolbar icons] │├──────┬───────────────────────────────────────────┬───────────────┤│ Side │ FilterStrip (presets + content types) │ SlideOver ││ bar │ ┌──────────────┬────────────────┐ │ Panel ││ 240px│ │ Flow List │ Flow Inspector│ │ 420px ││ │ │ (55%) │ (45%) │ │ (resizable ││ │ │ min: 20% │ min: 15% │ │ 320-70vw) ││ │ │ │ │ │ ││ │ └──────────────┴────────────────┘ │ │├──────┴───────────────────────────────────────────┴───────────────┤│ StatusBar (28px) ││ [proxy●port] [flow rate] [throttle] | [X flows] [session] | [ext] [ws●] [db/mem] │└──────────────────────────────────────────────────────────────────┘The split between the flow list and inspector uses react-resizable-panels with draggable dividers. The panel orientation can be switched between horizontal (side-by-side, the default) and vertical (stacked top-bottom) through a layout toggle. Panel sizes are percentage-based — the flow list defaults to 55% with a minimum of 20%, and the inspector defaults to 45% with a minimum of 15%.
The left sidebar uses a fixed 240px CSS width (not the resizable panels library) and can be collapsed entirely via Ctrl+B / Cmd+B.
Initialization Sequence
Section titled “Initialization Sequence”When the app loads after setup is complete, it runs through a specific sequence of operations. Understanding this sequence helps explain why certain things appear before others and what happens if any step fails.
-
Register global keyboard shortcuts — sets up all
Ctrl+1throughCtrl+5shortcuts,Ctrl+B,Ctrl+P, etc. These are active immediately. -
Register native menu handler — connects to Tauri’s native macOS/Windows menu bar events (File > Export, Tools > Addons, etc.). This enables the operating system menu to control Ghost.
-
Initialize toast bridge — subscribes to error fields on 5 stores (flows, sessions, addons, proxy, settings). When any store sets an error, a toast notification appears automatically. Also watches proxy status changes to show “Proxy started on :PORT” and “Proxy stopped” toasts. Includes deduplication (5-second window, 100-entry max cache) and suppresses “Backend unavailable” messages.
-
Check setup status — calls the backend to verify the first-run wizard was completed. Until this returns, the app shows a loading spinner.
-
Fetch initial data (all in parallel after setup confirmed):
- Fetch all sessions from the database
- Fetch proxy status (running, stopped, port)
- Fetch all addons
- Fetch breakpoint rules
- Fetch map rules
- Fetch injection rules
- Fetch connected devices
-
Check for updates (5-second delay) — after a 5-second
setTimeout, checks the Tauri updater for a new version. If an update is available, shows a toast notification that stays visible for 15 seconds with a link to Settings > About. -
Initialize Sentry + telemetry — fetches settings from the backend. If a Sentry DSN is configured, initializes the Sentry SDK for error tracking (10% trace sample rate, production environment only, strips auth tokens from breadcrumb URLs). If telemetry is enabled, sends a
session_startheartbeat and registers abeforeunloadhandler to sendsession_end. -
Establish WebSocket connection — connects to the backend’s WebSocket endpoint for real-time event streaming. This is how the traffic list updates in real time.
-
Fetch flows — loads the initial set of flows based on the active session, filters, and scope selections.
-
Polling fallback — if the WebSocket disconnects, a
setIntervalpolls for new flows every 3 seconds until the WebSocket reconnects. This ensures the traffic list stays somewhat current even during connection interruptions.
WebSocket Connection
Section titled “WebSocket Connection”Ghost uses a single persistent WebSocket connection to the Go backend for all real-time updates. Every time a new flow is captured, a rule is changed, a device connects, or any other state change occurs, the backend broadcasts an event through this connection and the frontend updates immediately.
Connection URL
Section titled “Connection URL”The WebSocket URL is constructed from the backend’s HTTP URL:
- In Tauri (desktop app): converts the
http://base URL tows://and appends/ws?token=<encoded_token> - In browser (Go-served frontend): derives from
window.locationusingwss:for HTTPS orws:for HTTP
If no authentication token is available yet (can happen briefly during Tauri startup), the connection retries after 500ms.
Reconnection
Section titled “Reconnection”When the WebSocket connection drops (backend restart, network interruption), it automatically reconnects with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st | 1 second |
| 2nd | 2 seconds |
| 3rd | 4 seconds |
| 4th | 8 seconds |
| 5th | 16 seconds |
| 6th+ | 30 seconds (cap) |
On successful reconnection, the delay resets back to 1 second for the next potential disconnection. The connection status is tracked as one of three states: connecting, connected, or disconnected.
Event Types
Section titled “Event Types”38 distinct event types are handled, routed to the appropriate Zustand store:
| Category | Events | Target Store | What Happens |
|---|---|---|---|
| Flows | flow.created, flow.updated, flow.deleted | flowStore | New flows are checked against active filters before being added to the list. Deleted events can clear individual flows or all flows. Stats (host counts, app counts, device counts) are refreshed with a 2-second debounce after flow events. |
| Sessions | session.created, session.updated, session.deleted | sessionStore | Sessions are upserted (created or updated) in the session list. Deleted sessions are removed. |
| Proxy | proxy.started, proxy.stopped, proxy.status | proxyStore | Updates the proxy status indicator (green dot = running, gray = stopped). |
| Addons | addon.created, addon.updated, addon.deleted, addon.log | addonStore | Addon CRUD updates the addon list. Log events append to the addon console (used for ghost.log(), ghost.warn(), ghost.error() output from addon scripts). |
| Extension | extension.connected, extension.disconnected, extension.interaction, extension.tab_switch, extension.console_error, extension.navigation | extensionStore | Connection events update the extension status indicator. Interactions are mapped from camelCase (backend) to snake_case (frontend DTO). Console errors and navigation events are received but not currently surfaced in the UI. |
| Breakpoints | flow.breakpoint, flow.resumed | breakpointStore | Breakpoint events add pending flows to the breakpoint editor. Resume events remove them. |
| Rules | rule.created, rule.updated, rule.deleted, injection_rule.created, injection_rule.updated, injection_rule.deleted | rulesStore, injectionRulesStore | CRUD updates for map rules and injection rules. |
| Devices | device.discovered, device.updated, device.connected, device.disconnected, device.removed | deviceStore | All device events upsert the device in the store, except removed which deletes it. |
| Artifacts | artifact.created, artifact.deleted | artifactStore | Bug report and export artifact CRUD. |
| Findings | finding.created, finding.updated, finding.deleted | uiStore | Bumps a version counter that triggers re-fetches. For critical/high severity findings in security mode, shows a toast notification. |
| Frida | frida.session.started, frida.session.stopped, frida.output | fridaStore | Session events trigger a re-fetch of Frida sessions. Stop events show a retry toast if the user had an active operation. Output events append to the Frida console. |
| Attacker | attacker:started, attacker:progress, attacker:completed, attacker:error | attackerStore | Request attacker lifecycle events: baseline, progress updates, summary, and errors. |
Live Filter Matching
Section titled “Live Filter Matching”When a flow.created event arrives, it doesn’t automatically appear in the traffic list. The flow is first checked against all active filters using matchesClientFilter(). This function checks, in order:
- Method filter — case-insensitive match against the HTTP method
- Status code filter — supports exact codes (404), ranges (2xx, 3xx), and comparisons (>399, <500)
- Host filter — substring match from structured filter conditions
- Content type filter — substring match from structured filter conditions
- Domain navigator selection — exact match against the selected domain in the left sidebar
- Content type panel selection — substring match against the selected content category
- App navigator selection — exact match against the selected app’s
source_appfield - Device navigator selection — matches against
device_idorclient_ip - Free text search — case-insensitive substring search across
method,host,path, andurl
Only flows that pass ALL active filters are added to the visible list. This means the live traffic feed respects your current view — if you’re filtering to “status:>399”, you’ll only see error responses appear in real time.
Dual Mode Rendering
Section titled “Dual Mode Rendering”Ghost has two operational modes — QA and Security — that change which components are rendered in several zones of the interface. The mode is stored in useUIStore.appMode and toggled via a pill button pair in the command bar.
| UI Zone | QA Mode | Security Mode |
|---|---|---|
| Left sidebar | ScopePanel — domain navigator tree, app list, device list with selection for scoping the traffic view | FindingsPanel — security finding summary, target patterns, severity stats |
| Flow list | FlowList — standard traffic list with status/method/host/path columns | SecurityFlowList — traffic list with security-focused columns and finding indicators |
| Flow inspector | FlowInspector — request/response detail with headers, body, timing, notes | SecurityInspector — request/response detail with security finding annotations |
| Center view option | N/A | SecurityFindingsView — full-width findings table that replaces the flow list when selected |
| Command bar icons | Standard toolbar (compose, chat, rules, injection, addons, extension, compare, settings) | Standard toolbar + Request Attacker (crosshair icon) + Frida Instrumentation (syringe icon) |
When switching modes, the UI resets: slide-over panel closes, search query clears, all filters reset, all selections (hosts, apps, devices, content types) clear, inspector closes, Frida panel closes, sort state resets, flow selection clears, and the security center view resets. This clean slate ensures you start fresh in the new mode without leftover filter state from the previous mode.
Exclusive center panels: Five panels can replace the traffic view: Attack Planner, Session Compare, Inspector, Frida, and Security Findings. These are mutually exclusive via the single activePanel slot in the UI store — only one can be active at a time. Opening any panel automatically replaces whatever was there. Each panel gets a context-aware back button that navigates to the previous panel (or traffic).
Slide-Over Panels
Section titled “Slide-Over Panels”The right side of the screen can show a slide-over panel — an overlay that appears on top of the traffic view without replacing it. Only one slide-over is visible at a time. Opening a new panel replaces the current one.
| Panel ID | Title | What It Contains |
|---|---|---|
chat | AI Chat | AI agent conversation interface with streaming responses |
rules | Map Rules | Map rules editor — create, edit, enable/disable URL rewriting and response modification rules |
injection-rules | Injection Rules | JavaScript injection rules — manage scripts that get injected into web pages |
addons | Addons | Addon editor with Monaco code editor, template gallery, and real-time log console |
breakpoints | Breakpoint Rules | Breakpoint rule configuration — set conditions for pausing requests/responses |
composer | Request Composer | Manual HTTP request builder with headers, body, and response comparison |
bug-reports | Bug Reports | List of generated bug reports with detail view |
test-scenarios | Test Scenarios | Generated test scenario viewer |
security-tools | Security Tools | External security scanner management (install, enable, configure tools like sqlmap, nikto) |
attacker | Request Attacker | Request fuzzer configuration and results (security mode only) |
extension | Browser Extension | Browser extension connection status, captured interactions, and action controls |
Panel dimensions:
- Default width: 420px
- Minimum width: 320px
- Maximum width: 70% of viewport width
- Resizable via a drag handle on the left edge
- Header height: 44px (
h-11) - Positioned as an absolute overlay (
z-40) on the right side of the main content area
The panel includes scroll-pinning logic to prevent child components that call scrollIntoView() from shifting the panel’s scroll position unexpectedly.
Command Bar
Section titled “Command Bar”The command bar is the top strip of the application (38px tall) containing all primary navigation and action controls. It has a draggable region at the very top (for moving the Tauri window) and is padded on the left (pl-20, 80px) to avoid overlapping with macOS traffic light buttons (close/minimize/maximize).
Left-to-right contents:
- Logo — “GHOST” in gradient text (cyan → purple → pink)
- Session picker — dropdown showing the active session name with a caret. Click to see all sessions; option to create a new session.
- Mode toggle — two pill buttons: QA and Security. Switching resets all filters and selections.
- Proxy toggle — button showing proxy state: “Capturing” (with port), “Not Capturing”, or “Stopped”. Click to start/stop.
- Divider
- Search bar — magnifying glass icon, active filter chips (removable), text input field. Debounced at 600ms. Shows syntax hint dropdown when typing filter prefixes (
method:,status:,host:,content_type:,duration:,size:). - Divider
- Toolbar icons (left-to-right):
- Compose Request (paper plane icon)
- AI Chat (chat bubble icon)
- Map Rules (shuffle icon)
- Script Injection (code block icon)
- Addons (puzzle piece icon)
- Browser Extension (globe icon)
- Compare Sessions (git diff icon)
- Security mode only: Request Attacker (crosshair icon), Frida (syringe icon)
- Settings (gear icon)
- Divider
- Theme toggle (sun/moon icon)
Status Bar
Section titled “Status Bar”The status bar is the bottom strip (28px tall) with a subtle gradient accent line at the top (cyan → purple → pink, 30% opacity). It’s divided into three sections:
Left section:
- Proxy indicator — colored dot (green pulsing when running, amber pulsing when network is down, gray when stopped) with the port number. Clickable to toggle proxy.
- Flow rate — rolling 5-second window counter showing requests per second (e.g., “2.4/s”). Only visible when the proxy is running and the rate is above zero.
- Throttle dropdown — gauge icon that opens a dropdown with network throttling presets and custom speed/latency inputs.
Center section:
- Flow count — “X flows” with
aria-live="polite"for screen readers - Active filter badge — shows how many filters are active, with a click-to-clear button
- Active session name
Right section:
- Extension status — dot indicator and “Extension” label, only shown after the first extension connection. Clickable to open the extension panel.
- WebSocket status — green/amber/red dot with “Live”, “Connecting…”, or “Disconnected” label
- Resource monitor — database size and memory allocation, polled every 30 seconds. Only visible on screens wider than the
smbreakpoint. - Ctrl+K hint — small button that dispatches a keyboard event to open the command palette
Command Palette
Section titled “Command Palette”Opened with Ctrl+K / Cmd+K. A search-powered command launcher that fuzzy-matches against available commands. Navigate with arrow keys, execute with Enter, close with Escape.
Available commands:
| Command | Action | Shortcut Displayed |
|---|---|---|
| Open AI Chat | Opens the chat slide-over panel | Ctrl+4 |
| Open Map Rules | Opens the map rules slide-over panel | Ctrl+3 |
| Open Addons | Opens the addons slide-over panel | Ctrl+5 |
| Open Breakpoints | Opens the breakpoint rules panel | — |
| Close Panel | Closes any open slide-over panel | — |
| Open Settings | Opens the settings modal | Ctrl+, |
| Start/Stop Proxy | Toggles the proxy (label changes based on state) | Ctrl+P |
| Toggle Scope Panel | Shows/hides the left sidebar | — |
| Switch to Light/Dark Mode | Toggles the theme | — |
| Clear All Flows | Deletes all flows in the active session | — |
| New Session | Creates a new session with a timestamp name | — |
Global Keyboard Shortcuts
Section titled “Global Keyboard Shortcuts”All shortcuts are suppressed when focus is inside an INPUT, TEXTAREA, SELECT, or contentEditable element (except Ctrl+F which always works).
| Shortcut | Action |
|---|---|
Ctrl+1 / Cmd+1 | Close slide-over panel, focus on traffic |
Ctrl+3 / Cmd+3 | Open Map Rules panel |
Ctrl+4 / Cmd+4 | Open AI Chat panel |
Ctrl+5 / Cmd+5 | Open Addons panel |
Ctrl+B / Cmd+B | Toggle scope panel (left sidebar) |
Ctrl+, / Cmd+, | Open Settings modal |
Ctrl+P / Cmd+P | Toggle proxy start/stop (only if Shift is not held) |
Ctrl+K / Cmd+K | Toggle command palette |
Ctrl+F / Cmd+F | Focus and select the search input |
Ctrl+Shift+K / Cmd+Shift+K | Toggle session comparison view |
/ | Focus search input (when not in an input field) |
Escape | Close the currently open panel, palette, or overlay |
Note: there is no Ctrl+2 shortcut — the sequence skips from 1 to 3.
Native Menu Integration
Section titled “Native Menu Integration”On macOS (and to a lesser extent Windows), Ghost registers handlers for Tauri’s native menu bar. This means actions like “File > Export as HAR” or “Tools > Addons” work through the operating system’s menu system, not just through the in-app toolbar.
Menu actions handled:
| Menu Category | Actions |
|---|---|
| File | New Session, Export (HAR/JSON/CSV/Postman), Import HAR/JSON, Clear Flows |
| View | Composer, AI Chat, Map Rules, Addons, Breakpoints, Toggle Theme |
| Tools | Addons, Breakpoints |
| Testing | Bug Reports, Test Scenarios, Generate Bug Report |
| Proxy | Toggle Proxy |
| Certificates | Install CA (macOS), iOS Device/Simulator setup, Android Device/Emulator setup, Export CA, Cert Status |
| Help | About Ghost |
Setup Wizard
Section titled “Setup Wizard”The first time Ghost launches, it shows a setup wizard instead of the main application. The wizard guides the user through two essential steps:
- Install Root Certificate — installs Ghost’s CA certificate so it can decrypt HTTPS traffic
- Enable System Proxy — configures the operating system to route traffic through Ghost
The wizard offers two paths:
- “Set Up Ghost” — runs both steps automatically. If the certificate installation fails, it’s marked as skipped but setup continues (the user gets a warning that HTTPS traffic won’t be decrypted). The proxy is then enabled via the
completeSetupAPI withproxy_method: 'system_proxy'. - “Skip — I’ll configure manually in Settings” — skips the wizard entirely and lets the user configure everything themselves later.
Once complete, clicking “Launch Ghost” transitions to the main application.
Drop Zone
Section titled “Drop Zone”The entire main content area is wrapped in a DropZone component that accepts file drops. When you drag a .har or .json file over the app, a translucent overlay appears with an upload icon, “Drop to import session”, and “Supports .har and .json files.”
On drop:
- Creates a new session named
<filename> (imported) - Probes the first 1KB of the file to detect if it’s a HAR file (looks for
"log"and"entries"keys in the JSON structure) - Imports via the appropriate API endpoint (HAR import or JSON import)
- Shows a success toast with the import count and a “Compare” action button that opens session comparison
The maximum import file size is 256MB.
Filter Strip
Section titled “Filter Strip”The filter strip sits between the command bar and the flow list, providing quick access to common filters and a visual query builder.
Row 1 — Quick access (always visible):
Smart presets (one-click filters):
- API Only — shows only JSON/XML/GraphQL/protobuf responses (
content_type:json) - Errors — shows only error responses (
status:>399) - Slow — shows only slow responses (
duration:>1000) - WebSocket — shows only WebSocket connections (
has:websocket) - Has Interaction — shows flows with browser interactions (
has:interaction)
Content type pills (dynamically populated from current traffic): All, API, Pages, JS, CSS, Images, Fonts, Media, Other
Right-side controls: active filter count badge with clear button, save preset button, filter builder toggle.
Row 2 — Filter Builder (toggleable):
A visual query builder with condition rows. Each row has:
- Field selector: Method, Status, Host, Path, Content Type, Duration (ms), Size (bytes), Tag
- Operator selector (varies by field):
is,:,:>,:<,:>=,:<= - Value input: enum dropdown for Method and Content Type, text or number input for others
- Remove button
Saved filter presets are persisted to localStorage under the key ghost-filter-presets.
Error Handling
Section titled “Error Handling”Error Boundary: The entire application is wrapped in a React error boundary (class component). When an unhandled exception occurs:
- The error is logged to the console with the component stack trace
- The error is sent to Sentry via
captureError(if Sentry is configured) - A crash recovery screen appears with the Ghost logo (30% opacity), “Something went wrong”, the error message in a
<pre>block, and a “Reload” button that callswindow.location.reload()
Toast notifications: API errors from store actions are automatically surfaced as toast notifications via the toast bridge. The bridge subscribes to the .error field on 5 stores and fires toast.error() when errors appear. Deduplication prevents the same error from spamming multiple toasts (5-second cooldown, 100-entry dedup cache).
Sentry: In production, Sentry captures unhandled exceptions with:
- Release tagged as
ghost@<version> - 10% trace sampling rate
- Browser tracing integration for performance monitoring
- Token stripping in breadcrumbs (removes
token=from URLs before sending to Sentry) - Skipped entirely in development mode
Persistence
Section titled “Persistence”The following UI state is persisted to localStorage across sessions:
| Key | What It Stores |
|---|---|
ghost-app-mode | QA or Security mode selection |
ghost-theme | Dark or Light theme |
ghost-pinned-hosts | Pinned domains in the scope panel |
ghost-pinned-apps | Pinned apps in the scope panel |
ghost-pinned-devices | Pinned devices in the scope panel |
ghost-detail-layout | Inspector layout (stacked or side-by-side) |
ghost-panel-layout | Panel orientation (horizontal or vertical) |
ghost-filter-presets | Saved filter presets |
ghost-security-mode | Security scan mode (passive/active-safe/active-full) |
ghost-scan-mode | Alternative scan mode key |
ghost-security-sidebar-tab | Active tab in security sidebar (traffic/devices/findings) |