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 findings list with one row per CVE that opens into every affected
workload, 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.
When trivy-operator stops, or cannot be checked¶
KubeManta counts trivy-operator as running only when its Deployment has at
least one ready replica. It finds that Deployment by the label the official
Helm chart and static manifest both set, app.kubernetes.io/name=trivy-operator,
and — if your install rewrote the labels — by the default name
trivy-operator. An install that changes both the chart name and the
Deployment name is not recognised and is treated as not running.
A stopped operator's reports keep their age on screen. Scaling
trivy-operator to zero, or uninstalling it, leaves its report resources in the
cluster, and KubeManta keeps reading them. Whenever it can read them, the
status reports how old the newest one is, whatever state the operator is in, so a frozen
set of findings is visibly frozen: with built-in scan as the source the badge
adds "also showing a stopped trivy-operator's reports, newest …", and with no
live source at all the Vulnerability Scanner shows the "Scanner removed" banner with
the same date. Under the default auto mode, built-in scan becomes the source
as soon as no running operator is seen; under operator mode nothing refreshes
those findings until the operator runs again.
An operator KubeManta cannot check is not counted as running. If the Kubernetes API refuses or times out the lookup, the status reads "trivy-operator status could not be checked" (and the Settings sub-tab "status unknown") rather than guessing either way, and its reports are not labelled as left behind by a removed operator. A confirmed answer from the last five minutes is kept through a brief API interruption so the source does not flip back and forth; after that, the operator is not treated as a live source until it can be seen again. The answer is refreshed at most once a minute, so starting or stopping the operator shows up within about a minute.
Runs and snapshots in scan history¶
Scan history holds two kinds of record, and they are shown differently on purpose. A run is a scan KubeManta executed itself — a built-in scan — so it has a start, an end and a duration. A snapshot is a reading of your trivy-operator's reports: taken when an admin refreshes from the operator, and once a day while the posture grade is computed. KubeManta did not run that scan and cannot know how long trivy-operator took over it, so a snapshot shows snapshot at and the time it was read where a run shows its duration. A snapshot is recorded complete: it never appears as running, and it is never listed under Built-in scan jobs, which holds only the scans KubeManta launched.
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. A scheduled scan never runs alongside another scan: it
waits for the running one to finish (see One scan at a time below).
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: it reads the target namespace through the API server
and pulls that namespace's images from their registries itself, 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
Vulnerability Scanner → Settings → 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. The deadline is how long a scan may take before
Kubernetes stops it, defaulting to 60 minutes. Set it with
security.scanning.selfScan.timeoutSecs, or at runtime as an admin with
PUT /security/scanner/timeout ({"timeout_secs": 0} for unlimited, null to
go back to the Helm value). Unlimited is the right choice for a large cluster
whose scan duration you do not yet know. It 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
and is set under Admin → Vulnerability Scanner (see below); Trivy holds
image layers and the CVE database in memory, so the ceiling scales with the
size of the images in scope.
One scan at a time¶
Only one built-in scan runs at a time across the whole install, whoever starts it and whichever namespace it covers. Each scan pulls every image of its namespace and the CVE database, so parallel scans are what load a cluster. While a scan runs, every admin's scan console shows it, with who started it, and starting another answers with the scan already running — its namespace, who started it, when, and how many images it has covered — and attaches the console to it. A scheduled scan waits its turn instead of being skipped: its namespaces run one after another as the scanner frees up. Any admin can cancel the running scan from the console; the run records who cancelled it.
To run several at once on a cluster sized for it, set
security.scanning.selfScan.maxConcurrentScans (default 1). With the persistent
CVE-database cache enabled it stays at one, because the cache volume can be
mounted by one scan pod at a time. The cache is on by default, so also set
security.scanning.selfScan.cache.enabled=false; each scan then downloads the
CVE database itself.
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 Admin → 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 that screen 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 podlocalhostis 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. The scan pod therefore needs to reach KubeManta's agent Service inside the cluster. If a piece cannot be uploaded — an agent that is unreachable, a NetworkPolicy of your own in the way — the Job prints it to the scan's log instead, and the run records what never arrived as not covered, never as clean.
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.
The CVSS v3.x column shows a CVSS v3.x base score, and nothing else. The built-in scanner reads it from NVD, or from the vendor (Red Hat, then GitHub) when NVD has none. trivy-operator chooses its own v3 base score and prefers a vendor's over NVD's. Each score sits in a small fixed-size shape: solid red for 9.0 and above, solid amber for 7.0 to 8.9, and solid grey below that. A CVE with no v3 score shows an empty outline with a dash. That happens with older CVEs that NVD scored only in v2, and with very new CVEs scored only in v4.0. Such a CVE is ranked by its severity, and its v2 or v4.0 number is never shown under the v3 heading, because the same number falls in different bands in each version.
Hover a score to see which score it is: its version, its source and its
vector, for example 3.1 · NVD · AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H. Only what
the scan report states is shown, never a guess:
- The version, 3.0 or 3.1, is read from the vector. A score reported without a vector shows as 3.x.
- trivy-operator does not say by default which source its score came from. It
says so when it is configured to include each vendor's scores
(
trivy.additionalVulnerabilityReportFieldscontainingCVSS). Otherwise the source shows as not stated. - A finding stored before 1.5.0 shows no version or source until its image is scanned again.
The CSV and JSON exports carry the same facts in cvss_version and
cvss_source, beside cvss. The source is the scanner's own name for it:
nvd, redhat or ghsa (GitHub). Both are empty when the report does not
state them.
Findings: one row per CVE¶
The Findings tab opens on one row per CVE — the unit you act on. Fixing a CVE means fixing it in the image, and every workload running that image is clean at once; a list with one row per workload makes the same problem look like seventy. Each row shows the worst severity the CVE was given, its highest CVSS v3.x score with the score's provenance on hover, the version that fixes it (or that there is no fix yet, and for how many of the affected images a fix exists), and how far it reaches: the number of workloads, images and sightings. Sort by severity, score, fixable first, or most workloads.
Open a row to see where it is: every workload running an affected image, a page at a time, with the image, the package and the installed and fixed versions, under the same namespace, severity, fix and search filters as the table, so the list always adds up to the row's own count. Exclude on a sighting accepts the risk for that workload, as before. Exclude on the CVE row accepts it for every workload in the selected namespace, or everywhere when no namespace is selected; the dialog says which, and how many sightings that hides, before you confirm.
Two other views are one click away and remembered per browser: By image, one row per image and CVE, for finding the base image that carries the most; and All sightings, the flat per-workload list. The counts on a CVE row match the other views and the posture numbers because they are read from the same findings, with the same exclusions applied: a risk accepted for one workload lowers that CVE's workload count by one, and a CVE accepted everywhere disappears from the list.
On the Workloads page¶
The same findings show on the Workloads page, in a CVSS column right after each workload's name. It appears on Deployments, StatefulSets, DaemonSets and Jobs when a finished scan found a non-excluded high or critical finding in the workload's images, and shows the worst CVSS v3 score in the same shape as the scanner. Critical badges there breathe; the scanner's own column stays still. On Pods the column is off by default, because a controller's score would repeat on each of its pods. Each user chooses Critical only or High and critical, and turns the column on Pods on or off, with View in the Workloads toolbar. The Workloads feature page describes the badge card.
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. The grade,
the count of workloads covered and the list of workloads with no scan result all
follow your namespace scope: a workload in a namespace you have left out of the
allowed namespaces is neither counted nor listed. A health
trend plots a per-scan 0-100 health score as a sparkline, higher meaning
healthier. Scan history lists past runs, each with the totals it recorded when
it finished, and a diff view shows what changed
since the previous run of the same scope — new versus fixed vulnerabilities
and misconfigurations. History, a run opened from it and the diff follow your
namespace scope as well: a run of a namespace you cannot open is not listed and
cannot be opened or compared, and a cluster-wide run shows only the findings,
and the counts, of the namespaces you can open. A diff names the worst 500 new
and fixed findings on each side, with the exact totals. Findings support
a severity filter, fixable-only, free-text search, and CSV, JSON, Markdown and
HTML export honoring the active filters and the view on screen: an export of
the By CVE view is one row per CVE, with fixed_versions, fixable_images,
images, workloads, sightings and packages beside the score and its
cvss_version and cvss_source. The Export menu also offers that sheet
directly, whatever view is open.
The findings list, its totals, the posture grade, the scan history and the export are computed in the agent's database, a page at a time, so they load in about the same time and memory whether a namespace has a thousand findings or a million. An export streams: it starts at once and does not hold the whole set in memory.
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 — This workload only, This image, anywhere, Every workload in this namespace or Everywhere — 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 Vulnerability Scanner is in the sidebar 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 NOTrbac.allowWrites: the scan Job is created through expert-exec either way. Whatrbac.allowWriteschanges is the reader ServiceAccount itself — with scanning on (security.scanning.enabled, the default) ANDrbac.allowWrites=true, it is also grantedcreateanddeleteonjobs. Withrbac.allowWrites=falseit never holds either verb, so on a read-only install 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 Vulnerability Scanner is populated.
- You do not — the page 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.
Troubleshooting¶
If the Vulnerability Scanner shows a stopped trivy-operator's reports, or says its
status could not be checked, check the operator from the cluster side.
kubectl get deploy -A -l app.kubernetes.io/name=trivy-operator should list it
with a ready replica; scale it back up, or switch the source to built-in scan
if you removed it on purpose. "Could not be checked" means KubeManta's reader
was refused or timed out listing Deployments — confirm the reader ServiceAccount
may list Deployments cluster-wide
(kubectl auth can-i list deployments -A --as=system:serviceaccount:<release-namespace>:<reader-service-account>).
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 two 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. Or another built-in scan is already running, in any namespace:
the body names its namespace, who started it and when, and the scan console
attaches to it. Wait for it to finish, or cancel it from the console. To allow
parallel scans, raise security.scanning.selfScan.maxConcurrentScans and set
security.scanning.selfScan.cache.enabled=false, because the persistent
CVE-database cache, on by default, holds scans to one at a time.
Generated from KubeManta 1.5.0 — this page is rendered from the product's own documentation, so it cannot drift from what ships. Manifest built 2026-11-03T12:53:14Z.