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.
29.1 What it solves
Section titled “29.1 What it solves”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:
- 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. - Fingerprint: the agent runs the upstream fetch through a bundled
curl-impersonate-chromesubprocess, patching libcurl + BoringSSL to match Chrome’s exact TLS+H2 fingerprint.
29.2 Curl-impersonate bundling (v1.0.73+)
Section titled “29.2 Curl-impersonate bundling (v1.0.73+)”- 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
29.3 Local agent surface
Section titled “29.3 Local agent surface”- 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 viaAGENTWATCH_PROXY_BUNSERVE=0to legacyhttps.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.1with 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
29.4 Install / uninstall / status
Section titled “29.4 Install / uninstall / status”- 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 (
--purgeremoves those too): Windows Settings → Apps, macOSsudo "/Applications/AgentWatch Connect.app/Contents/Resources/uninstall.sh", Linuxsudo apt remove agentwatch-connectorsudo 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:
AAAAand 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 redirectedArecord. -
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 localconfig.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-settingssets 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), aselftestcommand 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/0and prefixes broader than/16are refused, and an IP claimed by two orgs resolves to null (never mis-attributed).resolveAgentWatchTenantconsults 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 thesystem_settingsKV 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 acrossdown/up. Auto-selects DB on Postgres, file on SQLite; override withAGENTWATCH_CA_STORE=db|file. The key is never committed or baked into the image. The Fleet UI serves a.mobileconfigdownload (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.shremains 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 inagent_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 viaallowedAccountDomains.resolveAgentWatchTenantconsults 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. Seesrc/enhanced/lan-ip-mapping.tsanddns-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 viaAGENTWATCH_DNS_TLS_PEM, or separate cert/key), else cleartext DoH is served behind a fronting proxy — the repo’shaproxy-production.cfgships sampledoh_backend+dot-inblocks (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.mobileconfigprofiles ship indns-server/mdm/. -
Build & test: the top-level
./build.shbuilds and tagsiterateai/agentwatch-dnsalongside the main image (skip with--skip-dns); the rootdocker-compose.ymlruns it as an opt-in--profile dnsservice, and the Helm chart has an opt-indns.enabledDeployment/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.mobileconfigprofiles and a JumpCloud subfolder with a combined root-CA + DoH profile plusbuild-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 anArecord (noNSdelegation) 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=0forces 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_portalsettings):sso(IdP bridge via/portal/api/sso/start→/sso/callback),otp(emailed one-time code viaEmailService),domain(allow-listed email),none(monitoring-only). Multi-domainallowedDomains. Admin UI:/portal-admin.html. - Trigger models: B DNS captive-steering (
AGENTWATCH_DNS_CAPTIVE=1foldsCAPTIVE_PROBE_DOMAINSinto 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-shapedadmitWebhookUrl; 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-Userfrom 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-mobileconfiguserEmailTemplateemits the MDM variable (Jamf$EMAIL, Intune{{mail}}) for per-user attribution without N files.