Skip to content
Docs

Client Agent (AgentWatch Connect) and Egress Tunnel

The endpoint agent (agentwatch-connect, v1.0.77) is shipped as a signed installer for macOS (universal DMG), Windows (NSSM service + tray), and Linux (systemd + tray). It is the on-device component that intercepts unmanaged web-chat traffic (chatgpt.com, claude.ai, gemini.google.com, etc.) without requiring a corporate proxy.

Web-chat surfaces bind anti-bot tokens (cf_clearance, Cloudflare Turnstile, OpenAI Sentinel) to the IP that minted them, and fingerprint the TLS+HTTP/2 layer. Naively fetching these endpoints from the central AgentWatch server triggers 400/403 from Cloudflare Bot Management. The agent solves both problems:

  1. IP: AgentWatch routes the upstream fetch back through the user’s Connect agent over the long-lived NDJSON egress tunnel (/api/agent/egress-tunnel/listen), so the egress IP is the user’s machine.
  2. Fingerprint: the agent runs the upstream fetch through a bundled curl-impersonate-chrome subprocess, patching libcurl + BoringSSL to match Chrome’s exact TLS+H2 fingerprint.
  • Vendored upstream binaries for macOS + Linux (v1.5.6)
  • Built and signed on Windows from source (clang-cl + VS 17 build toolchain)
  • Bundled-binary discovery + per-request variant matching
  • Startup smoke test
  • AV-blocked tray banner (Windows Defender / SmartScreen false-positive UX)
  • Heartbeat telemetry: variant usage counters + coverage-gap suppress list
  • Local TLS proxy on 127.0.0.1:443 — Bun.serve (default): HTTP/1.1 over TCP + HTTP/3 over QUIC/UDP on the same port, with HTTP/2 ALPN structurally wired; opt-out via AGENTWATCH_PROXY_BUNSERVE=0 to legacy https.createServer (HTTP/1.1 only). See Section 30 for the full multi-protocol story.
  • Control API on 127.0.0.1:19457
  • Local CA: forge-generated, installed in OS trust store + browser stores
  • CRL Distribution Point: empty CRL served from the control API so the CDP URL is reachable cross-platform
  • Hosts-file routing: managed domains are pinned to 127.0.0.1
  • Browser policy: Chrome + Edge + Firefox managed-policy bundle
  • DNS fallback: 1.1.1.1 with visible error state if local resolver fails; matches the proxy’s c-ares workaround
  • Daemon detection: NSSM-aware (Windows), launchd-aware (mac), systemd-aware (Linux) — no duplicate tray spawn
  • ChatGPT anonymized logging for capture coverage diagnostics
  • Installers: macOS .pkg + .dmg (signed + notarized), Windows .msi + .exe (NSSM service), Linux .deb + .rpm + .tar.gz
  • Privileged install elevates with the correct platform mechanism (Windows uses non-RedirectStandardOutput variant of Start-Process -Verb RunAs)
  • Sign-in flow: CLI prompts for API Key (not password); auto-fills via tray
  • Tray UI shows live connect/disconnect, AV-blocked state, last-heartbeat, version
  • Uninstall removes everything Connect installed (service, hosts entries, CA, helpers, browser policies) and keeps MDM-owned settings and per-user settings (--purge removes those too): Windows Settings → Apps, macOS sudo "/Applications/AgentWatch Connect.app/Contents/Resources/uninstall.sh", Linux sudo apt remove agentwatch-connect or sudo agentwatch-connect uninstall (Connect 1.0.150+)

29.5 DNS Redirector — network-level steering (dns-server/)

Section titled “29.5 DNS Redirector — network-level steering (dns-server/)”

The DNS Redirector is a second, agentless way to steer AI traffic to the gateway, for devices where the endpoint agent can’t be installed. It is a forwarding DNS resolver, shipped as a standalone Docker container, that a customer points their router/DHCP DNS at.

  • Redirect: the ~26 agent-fleet AI domains (chatgpt.com, api.anthropic.com, claude.ai, …) resolve to the tenant’s AgentWatch gateway IP. Everything else is forwarded raw to the customer’s upstream resolvers (default 8.8.8.8,1.1.1.1), preserving EDNS/DNSSEC.

  • NODATA guard: AAAA and non-address queries on a redirected name return NODATA so no real IP or HTTPS/SVCB hint can route a client around the gateway; clients fall back to the redirected A record.

  • Hybrid config: pulls the redirect list + gateway target from the central server via the tenant connect API key (GET /api/dns/config, kept in sync with the Agent Fleet tracked-domains editor), caches to disk, and falls back to a local config.json / env for standalone / air-gapped sites. A token-guarded local admin UI (:8053) edits domains/gateway/upstream on-prem.

  • Tenant attribution: each config pull registers the deployment’s public egress IP against the org (agent_dns_egress_ips), so gateway traffic from that IP is attributable at the organization level even with no per-request identity header. POST /api/admin/agent-fleet/dns-settings sets the gateway + upstream centrally.

  • Prerequisite for content inspection: because DNS only returns an IP, the browser still negotiates TLS for the real hostname — so the AgentWatch root CA must be trusted on endpoints (MDM/GPO) for the gateway to decrypt/DLP the traffic. Without it, only SNI-passthrough metadata is available. This is the standard enterprise CASB model (as used by Zscaler/Netskope).

  • Observability & demo tooling: a live stats panel in the local admin UI (per-domain redirect counters, “domains resolved” shadow-AI-discovery table, recent-query feed with client IP; GET /stats + /status), a selftest command that proves redirect-vs-passthrough behaviour on-site, and a periodic stats push to the central Fleet DNS dashboard for a single pane of glass. Even without the CA, the resolved-domain counts are a genuine shadow-AI-discovery signal. A DoH / Secure-DNS preflight warning is emitted at startup and in the self-test (DoH bypasses network DNS — the top cause of “redirect not working”).

  • Admin UI panel: a nav-integrated DNS Redirector section in the AgentWatch admin (alongside Agent Fleet) configures the gateway target + upstream resolvers (/api/admin/agent-fleet/dns-settings), shows the live traffic dashboard (/dns-stats) and registered egress IPs, and edits the per-user LAN-IP map.

  • Tenant identification by gateway IP: an admin-managed per-tenant CIDR allowlist (/api/admin/agent-fleet/gateway-ips, agent_gateway_ip_allowlist) maps each customer’s corporate gateway egress IP(s) to their org — the deliberate way to identify a tenant from DNS traffic on a shared gateway (many CIDRs → one tenant supported). Guarded: 0.0.0.0/0 and prefixes broader than /16 are refused, and an IP claimed by two orgs resolves to null (never mis-attributed). resolveAgentWatchTenant consults it ahead of the auto-learned egress map.

  • Multi-gateway routing (DNS side): the resolver can point different source-IP CIDRs at different AgentWatch gateways (gatewayRoutes) — e.g. segments 1&2 → gateway-a, 3–5 → gateway-b, else default — chosen per query by the client’s egress IP.

  • Root CA — auto-generated, shared correctly across hosts (ca-manager.ts). Resolution precedence: (1) explicit mounted secret — AGENTWATCH_CA_CERT_FILE+AGENTWATCH_CA_KEY_FILE (or PEM env); (2) shared DB — when gateways share Postgres, the CA lives in the system_settings KV table (first host to boot mints it, atomic first-writer-wins; every other host reads the same one — so it’s not regenerated per host); (3) local volume — single-host fallback (AGENTWATCH_CA_DIR, default /app/data/ca), reused across down/up. Auto-selects DB on Postgres, file on SQLite; override with AGENTWATCH_CA_STORE=db|file. The key is never committed or baked into the image. The Fleet UI serves a .mobileconfig download (embeds the CA + DoH; AgentWatch domain from the address bar, DoH host/IP overridable) and a root-CA download (/api/admin/agent-fleet/dns-mobileconfig, /root-ca.crt). certs/generate-agentwatch-root-ca.sh remains for out-of-band pre-provisioning.

  • Per-user attribution: an org-level egress-IP → org map (public IP a deployment reports from) plus a finer on-prem LAN-IP → user map (/api/admin/agent-fleet/lan-ip-mapping, stored in agent_lan_ip_user_mapping). Behind one NATed corporate gateway employees share an IP, so per-employee identity comes from LLM-account identity (the account email seen in the MITM’d traffic; llm-account-identity.ts) — which also flags personal accounts via allowedAccountDomains. resolveAgentWatchTenant consults both as strictly-additive fallbacks when no identity header is present (LAN-IP → user first, then egress-IP → org); ambiguous private IPs claimed by >1 org are refused rather than mis-attributed. See src/enhanced/lan-ip-mapping.ts and dns-egress.ts.

  • Encrypted DNS (DoH + DoT): also serves DNS-over-HTTPS (RFC 8484, /dns-query) and DNS-over-TLS (RFC 7858, :853) over the same query engine. TLS is terminated in-container when a cert is provided (a combined HAProxy-style PEM via AGENTWATCH_DNS_TLS_PEM, or separate cert/key), else cleartext DoH is served behind a fronting proxy — the repo’s haproxy-production.cfg ships sample doh_backend + dot-in blocks (HAProxy terminates TLS; DoT-minus-TLS forwards to TCP :53). This is what lets managed Mac/iOS/Android devices point their system encrypted DNS at AgentWatch via an MDM DNS Settings profile — steering works system-wide and off the corporate network, turning the DoH-bypass problem into the solution. Ready-to-edit Apple .mobileconfig profiles ship in dns-server/mdm/.

  • Build & test: the top-level ./build.sh builds and tags iterateai/agentwatch-dns alongside the main image (skip with --skip-dns); the root docker-compose.yml runs it as an opt-in --profile dns service, and the Helm chart has an opt-in dns.enabled Deployment/Service (recommended production deployment is still standalone Docker on a dedicated port-53 IP). API coverage is a newman companion (AgentWatch_DNS_Test_Collection.postman_collection.json) wired into ./newman-runner.sh (--dns).

  • MDM enrollment: dns-server/mdm/ ships Apple DoH/DoT .mobileconfig profiles and a JumpCloud subfolder with a combined root-CA + DoH profile plus build-profile.sh (injects the org root CA, generates UUIDs) — so a single MDM profile both trusts the gateway CA and points the device’s system DNS at the resolver. Resolver hostname needs only an A record (no NS delegation) plus a trusted TLS cert.

  • Dual-stack IPv6: binds :: by default and serves IPv4 + IPv6 across Do53/DoH/DoT/admin; client ACLs, gateway routes, and the central tenant-IP allowlist all accept IPv6 CIDRs (matching is BigInt-based, v4-mapped ::ffff: treated as v4); upstream forwarding is family-aware. AGENTWATCH_DNS_IPV6=0 forces v4-only.

Runs on Bun with zero runtime dependencies (hand-rolled minimal DNS wire codec); UDP + TCP on :53, plus DoH/DoT. See dns-server/README.md.

29.6 DNS Redirector vs Connect client — when to use which

Section titled “29.6 DNS Redirector vs Connect client — when to use which”

The two are complementary, covering different halves of the steering problem; most deployments run both.

Connect client (per-machine) DNS Redirector (network)
Steering Edits each device’s hosts file One DNS change on the router; whole LAN
TLS trust Installs its own local CA per machine CA pushed separately via MDM/GPO
Identity Injects X-Zscaler-User → per-user Org-level (cloud) / LAN-IP (on-prem gateway)
Endpoint install App on every device Nothing on endpoints (just CA trust)
Device coverage Only manageable devices Every LAN device — BYOD, Linux, IoT, phones
Off-network (roaming) Follows the user everywhere Only on a network using our DNS
Local anti-bot egress curl-impersonate from user IP Relies on gateway egress

Use the DNS Redirector for unmanaged/BYOD coverage and fast, low-friction rollout on networks whose DNS you control. Keep the Connect client for reliable per-user attribution, roaming/off-network laptops, and local curl-impersonate egress. The DNS Redirector + MDM-pushed CA replace the client’s steering and TLS-trust roles — not its identity and roaming roles.

29.7 Enrollment portal + captive-portal onboarding (2.0.193+)

Section titled “29.7 Enrollment portal + captive-portal onboarding (2.0.193+)”

A tenant-branded captive/enrollment portal at /portal onboards employees + guests without pre-known emails. Access is gated on device enrollment (CA trust), not identity; identity strength is a separate, per-tenant dial. Full customer-facing setup — the three deployment models, NAC admit-webhook payloads + Meraki/Aruba/UniFi/ISE recipes, inline-gateway Docker, bulk MDM — is in AgentWatch_Endpoint_Enrollment_Guide.md (+ sample bridge docs/samples/agentwatch-admit-bridge.py).

  • Identity modes (per tenant, enrollment_portal settings): sso (IdP bridge via /portal/api/sso/start → /sso/callback), otp (emailed one-time code via EmailService), domain (allow-listed email), none (monitoring-only). Multi-domain allowedDomains. Admin UI: /portal-admin.html.
  • Trigger models: B DNS captive-steering (AGENTWATCH_DNS_CAPTIVE=1 folds CAPTIVE_PROBE_DOMAINS into the redirect set; the gateway 302s the OS probe paths /generate_204, /hotspot-detect.html, /connecttest.txt → /portal) — turnkey; A NAC walled-garden + external captive portal → our /portal, admitted via the HMAC-signed CoA-shaped admitWebhookUrl; C inline gateway (keeps the L3 default route; forwards to the real upstream).
  • Guest vs managed: guests → keyless mobileconfig (org by egress IP, identity by OTP-email/LLM-account, user-removable); managed → Connect via MDM (X-Zscaler-User from OS session, MDM-locked). Opt-in guest Connect issues a per-device, revocable key (/portal/api/connect-config, off by default, verified-identity only) — never the shared org key.
  • Bulk MDM: one profile scoped to N devices; connect-mobileconfig userEmailTemplate emits the MDM variable (Jamf $EMAIL, Intune {{mail}}) for per-user attribution without N files.