Security
A runbook for auditors — how to verify each of KubeHero's security properties on a running install.
Every commitment on kubehero.io/security is an engineering property you can check. This page shows how.
Read-only collection
The collector's ClusterRole grants get, list, watch on pods, nodes, services, endpoints, namespaces and their owners (Deployments, StatefulSets, DaemonSets, ReplicaSets, Jobs, CronJobs), get on nodes/proxy for kubelet stats, read on events, and a namespaced Lease for leader election. No create, update, patch or delete on anything else.
kubectl auth can-i --list --as=system:serviceaccount:kubehero-system:kubehero-collector
kubectl auth can-i patch deployments -A \
--as=system:serviceaccount:kubehero-system:kubehero-collector # noeBPF privileges
With collector.ebpf.enabled: true (the default) the collector container is privileged and uses hostPID:
- loading BPF programs, opening
perf_eventsamplers and attachingcgroup_skbprograms to the cgroup v2 root require it; hostPIDgives access to/proc/<pid>for stack symbolisation and cgroup → container attribution;- mounts:
/var/log/pods(read-only),/var/lib/kubehero(log checkpoints), the cgroup v2 filesystem and tracefs.
What the programs do and don't do:
cgroup_skbprograms always return "allow" — they count bytes and packets per endpoint pair, never drop, delay or modify traffic;- the retransmit program reads a stable tracepoint (
tcp:tcp_retransmit_skb); - the profiler samples stacks at 49 Hz per CPU;
- programs use stable kernel interfaces only (no CO-RE/BTF dependency) and are loaded from the collector image — no runtime compilation.
To run without privileges set collector.ebpf.enabled: false: the collector then runs with a read-only root filesystem and every capability dropped except DAC_READ_SEARCH (to read pod logs), and loses eBPF profiles and flows. pprof scraping, logs, cost and events keep working.
kubectl -n kubehero-system get ds kubehero-collector \
-o jsonpath='{.spec.template.spec.hostPID}{" "}{.spec.template.spec.containers[0].securityContext}'Only the operator writes
The operator is the only component with write verbs on workloads (Deployments, StatefulSets, HPAs, pod evictions, node cordon), and it only uses them for armed policies.
kubectl auth can-i patch deployments -A \
--as=system:serviceaccount:kubehero-system:kubehero-operator # yes — gated by arming
kubectl get rightsizingpolicy,ceilingpolicy,budgetpolicy -A \
-o custom-columns='KIND:.kind,NAME:.metadata.name,ARMED:.metadata.annotations.kubehero\.kubehero\.io/armed'Every applied rightsizing change records the previous resources on the workload (kubehero.io/rightsize-previous) and can be reverted with kubehero undo.
The advisor can't act
The advisor's ServiceAccount has no role bindings.
kubectl auth can-i --list --as=system:serviceaccount:kubehero-system:kubehero-advisor
kubectl get rolebinding,clusterrolebinding -A -o wide | grep kubehero-advisor # nothingIt calls only read RPCs; proposals are validated against the CRD whitelist before they're returned.
It is also never more open than the control plane. The advisor reads with its own service token, so it checks every caller itself: each request's bearer token goes to the control plane's WhoAmI and needs the viewer role or above (cluster enrollment tokens are refused). A request without a token passes only when the control plane accepts anonymous requests. The dashboard and the CLI send the signed-in user's token automatically.
What leaves the cluster
Nothing, by default. The only optional egress is the advisor's LLM brain: when advisor.anthropic.existingSecret is set, the snapshot and tool results the model reasons over — which can include capped samples of log lines — are sent to Anthropic's API under your key. Check what's configured:
helm -n kubehero-system get values kubehero -a | grep -A3 anthropicWith network policies enabled (networkPolicies.enabled: true), you can also restrict the advisor's egress to the control plane and the Anthropic API — or to the control plane only.
Authentication and roles
- API keys with roles
owner,admin,auditor,member,viewer(controlPlane.auth.apiKeysSecret). - Per-cluster enrollment tokens from
kubehero cluster add— only their SHA-256 hash is stored. - OIDC with JWKS signature verification and group → role mapping (
controlPlane.auth.oidc.*); SCIM 2.0 provisioning. controlPlane.auth.requireAuth: true(default) rejects anonymous RPCs; the compatibility APIs (Loki, OTLP, Pyroscope, OpenCost) and the advisor use the same tokens.
See Identity.
Audit log
Policy changes, arming, enforcement actions and rightsizing applies are written to an append-only table, each row signed with the HMAC key in the kubehero-control-plane Secret (audit-hmac-key). ListAuditLog (auditor role) exports them.
Supply chain
Images are signed with cosign (keyless, by the release workflow) and carry SPDX SBOM attestations; releases are scanned with Trivy.
cosign verify ghcr.io/kubehero-io/control-plane:0.3.3 \
--certificate-identity-regexp 'https://github.com/kubehero-io/platform/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
cosign verify-attestation --type spdxjson ghcr.io/kubehero-io/control-plane:0.3.3 \
--certificate-identity-regexp 'https://github.com/kubehero-io/platform/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comReporting a vulnerability
Email security@kubehero.io with reproduction steps. We aim to acknowledge within 2 business days. Please don't open public GitHub issues for security reports.