Skip to main content

Documentation Index

Fetch the complete documentation index at: https://quintsecurity.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Edge Daemon

The Quint edge daemon is a Go binary that runs as a LaunchDaemon on macOS. It operates the HTTPS forward proxy, MCP stdio relay, MCP multi-server gateway, and unified session tracker. It receives OS-level events from the EndpointSecurity system extension over a Unix socket and merges them with proxy content data into a unified session model.

Installation

Distributed as a signed .pkg installer that sets up both the Go daemon and the QuintAgent.app (ES extension):
# Download and install the .pkg
sudo installer -pkg quint-latest.pkg -target /

# Or use the install script with a deploy token
curl -fsSL https://install.quintai.dev | sh -s -- --token <deploy-token>
The installer registers:
  • LaunchDaemon at /Library/LaunchDaemons/dev.quintai.agent.plist
  • QuintAgent.app in /Applications/ (hosts the ES system extension)
  • Configuration at /etc/quint/config.yaml
The same config.yaml format is used in both development and production. The daemon reads it on startup — no separate dev/prod config mechanism.

Three Event Sources

The daemon ingests events from three independent sources, all feeding into the unified session tracker:

Source 1: EndpointSecurity Extension

The ES extension (Swift) detects AI agent processes via code signing and monitors 9 event types. Events arrive over a Unix socket with auth handshake. See ES Extension for full details.

Source 2: Forward Proxy

MITM TLS interception via HTTP_PROXY / HTTPS_PROXY environment variables. Parses 7 LLM API formats to extract tool calls with arguments. See Forward Proxy for full details.

Source 3: Process Scanner

Runs every 5 seconds, scanning the process table for AI agents using 21 platform signatures. Uses ps etime to recover real start times. This fills the gap for agents that were already running when the daemon started (the ES extension only sees new process launches).

Two Operation Modes

Full production mode: LaunchDaemon with ES extension, forward proxy, process scanner, cloud forwarder, and session lifecycle management.
# Started automatically by launchd
sudo launchctl bootstrap system /Library/LaunchDaemons/dev.quintai.agent.plist

# Or run manually for development
sudo ./quint-proxy daemon
This is the default mode when installed via .pkg.

Unified Session Tracker

The session tracker (internal/unisession/) is the core data model. It merges all three event sources into a single session per agent invocation.

Session Identity

Each session has a stable ID: {rootPID}-{startUnixMs}. This survives PID reuse — if a PID is recycled, the millisecond timestamp differentiates the sessions.

What Each Source Contributes

SourceContributes
ES ExtensionProcess lifecycle (start/fork/exit), file operations, code signing identity, process tree
Forward ProxyModel name, provider, tool calls, working directory (from LLM request bodies), conversation content
Process ScannerBootstrap discovery (agents running before daemon start), real start times via ps etime
Claude Session FilesSession name from ~/.claude/sessions/{PID}.json, session kind (interactive/headless), agent session UUID
lsofWorking directory resolution when not available from other sources

Session Fields

FieldSourceDescription
IDComputed{rootPID}-{startUnixMs}
PlatformES / Scannerclaude-code, cursor, copilot, etc.
CategoryComputedcli, electron, extension_hosted
SigningIDESe.g., com.anthropic.claude-code
TeamIDESe.g., Q6L2SF6YDW
SessionNameClaude filesUser-given name from session file
WorkingDirProxy / lsofProject directory
ModelProxye.g., claude-sonnet-4-20250514
ActionCountProxyTotal intercepted actions
ChildPIDsESSet of child process IDs
AvgRisk / MaxRiskProxyRisk score aggregates

PID Liveness Reaper

Every 10 seconds, the tracker checks if each active session’s root PID is still alive (via kill(pid, 0)). Dead sessions transition to ended and a session_end event is sent to the cloud.

Audit Log & Session Attribution

Every intercepted request, response, and tool call is persisted to a local SQLite audit database (~/.quint/quint.db, table audit_log). Each row is signed with Ed25519 and chained to the previous row via prev_hash — the audit log is tamper-evident even before it reaches the cloud.

Schema (subset)

ColumnDescription
idAutoincrement row ID
timestampISO-8601 UTC
server_nameDestination host (bedrock-runtime.us-east-1.amazonaws.com, etc.)
directionrequest | response
method / tool_nameHTTP method + canonical action string
arguments_json / response_jsonCaptured bodies (capped by policy)
verdictallow | deny | passthrough
signature / prev_hash / policy_hashEd25519 tamper chain
session_idStable unisession ID — {rootPID}-{startUnixMs}
process_pidSource PID from NE audit token or pidlookup
trace_idPer-tunnel trace (pre-dates session_id, kept for compatibility)
agent_id / agent_name / parent_agent_id / agent_depthIdentity + spawn hierarchy
risk_score / risk_level / behavioral_flags / score_decompositionScoring output
tool_use_idStable tool_use id from the LLM — enables UPDATE on tool_result arrival
parent_session_idFor subagent rows: points to the parent session

How session_id is populated

At every MITM log site (request, response, tool call), the daemon calls SessionLookup(pid) — a callback wired to unisession.Tracker.SessionByPID. If the PID is tracked (i.e. belongs to a detected AI agent process or one of its children), we stamp the row with that session’s ID and PID. If not tracked, the fields are left null. This makes the audit log natively join-able by session without reconstructing attribution after the fact:
SELECT tool_name, arguments_json, response_json, timestamp
FROM audit_log
WHERE session_id = '59247-1777085379101'
ORDER BY id ASC;
Two simultaneous Claude Code terminals produce two distinct session_id values — no bleed between invocations.

Decoded Timeline API

The raw response_json for streaming LLM calls is a thick stack of wrappers: AWS eventstream binary framing → JSON with base64-encoded "bytes" → Anthropic SSE events → content block deltas. To let downstream consumers avoid re-implementing the decode stack, the daemon exposes:
GET /api/sessions/timeline?pid=N          # or ?session_id=X
Returns a flat array of TimelineEvent objects — request, assistant_text, tool_call (with reconstructed JSON input), and response_raw fallback. This is what the local viewer uses to render a readable session drill-down.

Historical sessions

audit_log outlives the in-memory unisession.Tracker (reaped ~10s after the root PID exits). The /api/es/sessions endpoint merges live tracker state with audit.DB.HistoricalSessions(limit) — distinct (session_id, process_pid) groupings seen in audit, ordered by MAX(timestamp) — so reaped sessions remain discoverable.

Cloud Forwarder

The daemon pushes events and sessions to api.quintai.dev via HTTPS:
SettingValue
Buffer capacity5,000 events
Batch size500 events per push
Flush interval1 second
Max retries5 (exponential backoff, 1s → 5min)
OverflowJSONL file on disk, recovered on next flush
Source: proxy/internal/cloud/forwarder.go. Session lifecycle events (session_start, session_resume, session_end) go through a separate ingest endpoint (/v1/sessions/ingest). Each QuintEvent enqueued for the cloud forwarder also carries session_id, so the cloud actions table can be joined to the cloud sessions table by the same key the local audit log uses.

LLM Conversation Parsing

Seven dedicated parsers extract structured data from HTTP request/response bodies:
ParserFormat IDProviderExtracts
Anthropicanthropicapi.anthropic.comModel, messages, tool_use blocks, tool_result
OpenAIopenaiapi.openai.comModel, messages, function_call, tool_calls
OpenAI Responsesopenai-responses/v1/responses pathModel, input, function_call outputs
Bedrock Converseaws-bedrock-conversebedrock-runtime.*.amazonaws.comModel, toolUse blocks (camelCase)
Geminigoogle-geminigenerativelanguage.googleapis.comModel, contents, functionCall, functionResponse
Azure OpenAIazure-openai*.openai.azure.comSame as OpenAI parser, different provider tag
GenericgenericAll othersModel field extraction from JSON body
Detection priority: path-based (Responses, Gemini, Bedrock) -> host-based (Anthropic, OpenAI, Azure, Google, Mistral) -> body sniff -> generic fallback.

Agent Platform Detection

The daemon identifies AI agent platforms through 21 signatures using a multi-layer approach:
  1. Code signing (via ES extension) — team ID + signing ID, cryptographically verified
  2. Process name — case-insensitive exact match on binary name
  3. Path patterns — substring match in binary path
  4. Parent cascade — child inherits parent’s agent status
See Platform Detection and ES Extension for the full detection cascade.

Security Hardening

MechanismDetails
ES socket authShared secret at /etc/quint/es-auth-secret
TLS pinningISRG Root X1 + Amazon Root CA pinned for cloud API
Socket permissionsUnix socket file mode 0600
Deploy tokenRead from config file, never passed as CLI arg
CORSLocal dashboard restricted to localhost only

Data Classification

Stays on Machine

  • Source code content
  • Credentials and secrets
  • Full request/response bodies
  • Private signing keys
  • CA private key

Sent to Cloud

  • Structured metadata (action type, tool name, risk score)
  • Agent identity and platform
  • Session lifecycle (start, resume, end)
  • Timestamps and session IDs
  • File paths (for file operation events)
Source code, credentials, and secrets never leave the machine. The cloud receives only structured metadata sufficient for fleet-wide visibility and compliance reporting.