Kubescape Scan and Triage: Security Posture Without the Noise
Purpose
The daily Kubescape scan (Kubescape) already produces a full security audit of the k3s cluster, but a report with hundreds of controls is hard to read every day. This agent wraps the scan with two things: an identity that can read the cluster but write only to its own results repository, and a triage step that reduces the report to what changed against a baseline from a week earlier. The result is a small summary file that the ops digest picks up, so nobody has to read the full JSON.
This is part of the Hermes agents series. See the first post on Hermes itself for the guardrails, identities, and how access works.
Architecture
The scan runs through Hermes as a no-agent cron job at 06:00 daily: a script runs and its output is delivered, with no LLM turn involved. The pipeline has two stages: collect, then compare.
flowchart LR
A["Cron 06:00<br/>(no-agent mode)"] --> B["sudo wrapper"]
B --> C["Scanner identity<br/>(user kscan)"]
C --> D["k3s API<br/>(read, no Secrets)"]
D --> E["Results JSON<br/>(daily file)"]
E --> F["Commit to results repo<br/>(deploy key)"]
F --> G["Triage vs 7-day baseline"]
G --> H["Summary<br/>(readable by hermes)"]
H --> I["Ops digest<br/>(next morning)"]
The kubescape-daily script runs as a dedicated Unix user through a root-owned sudo wrapper. The scanner’s Kubernetes service account can read the cluster but not Secrets. The scanner’s only write path is a deploy key for a separate results repository, where each day’s JSON is committed under the name “Kubescape Scanner”.
At the end of the scan run, kubescape-triage, a Python script with no LLM, compares the latest scan with two earlier ones: the previous scan, for the score change since yesterday, and a baseline seven days earlier (or the oldest scan, while the history is shorter). The output is a small JSON summary with the controls that started failing, the controls that were fixed, the controls that now fail on more resources, and the score. It holds only control names, severities, counts and short resource ids, no object contents. The file lands in a setgid directory owned by the hermes group, so hermes can read it but not write it.
Permissions and roles
The scanner cannot widen its own permissions. Its deploy key can push to the results repository, so that repository never holds anything that gets applied to the cluster. The RBAC that defines what the scanner may read lives in a different repository, which the scanner can’t write. Someone who stole the deploy key could rewrite the scan history, but not the scanner’s access.
flowchart TD
A["Scanner<br/>(user kscan)"] -->|"read, no Secrets"| B["k3s cluster"]
A -->|"push via deploy key"| D["Results repo<br/>(scan JSON history)"]
A -.->|"no write access"| C["RBAC repo<br/>(scanner permissions)"]
This follows the standing pattern across all Hermes agents: each job runs under its own identity with only the rights it needs, enforced by file ownership, sudo rules and wrapper checks, not by prompt instructions.
How it runs
The morning jobs run in this order: docs-queue sync (05:00), host facts pushes (05:05 and 05:10), config compliance (05:20), the ops digest (05:30) and this Kubescape scan (06:00). Because the digest runs before the scan, each morning’s digest reads the summary from the previous morning’s scan. The docs drift check runs weekly, on Sundays at 03:00.
The triage output feeds into the ops digest’s Kubescape collector, which reports findings as changes. If the triage step itself fails, the scan still counts as done and its results are still committed; the digest then shows the summary as stale.
How it relates to the other agents
Like the other agents, this one is deterministic: a script gathers the facts and fixed rules decide what changed. It needs no gate tool, because it writes only to its own results repository and its own summary file. The language model is not involved in the scan or the triage; Hermes only provides the scheduler that runs the job.
The config compliance agent also reads cluster state, but it checks timezones, logging coverage, workload provenance and the repo inventory, not security controls. The ops digest reads this agent’s triage summary through its “kubescape” collector, and for the compliance job it only checks that the job ran. No two of these agents share write access to a repository: each has its own output path.
Notes
The first triage run found a high-severity control failure, credentials in configuration, on one workload. In the full report it was one result among hundreds; in the summary it was one of a few lines.
The compliance score trend is tracked across scans, so the summary shows whether the security posture is getting better or worse over time. The initial scan results and C-0013 remediation posts document the baseline against which these trends are measured.