Ghostly is built around a single guiding principle: your code, test steps, and credentials never leave your machine unless you explicitly configure them to. The engine runs entirely as a local process on the developer’s host, persists all data in a SQLite file in your home directory, and authenticates every internal request without phoning home to any Ghostly-operated server. This page documents how each layer of that security model works and what you need to do to keep it intact.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Meza-dev/Ghostly/llms.txt
Use this file to discover all available pages before exploring further.
Core Security Properties
Local Key Generation
API keys are generated on the developer’s machine by
ghostly keygen using Node’s crypto.randomUUID() or randomBytes(32).toString('hex'). No key material is ever generated, stored, or transmitted by a remote server.No External Data Transmission
Source code, test goals, run artifacts, screenshots, and traces are stored only in
~/.ghostly/ and never sent to Ghostly servers. The only optional outbound traffic is to your configured LLM endpoint.Auth File Isolation
All credentials live in
~/.ghostly/auth.json. The CLI writes this file with mode: 0o600 (user-read-only). Verify permissions with ls -la ~/.ghostly/auth.json and correct them with chmod 600 ~/.ghostly/auth.json if needed.Compliance Ready
Because no code or test data leaves the local machine, Ghostly is suitable for environments with strict data-residency, air-gap, or source-code-confidentiality requirements.
API Authentication
The Ghostly API enforces authentication on every route except/v1/auth/. Two authentication methods are accepted, checked in order by the middleware stack:
Outer gate — API Key middleware
TheapiKeyMiddleware runs first on all routes. It short-circuits (passes through) under two conditions: the path starts with /v1/auth/, or an Authorization: Bearer header (or ?token= query parameter) is present. For all other requests, it reads the expected key directly from ~/.ghostly/auth.json and compares it to the incoming X-Api-Key header. Requests that provide neither a Bearer token nor a valid API key are rejected with 401 before reaching any route handler.
1 — JWT Bearer Tokens
Used by the web dashboard and browser clients. A JWT is issued byPOST /v1/auth/login and must be sent in the Authorization: Bearer <token> header (or as a ?token= query parameter for SSE/EventSource connections that cannot set headers).
Tokens are HMAC-SHA256 signed using the JWT_SECRET environment variable. The default value is "ghostly-secret" — a placeholder that must be replaced in any deployment beyond your own laptop. Tokens expire after 7 days; the middleware additionally validates that the token’s subject (sub) still exists in the database, guarding against stale sessions after a database reset. Signature verification uses timingSafeEqual to prevent timing side-channel attacks.
2 — API Key (X-Api-Key Header)
Used by the CLI and the MCP server. The authMiddleware also accepts an X-Api-Key header as a fallback after JWT. It performs a database lookup against the ApiKey table (managed via the dashboard Settings page) and sets the authenticated user context from the matching record.
This is distinct from the outer apiKeyMiddleware (which validates against ~/.ghostly/auth.json): the outer gate checks the single installation key at the file-system level, while the inner auth middleware supports multiple per-user API keys stored in the database.
Password Hashing
User passwords are hashed with PBKDF2-SHA512 using a random 16-byte salt and 100,000 iterations, producing a 64-byte derived key. The stored format is<hex-salt>:<hex-hash>. Password verification uses timingSafeEqual to prevent timing attacks.
Auth File: ~/.ghostly/auth.json
The auth file is the single source of truth for the local installation. It contains:
apiKey— theGHOST_API_KEYvalue used by CLI and MCPapiUrl— theGHOST_API_URLbase URLllm— LLM provider, model, API key, and base URLextraEnv— any additional environment variables to inject into the API process
mode: 0o600 (owner read/write only). If you copy or restore this file, re-apply the permissions manually.
LLM Security
Read-only mode for Cursor Agent CLI
When using the
cursor-cli provider, Ghostly invokes the agent with --mode ask. This flag restricts the agent to read-only operations — it cannot write files or execute shell commands. Never use --force or --yolo flags with the agent in any Ghostly context.API keys stay local
LLM provider API keys (e.g., OpenAI, Anthropic, OpenRouter) are stored in
~/.ghostly/auth.json and injected into the API process environment at startup. They are never sent to Ghostly servers or logged in run artifacts.Secrets are not included in prompts
Ghostly does not include your API keys, file paths, or other credentials in the natural-language prompts it sends to the LLM. Assisted metadata is redacted before being persisted to the database.
Shell injection prevention
When spawning the Cursor Agent CLI, Ghostly uses
spawn without shell: true and delivers prompts via stdin (written to a temporary file). This prevents shell injection attacks even if a test goal contains characters that would be interpreted by a shell.MCP Security
The MCP server exposes Ghostly tools (ghostly_run_flow, project map, submit plan) to your IDE. Its configuration is written to ~/.cursor/mcp.json by ghostly install.
~/.cursor/mcp.json stores the Ghostly API key in plaintext as an environment variable passed to the MCP server process. Do not commit this file to version control. Add it to your global .gitignore or ensure your project’s .gitignore covers ~/.cursor/.X-Api-Key header. It reads the key from the X_API_KEY environment variable, which is set by the mcp.json entry written during ghostly install.
Default Credentials
ghostly up seeds a fresh database with a default admin account:
| Field | Value |
|---|---|
admin@ghostly.local | |
| Password | admin123 |
Production Hardening Checklist
Click to expand the full hardening checklist
Click to expand the full hardening checklist
Authentication & Secrets
- Change the default admin password (
admin@ghostly.local/admin123) via the dashboard Settings page after first login. - Set a strong, randomly generated
JWT_SECRETenvironment variable before exposing the API to any network beyond127.0.0.1. A 32-byte hex string fromopenssl rand -hex 32is a safe choice. - Rotate the Ghostly API key after any suspected compromise: run
ghostly keygenand thenghostly upto restart the server with the new key.
- Confirm
~/.ghostly/auth.jsonhas permissions600:chmod 600 ~/.ghostly/auth.json. - Confirm
~/.cursor/mcp.jsonis not tracked in any git repository. Add it to~/.gitignore_globalor your project’s.gitignore.
- Keep
HOSTset to127.0.0.1(enforced byghostly up). If launching the API directly without the CLI, setHOST=127.0.0.1explicitly — the env var default is0.0.0.0. - If Ghostly must be accessible over a network (e.g., from a VM or Docker host), place it behind a reverse proxy with TLS and restrict access to known IPs.
- If using the
cursor-cliprovider, verify the agent is invoked with--mode ask(Ghostly enforces this, but confirm it in your logs if you have a custom deployment). - If using an
httpLLM provider, prefer endpoints you control (Ollama locally, Azure OpenAI with VNet) over public cloud APIs for maximum data-residency assurance. - Never paste LLM provider API keys into test goal strings — they would be sent to the LLM in plaintext.
- Review
~/.ghostly/auth.jsonand~/.cursor/mcp.jsonas part of any regular secrets rotation schedule. - For air-gapped environments, configure
ASSIST_LLM_PROVIDER=cursor-cliwith a locally available model, or setASSIST_ENABLED=falseto disable AI assistance entirely.
Threat Model Summary
| Threat | Mitigation |
|---|---|
| Forged JWT tokens | timingSafeEqual comparison in verifyToken; strong JWT_SECRET required in production |
| Stolen API key | Installation key validated against ~/.ghostly/auth.json at request time; per-user DB keys also supported; rotate with ghostly keygen |
| Weak password storage | PBKDF2-SHA512, 100,000 iterations, random 16-byte salt; timingSafeEqual on verification |
| LLM prompt injection | Prompts delivered via stdin/temp file with spawn (no shell); --mode ask prevents file writes |
| Credential exfiltration via LLM | Secrets not included in prompts; metadata redacted before DB persistence |
| Unauthorized network access | ghostly up binds to 127.0.0.1; env var default is 0.0.0.0 — set explicitly for direct launches |
| Default credential abuse | Seed credentials are documented and must be rotated post-install |