# What the agent sends

This page is the contract for what leaves your cluster: what the agent sends, how it removes secrets before sending, and what it never sends. It is written for the people who approve installing it. What the agent sends from a VM is on [Install on a VM](https://docs.evohub.io/agent-vm.md#what-it-reads-and-sends). If the agent ever does something not described here, that is a bug; please [report it](https://docs.evohub.io/get-help.md).

## Requests

| Request | When | Body |
| --- | --- | --- |
| `POST /api/v1/agent-enroll` | Until enrolled | The public key, the hostname (the pod's name, or the VM's under systemd), the agent's version and platform, and its name if you set one |
| `POST /api/v1/catalog/ingest/heartbeat` | Every minute | Its version and uptime |
| `POST /api/v1/catalog/ingest/sweeps` | At the start of each sweep, every 5 minutes | Empty |
| `POST /api/v1/catalog/ingest/sweeps/{id}/resources` | Per batch of up to 1000 objects | The objects, below |
| `POST /api/v1/catalog/ingest/sweeps/{id}/complete` | At the end of a sweep | Whether it is complete, and the kinds it could not list |
| `POST /api/v1/catalog/ingest/live` | Every minute | Live state, below |

Every request carries `User-Agent: evohub-agent/<version>`. Every request after enrollment is [signed](https://docs.evohub.io/agent-overview.md#signed-requests); the enrollment request carries the token instead. EvoHub also sees the address the requests come from.

## Each object

For every object of the kinds it may list (see [What it is allowed to do](https://docs.evohub.io/agent-kubernetes.md#what-it-is-allowed-to-do)), the agent sends three things:

- **An identity**, as a path: `k8s://<cluster id>/<apiVersion>/<Kind>/<namespace>/<name>`. The cluster ID is the uid of your `kube-system` namespace.
- **A content hash**: the SHA-256 of the redacted content. When EvoHub already holds that content, only the hash is sent.
- **The redacted content**: the object as the Kubernetes API returns it, redacted as below, with `resourceVersion`, `generation`, `managedFields` and `selfLink` removed from `metadata`, `spec` kept in full (redacted), and `status` removed except for these fields:

| Kind | `status` kept |
| --- | --- |
| Node | `nodeInfo` (machine ID, system UUID, boot ID, kernel version, OS image, architecture, kubelet and container runtime versions), `addresses`, `capacity` |
| Service, Ingress | `loadBalancer`: assigned IPs and hostnames |
| Namespace | `phase` |
| PersistentVolumeClaim | `phase`, `capacity` |

In practice EvoHub receives, for example: container names, images, ports, resource requests and limits; the non-secret parts of commands and arguments; probe paths and ports; the **names** of environment variables, volumes, Secrets, ConfigMaps, ServiceAccounts and image pull secrets; volume sources such as an NFS server or a host path; labels, and annotations that pass the rules below; Service cluster IPs and load balancer ranges; Ingress hosts, paths and TLS Secret names; a Node's provider ID, pod CIDR and taints; HPA targets; and PVC storage classes and sizes.

An object larger than 256 KiB after redaction is sent with only its name, namespace, uid, labels, owner references and creation time.

## Live state

Every minute the agent reports how each Deployment, StatefulSet, DaemonSet and CronJob is running now: replicas wanted, ready, available and updated; its pods counted by phase; their restarts; and how many containers are in each failing state, by the state's name only (`CrashLoopBackOff`, `ImagePullBackOff`, `OOMKilled` and a few others; anything else is sent as `Other`). For the service map it adds one line per pod, at most 200 per workload: the pod's name, ReplicaSet, node, phase, readiness, restarts, start time and failing state's name.

No pod spec, no environment, no messages, nothing a container printed. EvoHub keeps only the latest report, to show health, and treats one older than ten minutes as unknown. Set `catalog.liveState.enabled=false` in the chart to send none, and the agent then has no access to pods at all.

## Redaction

The agent applies these rules inside your cluster, before an object is hashed or sent. Rules 1 to 6 are structural: they apply wherever the field is, whatever it holds.

1. **Environment variable values.** Every value is removed, whatever the variable is called, in containers, init containers and ephemeral containers. The variable's name stays, and so does what a `valueFrom` points at, such as the name and key of a `secretKeyRef`.
2. **Command lines**: a container's `command` and `args`, and any probe's or lifecycle hook's `exec`.
   - `--password=x`, `PASSWORD=x` and `-px` lose their value.
   - The argument after a bare `--password` (or `--token`, `--secret`, …) is replaced.
   - The whole program after an interpreter's script flag is replaced: `-c`, `-ec`, `-lc` and similar, `-e`, `--eval` and `-Command`.
3. **Probe headers.** Every probe `httpHeaders` value is replaced.
4. **Volume drivers.** Every volume driver `options` and `volumeAttributes` value is replaced.
5. **Annotations**, in every annotations map including pod templates:
   - `kubectl.kubernetes.io/last-applied-configuration` is removed.
   - Annotations under `evohub.io/`, `app.kubernetes.io/`, `meta.helm.sh/release-`, `prometheus.io/` and `deployment.kubernetes.io/revision` are kept as they are.
   - Any other annotation keeps its value only if the value is at most 256 bytes, one line, not JSON, and the key does not end in `snippet`.
6. **Secrets and ConfigMaps.** A Secret's `data` and `stringData` would be removed and a ConfigMap reduced to its key names. The agent does not list either; this rule is there for safety only.
7. **A final check on every remaining string.** A string is replaced if:
   - its key ends in a word such as `password`, `secret`, `token`, `apikey`, `credentials`, `auth`, `dsn` or `_key`
   - it looks like a credential: an AWS key ID, a Stripe, GitHub, GitLab, Slack or Google key, a JWT, a private key, an `Authorization:` header, a bearer or basic credential, a URL with a password or a token in its query, or an EvoHub token
   - it looks like random key material: 24 or more characters with no spaces, mixing upper case, lower case and digits, with high entropy

Replaced values read `[REDACTED]`. The hash is computed after redaction, so a rotated secret is not a change.

### What the rules guarantee, and what they do not

Rules 1 to 6 are structural, so whatever a field they cover holds is removed. Rule 7 is pattern-based and catches common forms only. It can miss, for example:

- a short password passed as a positional argument (`redis-cli -a x`)
- a token that is all lower case or hex
- a secret written into a label or an unlisted spec field

Where a value is in an environment variable, a mounted Secret or a `secretKeyRef`, it is never sent. EvoHub runs a scanner of the same kind again when data arrives, and never logs request bodies.

## What it never sends

- the value of any environment variable, secret or not
- the contents of any Secret or ConfigMap
- kubectl's last-applied configuration, and the values of annotations that fail the rules above
- the scripts given to `sh -c` and similar
- pod specs, logs, events, metrics, or anything from inside a container
- its own private key
- the enrollment token, once it has enrolled

The agent's own log records counts, IDs (agent, cluster, sweep), its public key, and error messages from EvoHub and Kubernetes. It never records any object's content.

## Related

- [EvoHub Agent overview](https://docs.evohub.io/agent-overview.md)
- [Install on Kubernetes](https://docs.evohub.io/agent-kubernetes.md)
- [Install on a VM](https://docs.evohub.io/agent-vm.md)
- [Security at EvoHub](https://docs.evohub.io/security.md)
