Security & Attacker
The security API covers four interconnected systems: findings (security vulnerabilities detected by Ghost’s passive interceptor and AI agent), security tools (external scanners like Nuclei, SQLMap, and Frida that extend Ghost’s capabilities), Frida (runtime instrumentation for bypassing SSL pinning, root detection, and custom hooking), and the request attacker (a fuzzer that tests endpoints by sending modified payloads and analyzing responses).
These systems work together — the AI agent uses the security tools to scan, the findings store collects results from both passive detection and active testing, and the attacker lets you manually fuzz specific parameters to validate suspected vulnerabilities.
Security Findings
Section titled “Security Findings”Findings represent security issues detected in your traffic. They can come from three sources:
- Passive detection — Ghost’s security interceptor automatically scans every response for common issues (exposed credentials, missing security headers, information disclosure)
- AI agent analysis — the agent uses tools to identify more complex issues (broken authentication, IDOR vulnerabilities, injection points)
- Active testing — scanners like Nuclei and SQLMap report findings back through the agent
There is no POST endpoint for creating findings — they are created exclusively by the security interceptor and agent tools internally. The API lets you list, inspect, update the status of, and delete findings.
List Findings
Section titled “List Findings”GET /api/v1/findings?session_id=01HWXYZ...Returns findings for a session with optional filtering.
Query parameters:
| Parameter | Required | Values | Description |
|---|---|---|---|
session_id | Yes | ULID string | Which session’s findings to list |
type | No | auth, injection, exposure, config, crypto, access, session | Filter by vulnerability category |
severity | No | critical, high, medium, low, info | Filter by severity level |
status | No | open, confirmed, false_positive, fixed | Filter by review status |
flow_id | No | ULID string | Show only findings related to a specific flow |
limit | No | 1–500 (default: all) | Maximum findings to return. Capped at 500 regardless of the value you send |
offset | No | ≥ 0 | Skip this many findings for pagination |
Response:
[ { "id": "01HWXYZ...", "session_id": "01HWABC...", "flow_id": "01HWDEF...", "type": "exposure", "severity": "high", "confidence": 0.85, "title": "API Key Exposed in Response Body", "description": "The response body contains what appears to be an API key in plain text...", "evidence": "Found pattern: sk_live_abc123... in response body", "remediation": "Remove API keys from response bodies. Use server-side session tokens instead.", "cwe_id": "CWE-200", "owasp_id": "A01:2021", "status": "open", "source": "passive", "metadata": { "host": "api.example.com", "path": "/api/config" }, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T10:30:00Z" }]Finding fields explained:
| Field | Type | Description |
|---|---|---|
id | string | Unique finding identifier (ULID) |
session_id | string | Which session this finding belongs to |
flow_id | string | The specific HTTP flow where this issue was detected (may be empty for session-level findings) |
type | string | Vulnerability category — see type values below |
severity | string | How serious this issue is — critical (actively exploitable), high (likely exploitable), medium (potential risk), low (minor concern), info (informational) |
confidence | number | How confident Ghost is in this finding (0.0 to 1.0). Passive detections use pattern matching and may have lower confidence; active testing with confirmed exploitation has higher confidence |
title | string | Short summary of the issue |
description | string | Detailed explanation of what was found and why it matters |
evidence | string | The specific data that triggered this finding (matched patterns, suspicious values, etc.) |
remediation | string | Suggested fix for the issue |
cwe_id | string | Common Weakness Enumeration identifier (e.g., “CWE-200” for information exposure) |
owasp_id | string | OWASP Top 10 mapping (e.g., “A01:2021” for broken access control) |
status | string | Review status — starts as "open", can be updated to "confirmed", "false_positive", or "fixed" |
source | string | How this finding was detected: "passive" (interceptor), "active" (scanner/tool), or "ai" (agent analysis) |
metadata | object | Additional context as key-value pairs (host, path, tool name, etc.). Always an object, never null |
created_at | timestamp | When the finding was first detected |
updated_at | timestamp | When the finding was last modified (status changes update this) |
Finding types:
| Type | Description |
|---|---|
auth | Authentication issues (weak tokens, missing auth, broken session management) |
injection | Injection vulnerabilities (SQL injection, XSS, command injection, etc.) |
exposure | Information disclosure (API keys, credentials, internal paths, stack traces in responses) |
config | Security misconfiguration (missing headers, CORS issues, verbose errors) |
crypto | Cryptographic issues (weak algorithms, insecure transport, certificate problems) |
access | Access control issues (IDOR, privilege escalation, missing authorization) |
session | Session management issues (predictable tokens, missing expiration, insecure cookies) |
Finding Statistics
Section titled “Finding Statistics”GET /api/v1/findings/stats?session_id=01HWXYZ...Returns aggregate counts for a session’s findings, broken down by severity, type, and status.
Response:
{ "total": 42, "by_severity": {"critical": 2, "high": 8, "medium": 15, "low": 12, "info": 5}, "by_type": {"config": 10, "exposure": 8, "session": 6, "auth": 5, "injection": 5, "crypto": 4, "access": 4}, "by_status": {"open": 30, "confirmed": 8, "false_positive": 4}}Get Finding
Section titled “Get Finding”GET /api/v1/findings/{id}Returns a single finding by ID. Same fields as the list endpoint.
Returns 404 if not found.
Update Finding Status
Section titled “Update Finding Status”PATCH /api/v1/findings/{id}/statusUpdates the review status of a finding. This is how you mark findings as confirmed (real issue), false positive (not actually a problem), or fixed (resolved).
Request body (64 KB limit):
{ "status": "confirmed"}Valid status values: "open", "confirmed", "false_positive", "fixed"
If you send an invalid status, the error message explicitly lists all valid values.
After a successful update, the finding is re-fetched from the database to return the complete state with the updated updated_at timestamp.
Response: Updated FindingDTO
Broadcasts a finding.updated WebSocket event.
Delete Finding
Section titled “Delete Finding”DELETE /api/v1/findings/{id}Permanently removes a finding. No cascade — only the finding row is deleted (findings don’t have child records).
Response: {"ok": true}
Broadcasts a finding.deleted WebSocket event with {"id": "..."}.
Security Tools
Section titled “Security Tools”Ghost integrates with external security scanners. These endpoints let you check which tools are installed, install new ones, and enable/disable them.
List Tools
Section titled “List Tools”GET /api/v1/security/toolsReturns all known security tools with their installation status and configuration.
Response:
[ { "name": "frida", "installed": true, "version": "16.5.9", "description": "Runtime instrumentation — SSL pinning bypass, root detection bypass, crypto function tracing", "enabled": true, "pinned_version": "16.5.9", "package_manager": "pip3", "package_manager_ok": true, "installable": true }, { "name": "nuclei", "installed": true, "version": "3.2.0", "description": "Template-based vulnerability scanner — 9,000+ checks for XSS, SQLi, misconfigs, CVEs", "enabled": true, "package_manager": "brew", "package_manager_ok": true, "installable": true }]| Field | Type | Description |
|---|---|---|
name | string | Tool identifier |
installed | boolean | Whether the tool binary is found on the system |
version | string | Detected version (empty if not installed) |
description | string | What this tool does |
enabled | boolean | Whether Ghost is allowed to use this tool (controlled via toggle endpoint) |
pinned_version | string | Version Ghost expects (currently only Frida has this: “16.5.9”) |
package_manager | string | Which package manager can install this tool (brew, choco, pip3) |
package_manager_ok | boolean | Whether that package manager is available on this system |
installable | boolean | Whether Ghost can install this tool automatically |
Known tools:
Frida is always listed first (handled specially), followed by these external scanners:
| Tool | Binary | Package (macOS/Windows) | Requires Python | Description |
|---|---|---|---|---|
| Frida | (special) | pip3 | Yes | Runtime instrumentation — SSL pinning bypass, root detection bypass, crypto function tracing |
| Nuclei | nuclei | brew / choco | No | Template-based vulnerability scanner — 9,000+ checks for XSS, SQLi, misconfigs, CVEs |
| Dalfox | dalfox | brew / — | No | XSS specialist — deep parameter analysis, DOM XSS, blind XSS detection |
| ffuf | ffuf | brew / choco | No | Path and parameter fuzzer — endpoint discovery, IDOR testing at scale |
| SQLMap | sqlmap | brew / choco | Yes | SQL injection detection — tests captured flows for injection vulnerabilities |
| TruffleHog | trufflehog | brew / — | No | Secret scanner — 800+ verified detector types for API keys, tokens, credentials |
| Katana | katana | brew / — | No | JS endpoint discovery — finds untested API paths from app’s own JavaScript |
| Semgrep | semgrep | brew / — | Yes | Static analysis — DOM XSS sinks, eval, innerHTML, postMessage in captured JS |
Install Tool
Section titled “Install Tool”POST /api/v1/security/tools/installInstalls a security tool and streams the installation progress as Server-Sent Events.
Request body (1 KB limit):
{ "tool": "nuclei"}Response: SSE stream (not JSON)
SSE events:
| Event | Data | Description |
|---|---|---|
progress | {"message": "Installing nuclei via brew..."} | Installation progress updates (newlines are escaped) |
error | {"error": "brew not found"} | Installation failed |
done | Full tool status JSON | Installation completed successfully |
Installation timeout: 10 minutes (Frida installation via pip can be slow). The install runs with its own background context — disconnecting the HTTP request will cancel the install.
Pre-flight errors (before SSE starts):
- 400 — unknown tool name
- 409 — tool is already installed (includes current version)
- 412 — package manager not found, or no installation package available for this operating system
Toggle Tool
Section titled “Toggle Tool”PATCH /api/v1/security/tools/{name}/toggleEnables or disables a security tool. Disabled tools won’t be offered to the AI agent and won’t appear as available for scanning.
Request body (1 KB limit):
{ "enabled": false}Response:
{ "name": "nuclei", "enabled": false}The setting is persisted to Ghost’s config file.
Frida Instrumentation
Section titled “Frida Instrumentation”Frida is a runtime instrumentation toolkit that lets you inject JavaScript into running applications. Ghost integrates Frida for SSL pinning bypass (so the app trusts Ghost’s CA), root/jailbreak detection bypass (so the app doesn’t refuse to run on a testing device), and custom hooking (inspect function arguments, modify return values, trace API calls).
Frida Status
Section titled “Frida Status”GET /api/v1/frida/statusChecks whether Frida is installed and which version.
Response:
{ "installed": true, "version": "16.5.9", "pinned_version": "16.5.9", "package_manager": "pip3", "package_manager_ok": true}List Frida Devices
Section titled “List Frida Devices”GET /api/v1/frida/devicesLists devices visible to Frida (local, USB-connected, remote).
Response:
[ { "id": "local", "name": "Local System", "type": "local" }, { "id": "abc123", "name": "iPhone 16 Pro", "type": "usb" }]Device types: "local", "usb", "remote", "socket"
If Frida is not installed, returns an empty array [] with status 200 (not an error — this prevents console spam when the Frida panel is open but Frida isn’t set up).
List Apps
Section titled “List Apps”GET /api/v1/frida/devices/{id}/appsLists installed applications on a Frida device.
Response:
[ { "pid": 12345, "name": "My App", "identifier": "com.example.myapp" }]The pid is 0 if the app is not currently running.
List Processes
Section titled “List Processes”GET /api/v1/frida/devices/{id}/processesLists running processes on a Frida device.
Response:
[ { "pid": 12345, "name": "myapp" }]Attach to Process
Section titled “Attach to Process”POST /api/v1/frida/attachAttaches Frida to a running application and injects a JavaScript script.
Request body (512 KB limit — the script code is embedded in the request):
{ "device_id": "local", "app": "com.example.myapp", "script": "Interceptor.attach(Module.findExportByName(null, 'SSL_read'), { ... });", "session_type": "ssl_bypass", "label": "SSL Bypass for MyApp"}| Field | Required | Default | Description |
|---|---|---|---|
device_id | Yes | — | Frida device ID |
app | Yes | — | Application identifier or process name |
script | Yes | — | JavaScript code to inject |
session_type | No | "custom" | Type of instrumentation: "ssl_bypass", "root_bypass", "trace", or "custom" |
label | No | — | Human-readable label for the session |
Response: The created session:
{ "key": "local:com.example.myapp:ssl_bypass", "device_id": "local", "app": "com.example.myapp", "type": "ssl_bypass", "label": "SSL Bypass for MyApp", "source": "panel", "started_at": "2024-01-15T10:30:00Z"}The key is a composite identifier (deviceID:app:type) used to reference this session in other endpoints. The source is always "panel" for API-initiated sessions (vs "agent" for AI-initiated ones).
Broadcasts frida.session.started WebSocket event.
Spawn Process
Section titled “Spawn Process”POST /api/v1/frida/spawnSpawns an application with Frida instrumentation from the start — unlike attach (which hooks into an already-running process), spawn starts the app fresh with the script injected from the very first instruction. This is necessary for SSL bypass on apps that verify certificates during startup.
Same request/response format as attach. Same 512 KB body limit.
Detach
Section titled “Detach”POST /api/v1/frida/detachDetaches Frida from a session, removing all injected hooks.
Request body (4 KB limit):
{ "key": "local:com.example.myapp:ssl_bypass"}Response: {"ok": true}
Returns 404 if the session doesn’t exist.
Broadcasts frida.session.stopped WebSocket event.
List Sessions
Section titled “List Sessions”GET /api/v1/frida/sessionsReturns all active Frida sessions.
Response: Array of session objects (same format as the attach response). Always returns [] if no sessions, never null.
Get Console Output
Section titled “Get Console Output”GET /api/v1/frida/sessions/{key}/output?cursor=0Returns console output from a Frida session. The output is stored in a ring buffer that holds the last 1,000 lines per session — older lines are automatically dropped as new ones arrive.
Path parameter: {key} — session key (URL-decoded, since it contains colons)
Query parameter:
cursor(optional, default: 0) — pass 0 to get all available lines, or pass the cursor from a previous response to get only new lines since then
Response:
{ "lines": ["[*] SSL_read hooked at 0x7fff...", "[*] Intercepted 2048 bytes"], "cursor": 42}Pass cursor: 42 on your next request to get only lines added after that point. This is how the frontend implements efficient polling without re-fetching the entire history.
Stop Session
Section titled “Stop Session”DELETE /api/v1/frida/sessions/{key}Stops a Frida session (same as detach, but using DELETE method with the key in the URL path).
Response: {"ok": true}
Saved Scripts
Section titled “Saved Scripts”Ghost can save Frida scripts to the database for reuse.
List:
GET /api/v1/frida/scriptsReturns all saved scripts.
Response:
[ { "id": "01HWXYZ...", "name": "iOS SSL Bypass", "description": "Hooks SecTrustEvaluate and related functions", "code": "...", "category": "ssl_bypass", "platform": "ios", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T10:30:00Z" }]| Field | Description |
|---|---|
category | "ssl_bypass", "root_bypass", "trace", or "custom" |
platform | "ios", "android", or empty string for cross-platform scripts |
Create:
POST /api/v1/frida/scriptsRequest body (256 KB limit):
{ "name": "Custom Hook", "description": "Hooks the login function", "code": "Interceptor.attach(...);", "category": "custom", "platform": "android"}name and code are required. category defaults to "custom".
Response: 201 Created with the saved script object.
Update:
PUT /api/v1/frida/scripts/{id}Partial update — only non-empty fields in the request body are applied. The handler fetches the existing script, merges your changes, and saves.
Request body (256 KB limit): Same format as create, but all fields are optional.
Response: Updated script object.
Delete:
DELETE /api/v1/frida/scripts/{id}Response: {"ok": true}
Request Attacker
Section titled “Request Attacker”The request attacker (fuzzer) takes a captured HTTP request, marks specific parameters for testing, and sends hundreds or thousands of modified versions with different payloads to find vulnerabilities. It’s like having a patient tester who tries every possible SQL injection string, XSS payload, or path traversal sequence against your endpoint.
Run Attack
Section titled “Run Attack”POST /api/v1/attacker/runStarts a fuzzing attack. Returns 202 Accepted immediately — the attack runs asynchronously in a background goroutine. Results stream via WebSocket events.
Request body (1 MB limit):
{ "flow_id": "01HWXYZ...", "insertion_points": [ { "location": "query_param", "name": "search", "marker": "FUZZ1", "original": "hello" } ], "payloads": { "FUZZ1": { "type": "wordlist", "name": "xss-basic" } }, "attack_mode": "sniper", "match_rules": [ { "type": "body_contains", "value": "<script>", "negative": false } ], "max_requests": 500, "threads": 5, "delay_ms": 50, "follow_redirects": false}Insertion points — where to inject payloads:
| Field | Description |
|---|---|
location | Where in the request: "header", "query_param", "body_json", "body_form", "body_raw", "cookie", "path_segment", "method" |
name | The parameter name (e.g., header name, query parameter name) |
marker | Marker name to link this point to a payload set (e.g., "FUZZ1", "FUZZ2") |
original | The original value at this position (for baseline comparison) |
Payload configuration — what to inject:
| Field | Description |
|---|---|
type | Payload source: "wordlist" (built-in list), "numbers" (range), "custom" (your values), "null" (empty/null values) |
name | Wordlist name (when type is "wordlist") |
values | Array of custom payload strings (when type is "custom") |
from / to / step | Number range (when type is "numbers") |
encoding | How to encode payloads before injection: "none", "url", "double_url", "base64", "html", "hex" |
Match rules — how to detect interesting responses:
| Rule Type | Description |
|---|---|
status_diff | Response status code differs from the baseline |
length_diff | Response body length differs from baseline |
body_contains | Response body contains a specific string |
body_regex | Response body matches a regular expression |
time_gt_ms | Response time exceeds a threshold (for time-based detection) |
header_contains | A response header contains a specific string |
The negative flag inverts a rule — {"type": "body_contains", "value": "error", "negative": true} matches when the response does NOT contain “error”.
Validation:
flow_idis required- At least one insertion point is required
- At least one payload set is required
Response: {"status": "started", "flow_id": "01HWXYZ..."}
Errors: 503 (attacker engine not available), 409 (an attack is already running), 400 (validation failures)
Stop Attack
Section titled “Stop Attack”POST /api/v1/attacker/stopStops the running attack. No request body needed.
Response: {"ok": true}
Returns 409 if no attack is currently running.
Attack Status
Section titled “Attack Status”GET /api/v1/attacker/statusCheck whether an attack is currently running.
Response:
{ "running": true}List Wordlists
Section titled “List Wordlists”GET /api/v1/attacker/wordlistsReturns all built-in payload wordlists with their names and payload counts. Wordlists are embedded in the Ghost binary via go:embed from a payloads/ directory.
Response:
[ {"name": "xss-basic", "count": 45}, {"name": "sqli-generic", "count": 62}, {"name": "path-traversal", "count": 30}]Attack WebSocket Events
Section titled “Attack WebSocket Events”| Event | Payload | Description |
|---|---|---|
attacker:started | {"flow_id": "...", "baseline": {"status": 200, "length": 1234, "time": 45}} | Attack begun, baseline response captured |
attacker:progress | {"completed": 50, "total": 100, "matched": 3, "result": {...}} | Per-request result with payload, status code, response length, timing, match details, and diff from baseline |
attacker:completed | Full summary (see below) | Attack finished |
attacker:error | {"error": "message"} | Attack failed |
Attack summary (in attacker:completed):
| Field | Description |
|---|---|
total_requests | Total number of requests planned |
completed | How many actually completed |
matched | How many matched your rules |
interesting | How many showed notable differences from baseline |
errors | How many requests failed |
baseline_status / baseline_length / baseline_time_ms | The baseline response metrics for comparison |
status_distribution | Map of status code → count |
top_interesting | Array of the most interesting results |
duration | Total attack duration |
Per-result fields (in progress and top_interesting):
| Field | Description |
|---|---|
index | Request number |
payload | The payload string that was sent |
position | Where it was injected (e.g., "query_param:search") |
status_code | Response status code |
response_length | Response body size in bytes |
response_time_ms | Response time in milliseconds |
matched | Whether any match rules triggered |
match_details | Which rule matched and why |
interesting | Whether this result is notably different from baseline |
diff_from_base | Description of differences (e.g., "length +266", "status 500 (was 200)") |
WebSocket Events Summary
Section titled “WebSocket Events Summary”| Event | Trigger | Payload |
|---|---|---|
finding.created | New finding detected by interceptor or agent | Finding DTO |
finding.updated | Finding status changed via PATCH | Updated finding DTO |
finding.deleted | Finding removed via DELETE | {"id": "..."} |
frida.session.started | Frida attached/spawned | Session DTO |
frida.session.stopped | Frida detached | Session key |
frida.output | Console output from injected script | Output line |
attacker:started | Attack begun | Baseline info |
attacker:progress | Each request completed | Result + progress counts |
attacker:completed | Attack finished | Full summary |
attacker:error | Attack failed | Error message |
Summary of Limits
Section titled “Summary of Limits”| Limit | Value | Context |
|---|---|---|
| Finding body size | 64 KB | Status update request |
| Finding list cap | 500 | Maximum findings per request |
| Security tool install timeout | 10 minutes | Background install context |
| Tool install/toggle body | 1 KB | Small JSON payloads |
| Frida attach/spawn body | 512 KB | Script code embedded in request |
| Frida script body | 256 KB | Saved script create/update |
| Frida detach body | 4 KB | Session key |
| Frida console buffer | 1,000 lines | Per-session ring buffer |
| Attacker body | 1 MB | Attack configuration |