bug : partially unreadable host-sensor data silently becomes a control pass (no coverage signal)
Description
When the node-agent reports N items of a host-sensor resource (e.g. OpenPortsList, KubeletInfo) but only M < N can be decoded, the dropped nodes vanish from the scan: reportCollectionGaps (core/pkg/hostsensorutils/hostsensorcollectcrds.go:53-58) emits only a Warning with read/reported counts and records nothing. The surviving M envelopes evaluate normally, and with zero failures the control reports Passed — indistinguishable from a fully inspected fleet. There is no finding, no skip, and no coverage marker for the unreadable nodes.
This is the cluster-scan counterpart to #3832 (unknown-severity CVEs silently passing --severity-threshold): an indeterminate input silently becomes a pass instead of a marked one.
Evidence on master (ranges verified as of Sep 2026):
core/pkg/hostsensorutils/hostsensorcollectcrds.go:27-30:dropped() = listed − convertedquantifies the loss, but the partial branch (:53-58) never callsaddInfoToMap, unlike the total-loss branch directly above it (:47-49).processorhandler.go:1281-1304: Pass-inference walks only arrived input resources; anything not failed is stampedStatusPassed(:1298). Dropped nodes never arrive, so nothing about them is ever recorded.processorhandler.go:1362-1374&:1300-1302:hasUnreachableDependencychecksInfoMap[gvr].InnerStatus == StatusSkippedand stamps passes withSubStatusIncompleteCoverage. The machinery exists; partial loss currently bypasses it because noInfoMapentry is written.core/pkg/hostsensorutils/utils.go:9-17:addInfoToMapwrites exactly the keyedStatusSkippedentries that the downstream coverage gate checks.
Environment
OS: Ubuntu 22.04 LTS
Version: master (cluster scan with node-agent host-sensor data; ranges verified on current tree)
Steps To Reproduce
- In a cluster with
node-agentreporting per-node host-sensor CRDs (e.g., 10 nodes reportingOpenPortsList). - Make a subset of envelopes undecodable (newer agent payload shape, truncated items) such that N are listed and M < N are converted (or hermetically stub the collection layer with N listed / M converted).
- Run a cluster scan covering a control that evaluates that resource.
- Observe: One Warning log line with read/reported counts; the control reports
Passedover the M readable nodes; zero findings, zero skips, and zero coverage markers for the N − M dropped nodes.
Expected behavior
- Partial conversion loss is recorded via the same
addInfoToMapsignal as total loss (with an error message naming read-vs-reported counts), causinghasUnreachableDependencyto trigger so that every pass produced by the affected control carriesSubStatusIncompleteCoveragein the report and JSON output. - The M readable nodes evaluate exactly as today: findings are neither discarded nor skipped; only the incomplete coverage marker is attached.
- Per-node identity of dropped envelopes remains out of scope for this issue (proposed follow-up).
Actual Behavior
Partial loss is warn-only in server logs. The control passes over the surviving subset with no incomplete coverage marker, and the final report is indistinguishable from a scan where all N nodes were successfully inspected and clean.
Additional context
Proposed approach (minimal, riding existing plumbing without schema changes):
// hostsensorcollectcrds.go, in reportCollectionGaps, dropped() > 0 branch:
err := fmt.Errorf("node-agent reported %d %s but only %d could be read", collected.listed, resource.String(), collected.converted)
addInfoToMap(resource, infoMap, err)
logger.L().Ctx(ctx).Warning("Some CRD items could not be read", /* unchanged fields */)Active verifications being validated in build:
- Confirm that host-sensor GVR keys from
MapHostSensorResourceToApiGroupappear inResourceToControlsMapfor the affected controls (to ensure the coverage marker triggers without requiring an intermediate key-mapping step). - Confirm that
CollectResourcespasses the activeopap.InfoMap(and not an ephemeral discarded map) intoreportCollectionGaps.
Testing plan:
Hermetic unit tests with stubbed collection fixtures:
- N listed / M < N converted → resulting passes carry
SubStatusIncompleteCoverage. - N listed / N converted → no coverage marker (clean path unaffected).
- 0 converted → existing full-loss behavior preserved.
Source: kubescape/kubescape