> ## Documentation Index
> Fetch the complete documentation index at: https://quintsecurity.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Edge Daemon

> Go daemon with forward proxy, MCP gateway, unified session tracker, and cloud forwarder

# 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):

```bash theme={null}
# Download and install the .pkg
sudo installer -pkg quint-latest.pkg -target /

# Or use the install script with a deploy token
# Install the signed .pkg, then register the daemon with a deploy token.
# See /operations/installation. The installer is distributed directly:
# hello@quintai.dev
```

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`

<Info>
  The same `config.yaml` format is used in both development and production. The daemon reads it on startup -- no separate dev/prod config mechanism.
</Info>

## Three Event Sources

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

```mermaid theme={null}
flowchart TD
    subgraph Sources["Event Sources"]
        ES["ES Extension\n(Unix Socket)\nexec, fork, exit, open,\nwrite, close, rename,\nunlink, create"]
        FP["Forward Proxy\n(HTTPS MITM)\n7 LLM API formats\ntool call extraction"]
        PS["Process Scanner\n(every 5s)\n21 agent signatures\nps etime for start times"]
    end

    subgraph Daemon["Daemon Core"]
        ESL["eslistener\n(socket server)"]
        LLM["llmparse\n(7 format parsers)"]
        PD["procscan\n(agent detection)"]
        UT["unisession.Tracker\n(merge all sources)"]
    end

    subgraph Output["Cloud Forwarding"]
        EVT["Event Forwarder\n(batch 100, 2s flush)"]
        SLC["Session Lifecycle\n(start/resume/end)"]
        OVF["Overflow File\n(JSONL on disk)"]
    end

    ES --> ESL --> UT
    FP --> LLM --> UT
    PS --> PD --> UT
    UT --> EVT
    UT --> SLC
    EVT -->|retry exhausted| OVF
    OVF -->|next flush| EVT
    EVT --> API["api.quintai.dev"]
    SLC --> API
```

### 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](/edge/endpoint-security) 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](/edge/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

<Tabs>
  <Tab title="Daemon Mode (quint daemon)">
    Full production mode: LaunchDaemon with ES extension, forward proxy, process scanner, cloud forwarder, and session lifecycle management.

    ```bash theme={null}
    # 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`.
  </Tab>

  <Tab title="Watch Mode (quint watch)">
    Lightweight mode: forward proxy only, with embedded local dashboard at `http://localhost:8080`. No ES extension, no cloud forwarder. Good for single-developer use.

    ```bash theme={null}
    quint watch
    ```

    Set proxy environment variables in another terminal:

    ```bash theme={null}
    export HTTP_PROXY=http://localhost:9090
    export HTTPS_PROXY=http://localhost:9090
    export SSL_CERT_FILE=~/.quint/ca/quint-ca-bundle.pem
    ```
  </Tab>
</Tabs>

## 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.

```mermaid theme={null}
stateDiagram-v2
    [*] --> active: ES exec / process scan
    active --> active: ES events / proxy events
    active --> draining: ES exit (root PID)
    draining --> ended: drain timeout (30s)
    active --> ended: PID liveness reaper (10s check)
    ended --> [*]: session_end sent to cloud
```

### 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

| Source                   | Contributes                                                                                                |
| ------------------------ | ---------------------------------------------------------------------------------------------------------- |
| **ES Extension**         | Process lifecycle (start/fork/exit), file operations, code signing identity, process tree                  |
| **Forward Proxy**        | Model name, provider, tool calls, working directory (from LLM request bodies), conversation content        |
| **Process Scanner**      | Bootstrap discovery (agents running before daemon start), real start times via `ps etime`                  |
| **Claude Session Files** | Session name from `~/.claude/sessions/{PID}.json`, session kind (interactive/headless), agent session UUID |
| **lsof**                 | Working directory resolution when not available from other sources                                         |

### Session Fields

| Field                 | Source       | Description                                              |
| --------------------- | ------------ | -------------------------------------------------------- |
| `ID`                  | Computed     | `{rootPID}-{startUnixMs}`                                |
| `Platform`            | ES / Scanner | `claude-code`, `cursor`, `copilot`, etc.                 |
| `Category`            | Computed     | `cli`, `electron`, `extension_hosted`                    |
| `SigningID`           | ES           | e.g., `com.anthropic.claude-code`                        |
| `TeamID`              | ES           | e.g., `Q6L2SF6YDW`                                       |
| `SessionName`         | Claude files | User-given name from session file                        |
| `WorkingDir`          | Proxy / lsof | Project directory                                        |
| `Model`               | Proxy        | e.g., `claude-sonnet-4-20250514`                         |
| `ActionCount`         | Proxy        | Total intercepted actions                                |
| `ChildPIDs`           | ES           | Set of child process IDs                                 |
| `AvgRisk` / `MaxRisk` | Proxy        | Risk score aggregates (shadow-mode corroborating signal) |

### 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)

| Column                                                                   | Description                                                               |
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------- |
| `id`                                                                     | Autoincrement row ID                                                      |
| `timestamp`                                                              | ISO-8601 UTC                                                              |
| `server_name`                                                            | Destination host (`bedrock-runtime.us-east-1.amazonaws.com`, etc.)        |
| `direction`                                                              | `request` \| `response`                                                   |
| `method` / `tool_name`                                                   | HTTP method + canonical action string                                     |
| `arguments_json` / `response_json`                                       | Captured bodies (capped by policy)                                        |
| `verdict`                                                                | `allow` \| `deny` \| `passthrough`                                        |
| `signature` / `prev_hash` / `policy_hash`                                | Ed25519 tamper chain                                                      |
| `session_id`                                                             | Stable unisession ID — `{rootPID}-{startUnixMs}`                          |
| `process_pid`                                                            | Source PID from NE audit token or pidlookup                               |
| `trace_id`                                                               | Per-tunnel trace (pre-dates session\_id, kept for compatibility)          |
| `agent_id` / `agent_name` / `parent_agent_id` / `agent_depth`            | Identity + spawn hierarchy                                                |
| `risk_score` / `risk_level` / `behavioral_flags` / `score_decomposition` | Shadow-mode corroborating signal (not the enforcement decision)           |
| `tool_use_id`                                                            | Stable tool\_use id from the LLM — enables UPDATE on tool\_result arrival |
| `parent_session_id`                                                      | For 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:

```sql theme={null}
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:

| Setting         | Value                                       |
| --------------- | ------------------------------------------- |
| Buffer capacity | 5,000 events                                |
| Batch size      | 500 events per push                         |
| Flush interval  | 1 second                                    |
| Max retries     | 5 (exponential backoff, 1s → 5min)          |
| Overflow        | JSONL file on disk, recovered on next flush |

Source: [`proxy/internal/cloud/forwarder.go`](https://github.com/Quint-Security/proxy/blob/main/internal/cloud/forwarder.go).

```mermaid theme={null}
flowchart LR
    BUF["Ring Buffer\n(5000 capacity)"] --> BATCH["Take up to 500 events"]
    BATCH --> PUSH["POST to api.quintai.dev"]
    PUSH -->|success| ACK["Clear batch"]
    PUSH -->|failure| RETRY["Retry (5x, exp backoff)"]
    RETRY -->|exhausted| DISK["Write to overflow.jsonl"]
    DISK -->|next flush| RECOVER["Recover + prepend to batch"]
```

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:

| Parser           | Format ID              | Provider                            | Extracts                                        |
| ---------------- | ---------------------- | ----------------------------------- | ----------------------------------------------- |
| Anthropic        | `anthropic`            | `api.anthropic.com`                 | Model, messages, tool\_use blocks, tool\_result |
| OpenAI           | `openai`               | `api.openai.com`                    | Model, messages, function\_call, tool\_calls    |
| OpenAI Responses | `openai-responses`     | `/v1/responses` path                | Model, input, function\_call outputs            |
| Bedrock Converse | `aws-bedrock-converse` | `bedrock-runtime.*.amazonaws.com`   | Model, toolUse blocks (camelCase)               |
| Gemini           | `google-gemini`        | `generativelanguage.googleapis.com` | Model, contents, functionCall, functionResponse |
| Azure OpenAI     | `azure-openai`         | `*.openai.azure.com`                | Same as OpenAI parser, different provider tag   |
| Generic          | `generic`              | All others                          | Model 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 [ES Extension](/edge/endpoint-security) for the rest of the detection cascade.

## Security Hardening

| Mechanism          | Details                                            |
| ------------------ | -------------------------------------------------- |
| ES socket auth     | Shared secret at `/etc/quint/es-auth-secret`       |
| TLS pinning        | ISRG Root X1 + Amazon Root CA pinned for cloud API |
| Socket permissions | Unix socket file mode `0600`                       |
| Deploy token       | Read from config file, never passed as CLI arg     |
| CORS               | Local dashboard restricted to localhost only       |

## Data Classification

<CardGroup cols={2}>
  <Card title="Stays on Machine" icon="lock">
    * Source code content
    * Credentials and secrets
    * Full request/response bodies
    * Private signing keys
    * CA private key
  </Card>

  <Card title="Sent to Cloud" icon="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)
  </Card>
</CardGroup>

Source code, credentials, and secrets **never leave the machine**. The cloud receives only structured metadata sufficient for fleet-wide visibility and compliance reporting.
