Skip to content

Config File Reference

Ghost stores all its settings in a single configuration file written in TOML format — a simple, human-readable format that looks similar to INI files but supports nested sections and typed values. Think of it as Ghost’s “preferences file” — everything from which port the proxy listens on, to your AI provider API key, to whether noise detection is enabled, lives in this one file.

The file is created automatically on first launch with sensible defaults. You can edit it manually with any text editor, but most users change settings through Ghost’s Settings UI, which reads and writes this file through the REST API.


Ghost keeps all its data in a single directory at ~/.ghost/ (your home folder, inside a hidden .ghost folder).

FilePermissionsDescription
~/.ghost/0700 (owner only)The Ghost data directory. Created automatically on first launch. Only your user account can read or enter this directory.
~/.ghost/config.toml0600 (owner read/write only)The main configuration file. Contains encrypted API keys, so it’s restricted to prevent other users on the machine from reading your secrets.
~/.ghost/ca.crt0644 (world readable)The CA certificate that Ghost uses for HTTPS interception. Needs to be readable because it’s installed into browser/OS trust stores.
~/.ghost/ca.key0600 (owner only)The CA private key. This is the most sensitive file — anyone with this key could generate certificates that your machine trusts.
~/.ghost/ghost.dbCreated by SQLiteThe SQLite database containing all captured flows, sessions, findings, conversations, and other persistent data.
~/.ghost/workspaces/<session-id>/0755Per-session workspace directories created for agent file output, containing subdirectories for findings/, scans/, and poc/.

When Ghost saves the config file, it never writes directly to config.toml. Instead, it uses a crash-safe atomic write pattern:

  1. Create a temporary file in the same directory (e.g., .config-abc123.tmp)
  2. Write the TOML-encoded config to the temporary file
  3. Close the temporary file
  4. Set permissions to 0600 on the temporary file
  5. Atomically rename the temporary file to config.toml

If any step fails (disk full, power loss, crash), the original config.toml remains intact. The temporary file is cleaned up on failure. This prevents the nightmare scenario where a half-written config file corrupts your settings.


Controls how the MITM (Man-in-the-Middle) proxy intercepts and processes HTTP/HTTPS traffic.

KeyTypeDefaultDescription
portint4545The TCP port the proxy listens on. Browsers and devices are configured to send their traffic to this port. Changing this requires a restart — it cannot be hot-reloaded.
cert_cache_sizeint10000How many dynamically-generated leaf certificates to keep in memory. Each HTTPS domain Ghost intercepts needs its own certificate, and generating them takes CPU time. The LRU (Least Recently Used) cache keeps the most recently used certificates ready. 10,000 covers most usage patterns. Requires restart to change.
host_includestring[][] (empty = capture all)A whitelist of host patterns. When set, Ghost ONLY captures traffic to hosts matching these patterns. An empty list means “capture everything.” Patterns use glob syntax (e.g., *.example.com matches api.example.com). Hot-reloadable.
host_excludestring[]134 default patternsA blacklist of host patterns to ignore. Traffic to these hosts passes through without being captured or displayed. The defaults cover Windows telemetry, browser telemetry, certificate/OCSP checks, connectivity probes, macOS noise, Edge/Bing, Google services, social media, streaming/media, analytics/ads, CDN infrastructure, and Microsoft services. Hot-reloadable.
upstream_proxystring"" (direct)URL of an upstream proxy to chain through. When set, Ghost forwards traffic through this proxy instead of connecting directly to the internet. Useful in corporate environments with mandatory proxies. Format: http://proxy.corp.com:8080.
ssl_bypass_hostsstring[][] (none)Hosts where Ghost should NOT perform HTTPS interception. Traffic to these hosts is tunneled through as-is, without decryption. Use this for apps with certificate pinning (like banking apps) that would break if Ghost intercepted their HTTPS. Supports wildcard suffix matching (e.g., *.apple.com).
noise_enabledbool*true (when nil)Master switch for noise detection. When enabled, Ghost automatically identifies and suppresses repetitive background traffic (like health checks, polling requests) that clutter the flow list. This is a pointer type — nil in TOML means “use default” which is true.
noise_thresholdint20 (when 0)How many requests from the same pattern within the window before Ghost classifies it as noise. A “pattern” is defined by the combination of HTTP method, host, and URL path. Zero means “use default.” Hot-reloadable.
noise_sample_rateint10 (when 0)Once a pattern is classified as noisy, Ghost keeps 1 out of every N requests (e.g., 10 means keep 10%, discard 90%). This gives you visibility into noisy patterns without flooding the flow list. Zero means “use default.” Hot-reloadable.
noise_window_secsint30 (when 0)The sliding window duration in seconds. Ghost counts how many times a pattern appears within this window to decide if it’s noisy. Zero means “use default.” Hot-reloadable.

Controls the HTTP server that the frontend (and any API clients) connect to.

KeyTypeDefaultDescription
portint5565The TCP port the REST API and WebSocket server listen on. The Ghost frontend connects to this port. Changing this requires a restart.
bearer_tokenstringAuto-generatedThe authentication token for all API access. If empty on first launch, Ghost generates a cryptographically random token (32 random bytes encoded as a 64-character hexadecimal string). This token is never exposed through the Settings API (json:"-" tag excludes it from JSON serialization) — the frontend receives it via the Tauri sidecar’s startup JSON output. Encrypted at rest in the TOML file using the same AES-256-GCM encryption as API keys.

Controls how Ghost stores captured traffic data in its SQLite database.

KeyTypeDefaultDescription
max_flow_body_sizeint10485760 (10 MB)Maximum size of request/response bodies that Ghost stores inline in the database, in bytes. Bodies larger than this are still captured and forwarded to the browser, but they aren’t saved in the database. This prevents a single large file download from bloating the database. Requires restart to change.
auto_purge_enabledboolfalseWhen enabled, Ghost automatically cleans up old flows on a schedule (every 5 minutes). This keeps the database from growing unbounded during long-running sessions.
auto_purge_max_age_hoursint0 (disabled)Maximum age of flows in hours. When auto-purge runs, any flows older than this are deleted. Set to 0 to disable age-based purging. Only takes effect when auto_purge_enabled is true.
auto_purge_max_flowsint0 (disabled)Maximum total number of flows to keep. When auto-purge runs, the oldest flows beyond this count are deleted. Set to 0 to disable count-based purging. Only takes effect when auto_purge_enabled is true.

Configures which AI model provider Ghost uses for its agent features (test generation, security analysis, traffic understanding, bug reports).

KeyTypeDefaultDescription
providerstring"anthropic"Which AI provider to use. Options: "anthropic" (Claude models), "openai" (GPT models), "ollama" (local models running on your machine).
api_keystring"" (none)Your API key for the selected cloud provider. Encrypted at rest using AES-256-GCM with Argon2id key derivation (see Encryption section below). Tagged json:"-" to exclude from JSON API responses. Not needed for Ollama since it runs locally. When displayed in the Settings UI, only the last 4 characters are shown (e.g., ****ab12).
modelstring"" (provider default)The specific model to use. When empty, Ghost uses the provider’s default: Claude Sonnet 4.6 for Anthropic, GPT-4o for OpenAI, llama3.2 for Ollama.
ollama_endpointstring"http://localhost:11434"The URL of your Ollama server. Only relevant when provider is "ollama". The default points to a locally running Ollama instance on its standard port.
KeyTypeDefaultDescription
levelstring"info"Controls how verbose Ghost’s log output is. Options from most to least verbose: "debug" (everything, including individual proxy operations), "info" (normal operations), "warn" (potential problems), "error" (only failures). Requires restart to change.
KeyTypeDefaultDescription
completedboolfalseWhether the user has completed the first-run setup wizard. When false, Ghost shows the setup wizard instead of the main interface. Set to true automatically when the user finishes setup.
proxy_methodstring"" (not chosen)The connection method the user selected during setup: "system_proxy" (Ghost configures the OS to route all traffic through it), "manual" (user configures their browser or device manually), or "device" (user is primarily testing mobile devices).
KeyTypeDefaultDescription
target_hostsstring[][] (scan nothing)Glob patterns defining which hosts the security interceptor should analyze. When empty, passive security scanning is disabled. Example: ["*.mydomain.com", "api.example.com"] would scan all mydomain.com subdomains and the specific api.example.com host. Hot-reloadable.
disabled_toolsstring[][] (all enabled)Names of security tools the user has toggled off. Ghost’s 8 known security tools (Frida, Nuclei, Dalfox, ffuf, sqlmap, TruffleHog, Katana, Semgrep) are all enabled by default. Adding a tool name here prevents the agent from using it. Hot-reloadable via the Security Tools management UI.

[telemetry] — Error Reporting and Usage Tracking

Section titled “[telemetry] — Error Reporting and Usage Tracking”
KeyTypeDefaultDescription
sentry_dsnstring"" (disabled)Sentry DSN (Data Source Name) for crash and error reporting. When set, unhandled errors are reported to Sentry for debugging. Tagged json:"-" so it is excluded from API JSON responses and settings exports — never shared when you export your config.
telemetry_enabledbool*true (when nil)Whether anonymous usage telemetry is enabled. Ghost sends periodic heartbeats with basic usage statistics (no traffic content, no personal data). This is a pointer type — nil in TOML means “use default” which is true.
telemetry_endpointstring"" (disabled)URL endpoint for telemetry heartbeats. Must be a valid HTTP or HTTPS URL. When empty, heartbeats are not sent regardless of the telemetry_enabled setting.
KeyTypeDefaultDescription
urlstring"" (not configured)URL of your TestRail instance (e.g., https://yourcompany.testrail.io). Must be HTTP or HTTPS. Validated against SSRF (Server-Side Request Forgery) — Ghost refuses to connect to private/local network addresses to prevent the server from being used to scan internal networks.
emailstring""The email address associated with your TestRail account. Used for API authentication.
api_keystring""Your TestRail API key. Encrypted at rest using AES-256-GCM, same as the LLM API key. Masked in the Settings UI (last 4 characters visible). Never included in config exports or imports.

Ghost uses a dual persistence model for settings:

  • SQLite (source of truth) — all runtime settings are stored as JSON blobs in the settings table (key TEXT PRIMARY KEY, value TEXT NOT NULL). Each setting category (llm, proxy, security, telemetry, logging, store, testrail) is stored under its own key.
  • TOML (backward compatibility) — config.toml is still written on every settings change, but SQLite takes precedence on startup.

Startup sequence: TOML is loaded first (bootstrap defaults), then SQLite values overlay on top. This means if you set your API key through the UI, it’s saved to SQLite and survives restarts regardless of what’s in the TOML file.

Why both? The original TOML-only approach used AES-256-GCM encryption with a machine-derived key for API keys. On macOS, os.Hostname() can return different values depending on network context (Wi-Fi name, VPN connection), which caused the encryption key to change between sessions — silently clearing API keys on restart. SQLite storage avoids this entirely by storing API keys as plaintext (the database already contains captured HTTP traffic with auth headers, so it’s the same security level).

Ghost encrypts sensitive values in the TOML file (LLM API keys, TestRail API keys, and the bearer token) at rest using AES-256-GCM — an industry-standard authenticated encryption algorithm. Note: This encryption only applies to values in config.toml. The SQLite settings table stores API keys as plaintext, since the database already contains sensitive traffic data at the same security level.

Ghost has evolved through three encryption versions, each improving key derivation security:

VersionPrefixKey DerivationNotes
v1enc:SHA-256 of machine identity stringOriginal. Fast but no salt — identical machines produce identical keys.
v2enc2:PBKDF2-HMAC-SHA256 (600K iterations) with random 16-byte saltAdded per-value random salt, OWASP-recommended iteration count.
v3enc3:Argon2id (1 iteration, 64 MiB memory, 4 threads) with random 16-byte saltCurrent. Memory-hard KDF that resists GPU/ASIC attacks. OWASP-recommended for interactive use.

All versions use AES-256-GCM for the actual encryption. The version prefix tells Ghost which KDF to use for decryption.

The encryption key is derived from machine-specific values using Argon2id, making the encrypted config file non-portable between machines:

Machine identity inputs:

ComponentSourceFallback
hostnameos.Hostname() — your computer’s network name"unknown-host" if the hostname can’t be determined
osruntime.GOOS — the operating system (e.g., "darwin", "windows", "linux")No fallback (always available)
homediros.UserHomeDir() — your home directory path"unknown-home" if the home directory can’t be determined
SaltRandom 16 bytes (generated per encrypted value)N/A

Argon2id parameters:

ParameterValue
Time (iterations)1
Memory64 MiB
Threads4
Key length32 bytes (256 bits)

Safety check: If both hostname AND home directory fail to resolve (both return fallbacks), the key derivation returns an error rather than producing a universal key that would be the same on every machine. At least one machine-specific component must be available.

When Ghost saves an API key:

  1. If the key is empty, store it as empty (nothing to encrypt)
  2. Generate a random 16-byte salt
  3. Derive a 32-byte encryption key using Argon2id with the salt and machine identity
  4. Create an AES-256 cipher block
  5. Create a GCM (Galois/Counter Mode) wrapper — this provides both encryption AND integrity verification
  6. Generate a random nonce using crypto/rand — this ensures encrypting the same key twice produces different ciphertext
  7. Encrypt the key and prepend the salt + nonce to the ciphertext
  8. Base64-encode the combined salt+nonce+ciphertext
  9. Store as "enc3:" + the base64 string

When Ghost loads the config:

  1. If the value is empty, return it as-is
  2. Detect the version prefix (enc3:, enc2:, or enc:) to select the correct KDF
  3. Strip the prefix and base64-decode the remaining string
  4. Extract the salt (v2/v3 only) and nonce from the decoded data
  5. Derive the encryption key using the appropriate KDF
  6. Decrypt using AES-256-GCM — this also verifies integrity (detects tampering)
  7. If decryption fails (wrong machine, corrupted data): the key is silently cleared to empty string

Ghost automatically migrates encrypted values through the version chain on load:

  • Plaintext (no prefix) → encrypted and saved as v3
  • v1 (enc: prefix) → decrypted, re-encrypted as v3, saved
  • v2 (enc2: prefix) → decrypted, re-encrypted as v3, saved
  • v3 (enc3: prefix) → used directly, no migration needed

Migration happens transparently on config load. If the migration save fails, Ghost logs a warning but continues running — the key works fine in memory, it just won’t be upgraded on disk until the next successful save.

What Happens When You Move Configs Between Machines

Section titled “What Happens When You Move Configs Between Machines”

If you copy config.toml to a different computer (different hostname or home directory), the encryption key will be different and the encrypted API keys cannot be decrypted. Ghost handles this gracefully:

  • The API keys are silently cleared to empty strings
  • Ghost continues running normally — everything works except AI features
  • You need to re-enter your API keys in the Settings UI
  • The keys are then encrypted with the new machine’s identity

When you change settings through the Settings UI (or via PUT /api/v1/settings), some settings take effect immediately without restarting Ghost, while others require a restart:

Hot-reloaded immediately (5 subsystems):

SubsystemWhat gets updated
Security interceptorTarget host patterns — which domains get passive security scanning
Proxy host filtersInclude/exclude patterns — which traffic gets captured vs ignored
Noise detectorThreshold, sample rate, window duration — noise detection sensitivity. Existing pattern state (which patterns are currently classified as noisy) is preserved across config changes.
LLM agentProvider, API key, model — creates a new AI provider instance with updated credentials
TestRail clientURL, email, API key — swaps the TestRail HTTP client under a mutex

Requires restart to change:

SettingWhy
proxy.portThe TCP listener is bound once at startup
api.portThe HTTP server listener is bound once at startup
proxy.cert_cache_sizeThe LRU cache is allocated once at startup
store.max_flow_body_sizeUsed during SQLite store initialization
logging.levelThe logger is configured once at startup
api.bearer_tokenOnly changed via the dedicated token rotation endpoint, not the general settings update

The Config struct has a Validate() method that performs structural validation on the configuration. This is called during Update() before saving, catching invalid values (like negative port numbers or malformed URLs) before they’re persisted.

The config is managed by a Manager struct that uses a read-write mutex (sync.RWMutex) for thread safety:

  • Reading (Get()): Acquires a read lock and returns a deep copy of the current config — slice fields (HostInclude, HostExclude, SSLBypassHosts, TargetHosts, DisabledTools) are cloned to decouple backing arrays, preventing callers from accidentally mutating shared state.
  • Writing (Update()): Deep-copies slice fields to decouple backing arrays from the caller, applies defaults to fill any zero-valued fields, validates the config, saves to disk atomically, then acquires a write lock and swaps the config pointer. Until a reader calls Get() again, they continue seeing the old config — this is safe because Go’s garbage collector keeps the old config alive as long as anyone holds a reference.

The API authentication token has its own dedicated rotation mechanism, separate from the general settings update:

  1. POST /api/v1/settings/token/rotate generates a new 32-byte random token
  2. The new token is stored in an atomic.Value (lock-free) for the fastest possible authentication checks
  3. The new token is persisted to config.toml
  4. If the disk write fails, the in-memory token is reverted to the old value — this prevents a situation where the frontend has a new token but it’s lost on restart

[proxy]
port = 4545
cert_cache_size = 10000
host_exclude = ["*.google.com", "*.googleapis.com", "*.gstatic.com"]
ssl_bypass_hosts = ["*.apple.com"]
noise_enabled = true
noise_threshold = 20
[api]
port = 5565
[store]
max_flow_body_size = 10485760
auto_purge_enabled = false
[llm]
provider = "anthropic"
api_key = "enc:dGhpcyBpcyBhIGJhc2U2NC..."
model = "claude-sonnet-4-20250514"
[security]
target_hosts = ["*.mydomain.com", "api.example.com"]
[logging]
level = "info"
[telemetry]
telemetry_enabled = true
[testrail]
url = "https://yourcompany.testrail.io"
email = "qa@company.com"
api_key = "enc:YW5vdGhlciBiYXNlNjQgZXhhbXBsZQ..."

When you export your settings (GET /api/v1/settings/export), three sensitive fields are explicitly cleared before the file is generated:

  • llm.api_key — removed entirely
  • testrail.api_key — removed entirely
  • telemetry.sentry_dsn — removed entirely

When you import settings (POST /api/v1/settings/import), Ghost explicitly ignores the llm.api_key and testrail.api_key fields from the imported file. This means importing a config file from another user or machine will never overwrite your API keys — you always need to enter them manually through the Settings UI.