Skip to content

Vulnerability Scanner

Container image CVEs and Kubernetes workload misconfigurations rolled up into a posture grade from A+ to F, reading your existing trivy-operator or running an on-demand self-scan — a non-AI feature that works with AI disabled.

Tier: Free — some features Pro


What it does

The Vulnerability Scanner surfaces container image vulnerabilities (CVEs) and Kubernetes workload misconfigurations, rolled up into a cluster posture score. The underlying capability is container scanning — a non-AI feature that works with AI disabled or entirely absent. It covers image vulnerability scanning (severity, affected package, installed vs. fixed version, and fix-availability), workload misconfiguration checks (privileged containers, runAsRoot, missing resource limits, dangerous capabilities, and other common hardening gaps), a posture dashboard with a letter grade from A+ to F and a 0-100 score, a severity breakdown, and a health trend over past scans, and a per-workload drill-down that expands into a full CVE list and misconfiguration findings, filterable by namespace and severity. The engine is Trivy, which covers both image CVE scanning and Kubernetes config-audit checks in one tool.

Scan-source adapter — "already have Trivy? Use it."

KubeManta detects whether trivy-operator is already installed in the cluster and, if so, reads its existing reports instead of scanning again. When the operator is present — VulnerabilityReport or ConfigAuditReport custom resources found — KubeManta reads those reports directly. This is read-only against the operator's own database: zero additional egress, no duplicate image pulls, and it respects whatever offline or air-gapped CVE database the operator was configured with. When the operator is absent, an admin can trigger an on-demand self-scan: KubeManta spawns an ephemeral Trivy Kubernetes Job that scans the target namespace's running workloads for both image CVEs and misconfigurations in one pass, then parses the results back into the same internal schema. Both paths write to the same tables, so the tab shows one unified view regardless of which source produced a finding — a source badge tells you which one is in play.

Choosing your scan source (BYO scanner)

The scan source is admin-selectable at runtime in the Settings sub-tab, persisted as a runtime setting that overrides the Helm-configured mode. Choosing a source is Free — you should never have to pay to point KubeManta at your own scanner. auto defers to the Helm-configured mode and lets an admin trigger a self-scan on demand; operator is the BYO path, where KubeManta deploys no Trivy and only reads your trivy-operator reports, with self-scan hard-disabled; and self has KubeManta deploy its own Trivy Kubernetes Job.

The Settings sub-tab also manages the platform's own scan Jobs. Listing recent self-scan Jobs and reading a Job's redacted Trivy logs are admin-only and Free; retrying a failed run and deleting a Job take the same admin + Expert Mode + Pro stack as running a scan.

Recurring self-scans can be configured at runtime — enabled/disabled, interval in hours, target namespaces, and source — and a background scheduler runs the scan when due, so a manual scan and a scheduled one never stack. Configuring the schedule is Free; the scan execution stays Pro and never runs when the effective source is operator.

The Job itself runs in KubeManta's own release namespace, never in the namespace being scanned: trivy k8s --include-namespaces <target> audits the target remotely through the API server, so the scanner needs no create/delete permission in your application namespaces. Two consequences are worth knowing before you go looking for it. It is not a workload of the namespace you scanned, and on a default install into kubemanta-system it does not appear under Workloads at all — that namespace is a system namespace and is filtered out of that view. Install into any other namespace — one not in the system blocklist and not prefixed kube-, gke-, gmp-, istio- or knative- — and the scan Job is listed under Workloads there like any other Job. Track a running scan on Security → Scanner → Built-in scan jobs, which lists every run with its live phase, or with kubectl get jobs -n <release-namespace> -l app.kubernetes.io/component=scanner.

Everything KubeManta deploys for scanning is name-prefixed kubemanta-scan-<run> and labelled app.kubernetes.io/managed-by: kubemanta plus app.kubernetes.io/component: scanner on both the Job and its pod template, so an admin can see KubeManta's entire scanner footprint at a glance with a single label selector query. To rely on your own scanner instead, one action removes it all — deleting the scanner (admin plus Expert Mode, Free) removes every label-matching Job cluster-wide and reconciles any still-running scan runs. It does not change the source setting, so switch the source to operator first so a later scan doesn't redeploy Trivy.

Watching a scan, and how it ends

A self-scan runs asynchronously: triggering it returns immediately with a run id, and the panel follows the Job from there. Trivy's own output streams into the panel while the scan works, so a long scan is visibly working rather than indistinguishable from a hung one. KubeManta deliberately does not estimate how long a scan will take — the honest answer depends on how many distinct images are in scope and whether the CVE database is already cached, and a promised time that is routinely wrong makes a working product look broken.

Every run ends in a state that says why. A scan that succeeds records its findings; one that fails records the reason alongside them — a Kubernetes deadline, a Job that could not start, or an out-of-memory kill, which names the memory limit and the setting that raises it. The reason is captured when the run finishes rather than looked up later, because the scan pod is garbage-collected within about half an hour and a reason that only exists on the pod is gone by the time anyone reads it.

Two settings bound a run, both in the Settings sub-tab. The deadline is how long a scan may take before Kubernetes stops it, defaulting to 60 minutes; it can be set to unlimited, which is the right choice for a large cluster whose scan duration you do not yet know. Unlimited is genuinely unbounded — nothing kills the Job — so a run that becomes unobservable is marked failed after a day rather than sitting at "running" forever. The memory limit defaults to 8Gi; Trivy holds image layers and the CVE database in memory, so the ceiling scales with the number of distinct images in scope.

A scan is many small pieces, not one long one

A namespace is scanned image by image: the misconfiguration checks run once for the whole namespace, then each distinct image is analysed on its own. Two things follow from that, and both are the point.

Memory stays flat. What a scan needs is set by the largest single image, not by how many images the namespace happens to run, so a 600-image namespace is not fundamentally harder than a 6-image one — it just takes longer.

Results are kept as they are found. Each image's findings are stored the moment that image finishes. A scan interrupted three-quarters of the way through — by its deadline, by memory pressure, by a node going away — keeps everything it had already found, and says how far it got.

Progress is visible in the scan's log as image 12/64, and the Tasks tray reports the run as live for as long as pieces keep arriving.

A re-scan only looks at what changed

Scanning an image means pulling it, and pulling is nearly all of the time a scan takes. So an image is skipped if the exact same image was already scanned in the last 24 hours.

"The same image" means the same digest, not the same tag. A tag can be moved — :latest today is not :latest tomorrow — so anything keyed on the tag would quietly keep showing you yesterday's findings for an image that changed underneath it. A digest cannot move: the same digest is the same bytes, so the earlier result is still true. KubeManta reads the digest from what Kubernetes already knows about your running pods, so this costs no extra registry calls and works for private registries too.

A skipped image is still covered. Its findings stay on the screen and still count toward the namespace's grade — the scan skips the download, not the result.

Three cases are always re-scanned, deliberately:

  • the image's digest changed since it was last scanned;
  • KubeManta cannot determine the digest — "we could not tell" is never treated as "it is fine";
  • the 24-hour window has passed. An image's contents never change, but the vulnerability database does: a CVE published today applies to an image built last year.

The practical effect is that the first scan of a large namespace is the slow one and everything after it is quick, and that a namespace too big to finish inside one scan's deadline makes real progress each time instead of restarting.

Set SECURITY_SCANNING_FRESHNESS_HOURS to change the window, or to 0 to turn the skipping off and scan every image every time.

When a scan runs out of memory — and how to stop it happening again

Trivy holds image layers and the CVE database in memory, so how much a scan needs depends on the size of what it is unpacking. There is no number that is correct for every cluster. Because a scan is made of independent pieces, a memory kill costs the piece it was working on rather than the run — but the run is still cut short, so the ceiling is worth setting properly.

So the scanner's resources are yours to set, in Settings → Vulnerability Scanner, and the same screen shows what a running scan is actually using against its ceiling. Watching that climb on your own cluster is the only reliable way to pick a limit; raising it after the third kill is the alternative.

Raise the request alongside the limit. Kubernetes schedules a pod on its request, so a large limit with a small request lands the scan on a node with no room for what it will really use — it then grows there and starts competing with your own workloads. The form refuses a request larger than its limit outright, because Kubernetes would reject that Job at creation and you would only see it as scanning having stopped.

Changes apply to the next scan. A run already in flight keeps the limits it started with — a pod's resources cannot be changed once it exists. Reset to install defaults puts you back on whatever your Helm values specify.

Images are analysed one at a time. Each image is scanned in its own short-lived process that exits before the next one starts, so peak memory is one image rather than the whole namespace — that process boundary, not a memory setting, is what stops a large namespace exhausting the limit. Peak memory is set by the largest single image in scope, not by how many images the namespace runs.

Because the vulnerability pass is serial, raising the memory limit does not make a scan faster. security.scanning.selfScan.parallel still applies to the misconfiguration pass, which reads Kubernetes objects rather than image layers and was never the expensive half. Raise the limit only if a scan is still being killed; the number to watch is the peak the Scanner tab reports.

Reaching your registries

The scanner pulls image layers itself, from inside its own pod, rather than through the kubelet. So it has to solve for itself the three things the kubelet already solved — the address, the credential, and the scheme. It does all three without configuration:

  • A registry named by a node-local address, such as an image published as localhost:30100/team/app. Inside the scan pod localhost is the scan pod, so that address is unreachable — but the registry is not. A NodePort is an ordinary Service, so KubeManta looks the port up cluster-wide and scans the image at the Service's in-cluster name. Nothing is assumed about the port number; whatever your cluster publishes is what gets resolved.
  • A private registry needing credentials. KubeManta reads the pull secrets the namespace already uses — both the ones named on each pod and the ones attached to the ServiceAccounts those pods run as — merges them into one docker config, and mounts it into the scan Job for the life of that scan. A namespace pulling from several private registries is covered, not just the first. A credential that had to be re-addressed (the NodePort case above) is copied onto the new address as well, because Trivy matches credentials by registry host.
  • A registry that serves plain HTTP. Trivy defaults to HTTPS, so an in-cluster registry with no TLS fails every image with http: server gave HTTP response to HTTPS client. KubeManta asks each registry in scope, once, what it speaks, and scans the plaintext ones with TLS verification disabled for those images only — a registry on ECR, GCR or Docker Hub in the same namespace is still verified normally. Where this applies, the log says so: the pull credential travels unencrypted to that registry, over the same path your kubelet already uses to pull the image.

A registry presenting a certificate this cluster does not trust is deliberately not downgraded automatically — that is indistinguishable from an interception, and it is a decision for you, not the scanner. Name it in security.scanning.selfScan.insecureRegistries (host and port, exactly as they appear in the image reference) to scan it anyway, or install the CA. Setting security.scanning.selfScan.autodetectInsecureRegistries: false turns the probe off entirely and requires TLS from every registry not on that list.

Whatever still cannot be reached is counted and named in Activity before the scan spends time on it, and a finished run states how many targets could not be scanned beside its findings. A scan that could not pull anything must never read as a clean bill of health.

When there was nothing to scan

Scanning a namespace that is running no containers does not produce a clean result — it produces no result. The run finishes as no images in scope, carrying the reason rather than a severity count, and it is deliberately left off the posture trend: a point at zero on that chart is a claim about your cluster, and a scan that read nothing has no claim to make. The history row wears a muted colour rather than the success green for the same reason.

If the namespace cannot be listed at all — RBAC refuses the read, or the API server is unreachable — the run is recorded as failed, not as empty. Those are different facts: one means there is nothing to find, the other means nobody could look, and on a security screen an unknown is read as not clean.

How the report gets back

The Job writes its report to a file and POSTs it to KubeManta, compressed, rather than printing it for the agent to read back from the pod log. That matters at real sizes: a production namespace produces a report tens of megabytes wide, and a container log past its size limit is rotated by dropping the OLDEST bytes — so a report read from the log would arrive starting mid-document and could not be parsed at all.

The upload authenticates with a single-use token minted when the Job is launched and bound to that one run, so the endpoint grants nothing beyond submitting a report for a scan that is already running. If the upload cannot happen at all — an agent that is unreachable, a NetworkPolicy in the way — the Job falls back to printing the report, which is what small namespaces always did successfully.

What the scan Job is allowed to do

The scan runs as its own Kubernetes identity, separate from KubeManta's. It can LIST workloads and configuration objects — deployments, pods, services, configmaps, roles, network policies and the like — because that is what a misconfiguration audit reads.

It cannot read Secrets. It cannot read logs, exec into anything, or write anything at all.

That separation is deliberate: the scan runs a third-party image (Trivy, and you can point it at your own build), so it gets an identity that says exactly what a scan needs and nothing that would be useful to anything else. KubeManta's own read access — which can include Secrets, if you enabled that — is never handed to it.

If you scope KubeManta to specific namespaces (rbac.clusterWide=false), the scanner is scoped the same way, to the same namespaces.

What the numbers count

A vulnerability count is a number of things to fix. One CVE in one image counts once, however many workloads run that image. If a sidecar with a CVE appears in seventy-four deployments, that is one thing to fix, and the posture number says one.

This matters because the alternative moves for the wrong reasons. Counting each workload separately means deploying the same image again raises your vulnerability count, though nothing became less secure — and it produces totals too large to plan against.

Misconfigurations are counted per workload, because that is what they are: a misconfiguration is a property of a workload's own manifest, so two deployments running as root are two separate things to fix.

The drill-down still names every affected workload. Collapsing happens only in the summary. Open a finding and you see each namespace and workload running the affected image, which is the question that needs a workload in the answer.

Posture grade, history, diff & export

The grade runs from A+ to F. It is capped by the worst unfixed severity band, then adjusted by finding density over the workloads covered and by fixable-weighting, where a finding that has a fix available and unapplied weighs more heavily than one with no fix at all. The denominator is distinct workloads covered, so one bad pod can't drag a large clean fleet to F, and the reason for the cap is always shown alongside the grade. Until something has actually been scanned the grade reads rather than a letter: no findings because nothing was looked at is a different statement from no findings after a scan. A health trend plots a per-scan 0-100 health score as a sparkline, higher meaning healthier. Scan history lists past runs, and a diff view shows what changed since the previous run of the same scope — new versus fixed vulnerabilities and misconfigurations. Findings support fixable-only and group-by-image filters, free-text search, and CSV/JSON export honoring the active filters.

Risk exclusions let you exclude a CVE or misconfig with a required reason; an exclusion drops the finding from both the grade and the default findings view, and is listed separately in an admin-only Exclusions sub-tab. Excluding shows what is about to be hidden and at what scope — one workload, one image, or cluster-wide — because "hide CVE-2024-1234" and "hide it everywhere" are different decisions. Basic exclusion — a reason, no expiry — plus list and revoke, is Free for admins; only the review date is Pro. A review date brings the finding back automatically on a chosen day, which is the difference between accepting a risk and forgetting it: a permanent exclusion keeps hiding a CVE long after everyone who agreed to it has moved on.

Setup

Container scanning is on by default, so the Security tab is present on a standard install with no extra flag, and what that default grants is READ-ONLY: the reader may read trivy-operator's report CRDs. It never scans on its own.

Running KubeManta's OWN scan needs two more things, and it is worth knowing which before you plan around them:

  • A Pro licence. On-demand scanning is a paid capability.
  • expertMode.enabled=true. A scan runs as a Kubernetes Job, and the Job is applied by the separate expert-exec pod. A default install does not deploy that pod, so the launch returns expert service unavailable. Note this is NOT rbac.allowWrites — the reader ServiceAccount never gains create on jobs at any setting, which is why running a scan does not widen what the agent itself can do to your cluster.

So on a default install:

  • You already run trivy-operator — nothing more to do. KubeManta reads its reports and the Security tab is populated.
  • You do not — the tab is present but has nothing to show until you add a Pro licence and reinstall with --set expertMode.enabled=true.

Set security.scanning.enabled=false to turn the whole feature off — the reader ClusterRole is then not broadened and a scan request is refused outright.

The rest of the Helm security.scanning values set the mode seed (auto — an admin can trigger a self-scan regardless of whether trivy-operator is installed; operator — hard-disables self-scan, trivy-operator's reports are the sole source of truth; or self — KubeManta deploys its own prefixed Trivy Job), the self-scan image (default aquasec/trivy:0.74.0), and its resource requests/limits (default 250m/1Gi request, 1/8Gi limit), and the scan deadline (selfScan.timeoutSecs, default 3600 — 60 minutes; 0 for unlimited). The mode value is only the seed: an admin can override it at runtime in the Settings sub-tab, and the stored runtime source then wins. Self-scan additionally requires Expert Mode, since the Job itself is created via the expert-exec privileged path rather than the reader ServiceAccount: install with --set expertMode.enabled=true to deploy the expert-exec pod, then switch Expert Mode on under Admin → System → System Settings. The Helm flag alone deploys the capability without turning it on.

Free covers reading any existing trivy-operator reports, and the full posture dashboard — score, severity breakdown, trend, drill-down — built from whatever has already been scanned; nothing here requires a license. Pro covers on-demand self-scan when no operator is installed, gated behind admin role, Expert Mode, and a Pro license. Scheduled recurring scans are configurable by any admin, though execution stays Pro, and the Alert Rules Engine includes a scan-severity-threshold condition that fires on the same deduped, suppression-filtered findings the tab shows, so you can alert when findings cross a severity threshold. A pre-deploy apply-gate that blocks risky images at the Resource Builder's Apply step remains a planned follow-up.

Troubleshooting

Self-scan requires Expert Mode, since the scan Job is launched via the same expert-exec path used for other privileged writes: install with --set expertMode.enabled=true, then switch Expert Mode on under Admin → System → System Settings. A self-scan attempted with either step missing returns a permissions error saying which one, rather than silently doing nothing.

Self-scan also needs egress to pull the Trivy vulnerability database, unless you're consuming an already-installed operator, which uses its own database. Air-gapped clusters are covered by mirroring trivy-db — and optionally trivy-java-db and the trivy-checks bundle — into an internal registry with oras copy, then setting security.scanning.selfScan.dbRepository to the mirrored reference. Under security.airgap.enabled a scan with no mirror configured is refused immediately with a 422 naming those values, rather than running until the deadline kills it.

Private-registry images are scanned with the credentials the scanned namespace already uses: KubeManta collects the pull secrets named on its pods and on the ServiceAccounts those pods run as, merges them, and mounts the result as Trivy's docker config for the life of that one scan. Trivy pulls layers itself, independent of the workload's own pull secrets, which is why it needs its own copy. If an image still reports as unauthenticated, the usual cause is a credential the agent may not read — rbac.readSecrets off, or a secret in a namespace it has no access to — and the run names that image rather than counting it clean.

When an image cannot be read, the run records why, in Trivy's own words — an authentication failure reads differently from a deleted tag or blocked egress, and the run detail shows the message rather than a generic "could not scan". Those failures are counted per image, with the affected workloads as a breakdown underneath: one unpullable sidecar shared by seventy workloads is one thing to fix, not seventy, and the count says so. Grouping is by cause, so the largest group is usually one registry — which is the shape of the fix.

A self-scan that fails after a long run is most often one of two things, and the run's recorded reason distinguishes them: DeadlineExceeded means the scan needed longer than the deadline allows — raise it, or set it to unlimited — while OOMKilled means Trivy ran out of memory on the images in scope, which the memory limit raises. Either way the run keeps every image it had already finished and records how far it got; the reason says why it stopped, not that nothing was kept.

A self-scan trigger returns a 409 for one of three reasons, and the response body says which. The effective scan source is operator, so KubeManta deploys no Trivy — switch it via the Settings sub-tab's source setting if you want it to run its own Job. A scan of the same namespace is already running. Or the persistent CVE-database cache, which is on by default and backed by a ReadWriteOnce volume, is holding scans to one at a time cluster-wide — wait for the namespace it names, or set security.scanning.selfScan.cache.enabled=false to scan namespaces in parallel.


Generated from KubeManta 1.2.0 — this page is rendered from the product's own documentation, so it cannot drift from what ships. Manifest built 2026-09-12T02:14:45Z.