Docs/What the agent sends

What the agent sends

Before you helm install an agent inside your cluster, you deserve to know exactly what crosses the wire. Here it is. Every field, every endpoint, no surprises.

The agent's source is public: github.com/statusharbor/lighthouse. You can read the transmit code yourself in internal/transport/ to verify everything below.

Network shape

  • One destination: https://lighthouse.statusharbor.io. The hostname is hardcoded at compile time. The agent never resolves any other host.
  • Outbound HTTPS only. No inbound ports, no UPnP, no reverse tunnels.
  • Bearer-token auth on every request. The token is the one shown once in the dashboard when the Lighthouse is created. Lost? Mint a new one - old tokens stop working immediately.
  • TLS-only. HTTP/1.1 + HTTP/2, modern cipher suites, no fallback to HTTP. The hardcoded URL has the https:// scheme.
  • No third-party requests. The agent doesn't call analytics, telemetry, error reporting, or any other vendor.

Endpoints

The agent talks to five endpoints. Six, counting graceful shutdown.

POST /api/lighthouse/v1/register

Called once on agent start. Body:

{
  "agent_version": "1.4.2",
  "agent_hostname": "prod-01"
}

That's the entire payload. The hostname is captured from os.Hostname() (or container hostname inside Docker / k8s); we use it as the display label until you set one in the UI.

The response contains your Lighthouse's ID, paused state, heartbeat interval, flap protection threshold, and the list of checks the agent should run.

POST /api/lighthouse/v1/heartbeat

Called every 60 seconds (configurable in admin config; default 60s). Body:

{
  "agent_version": "1.4.2",
  "config_etag": "etag-abc123",
  "check_latencies": {
    "<check-uuid>": {
      "last_observed_latency_ms": 78,
      "last_observed_at": "2026-05-09T16:00:00Z"
    }
  }
}
  • agent_version - for your visibility in the dashboard ("Update available" hints).
  • config_etag - the agent's cached config tag. If it doesn't match the server's, the response includes a fresh check list.
  • check_latencies - sparse map of recent latency observations keyed by check UUID. Only round-trip times, no payload, no response bodies, no headers.

POST /api/lighthouse/v1/events

Called when a check changes state (passing → failing or vice versa). Body:

{
  "sync_kind": "ongoing",
  "events": [
    {
      "check_id": "<uuid>",
      "prev_state": "ok",
      "new_state": "down",
      "response_time_ms": 320,
      "status_code": 503,
      "error_message": "expected status 200, got 503",
      "agent_observed_at": "2026-05-09T16:00:14Z"
    }
  ]
}
  • prev_state / new_state - pass/fail strings.
  • response_time_ms - total round-trip the agent observed.
  • status_code - HTTP status, if applicable.
  • error_message - humanized error string. For HTTP checks: status mismatches, keyword mismatches, header mismatches, TLS/handshake errors, DNS errors, timeouts. The agent does not transmit response bodies, custom headers, or any data the probed service emits beyond the assertion outcome.
  • agent_observed_at - RFC3339 timestamp from the agent.

POST /api/lighthouse/v1/discoveries

Sent only if Kubernetes auto-discovery is enabled (discovery.enabled = true in the Helm values). The agent watches Ingress and Service resources in the namespaces you allowed and posts a snapshot. Each entry is metadata only:

{
  "discoveries": [
    {
      "kind": "ingress",
      "namespace": "prod",
      "resource_name": "web",
      "host": "app.example.com",
      "path": "/",
      "port": 443,
      "protocol": "https"
    }
  ]
}

The agent never sends pod logs, secrets, environment variables, configmaps, or anything other than the discovery metadata listed above. Discovery is opt-in; if you don't enable it, nothing ships.

POST /api/lighthouse/v1/host-metrics

Called on the host-metrics push interval whenever the metrics collector is running. Body is a snappy-compressed Prometheus remote-write protobuf batch carrying only the aggregate counters derived from /proc and (on Kubernetes) the kubelet stats endpoint - the same metric names listed on the Host & cluster metrics page (e.g. cpu_busy_percent, mem_used_bytes, disk_used_percent, net_*_bytes_total).

Identifying labels are host (the agent-supplied hostname) and lighthouse_id. Per-metric labels (mount point, network interface, node name on Kubernetes) are included only where the underlying metric needs them to be distinguishable. No process lists, no command lines, no environment variables, no file contents, no per-PID data. If the metrics relay is not configured server-side, the endpoint returns 503 and the collector backs off.

POST /api/lighthouse/v1/db-snapshots

Sent per registered database cluster on its collection cadence when database monitoring is enabled. The agent connects to your Postgres or MySQL from inside the network - using the connection string the console delivers to the agent (stored encrypted at rest, sealed to the agent's key on the wire for single-agent installs) - introspects the read-only stat views, and ships normalized facts: per-node identity and role, reachability, connection counts, cache / buffer-pool hit ratio, deadlocks, transaction-wraparound headroom, replication lag, and a hash + values of the configuration settings. Only while you run an on-demand Live update session does it also ship the top-N normalized slow-query digests from pg_stat_statements / performance_schema.

It never sends raw table rows, the values your queries return, or the data in your database. All analysis - the config audit, thresholds, fitness verdict - happens server-side; the agent stays "run the read-only query, return the numbers". The connection string travels one way, to the agent, and stays in its memory; nothing about it is echoed back.

POST /api/lighthouse/v1/shutdown

Best-effort, sent on graceful shutdown (SIGTERM). Empty body. Lets the dashboard distinguish "agent stopped on purpose" from "agent died." If the request fails the agent doesn't retry - shutdown proceeds either way.

What the agent does not send

Explicitly, none of this leaves your network:

  • HTTP response bodies from the services the agent probes.
  • Request bodies (POST/PUT/PATCH bodies you configure on a monitor are stored in the dashboard, not echoed back through the agent).
  • Response headers beyond the pass/fail outcome of expected headers rules.
  • TLS certificate chains beyond expiry-day calculations.
  • DNS query results beyond the assertion outcome.
  • Pod logs, ConfigMaps, Secrets, Events, Deployments, or any other Kubernetes resource outside the Ingress / Service metadata listed above.
  • Process lists, environment variables, command lines or any per-PID data from /proc. The host-metrics collector reads only the aggregate files (/proc/stat, /proc/meminfo, /proc/diskstats, /proc/net/dev and friends) to derive the counters listed on the Host & cluster metrics page; nothing per-process leaves the host.
  • Files from disk beyond the agent's own config and event buffer at /var/lib/lighthouse.
  • Raw database rows, query results or the data in your database. Database introspection reads only the read-only stat views and ships normalized facts (counts, ratios, settings, and - during a Live update session - normalized slow-query digests); the values your queries return never leave the network.
  • Database connection strings. The DSN is delivered to the agent and held only in memory; nothing about it is sent back to the console.

Buffering during outages

The agent maintains an on-disk event buffer at /var/lib/lighthouse (override with LIGHTHOUSE_DATA_DIR). Today the buffer is flushed on graceful shutdown - when the agent receives SIGTERM it drains pending events to Status Harbor before exiting, so a planned restart doesn't lose state.

Transient send failures during normal operation are not buffered today; an event that fails to ship is logged and dropped. On the next state change the agent emits the new value as usual, so the dashboard converges as soon as the network recovers - but the brief gap between "old state failed to send" and "new state sends successfully" can land in a recovery resync rather than as a real-time transition. Wiring the buffer into the runtime path is on the roadmap.

Heartbeats and discoveries are best-effort by design - the most recent value is always re-derived on the next call, so there's nothing to buffer.

Verifying

The numbered claims above match the wire types in the agent's internal/transport/ package. To audit the actual bytes:

# 1. Read the source.
git clone https://github.com/statusharbor/lighthouse
grep -r "json:" lighthouse/internal/transport/
 
# 2. Run with debug logging - prints request bodies before sending.
LIGHTHOUSE_LOG_LEVEL=debug ./lighthouse run
 
# 3. Or capture traffic at the network layer.
mitmproxy --mode reverse:https://lighthouse.statusharbor.io

Container images on ghcr.io/statusharbor/lighthouse are signed with cosign + Sigstore. Verify provenance before pulling:

cosign verify ghcr.io/statusharbor/lighthouse:latest \
  --certificate-identity-regexp 'https://github.com/statusharbor/lighthouse/.*' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com'