Skip to content

[BUG] Kubescape tool reports ApplicationProfile execs/opens/containers as 0 due to metadata-only LIST (spec is not fetched)Β #69

Description

@nkawaa

🎯 Affected Component(s)

pkg/kubescape (kagent-tools MCP server) β€” the list-style handlers for ApplicationProfile (and likely other spdx.softwarecomposition.kubescape.io resources whose list handlers read spec-derived fields, e.g. vulnerabilityManifests).

πŸ› Bug Description

Note: I think the Kubescape tool is still in an early/beta stage and not yet listed among the GA tools. I'm filing this in case the feedback is useful for hardening it before GA β€” please feel free to deprioritize if this tool is out of active scope.

Kubescape's Storage aggregated API server implements metadata-only LIST: a LIST request (e.g., kubectl get <custom resource> -A -o json) returns object metadata only and omits the heavy spec payload. The full spec is only returned by an individual GET on a named object (or via the documented fullSpec resourceVersion mechanism).

The Kubescape tool's list handlers appear to aggregate values from spec (e.g., spec.containers[].execs, spec.containers[].opens) using data obtained through a LIST call. Because LIST returns no spec, the aggregation counts nothing and returns 0 for all fields β€” even containers_count β€” while the objects actually contain data.

This is a documented behavior of Kubescape Storage. Per the kubescape/storage README:

a listed object comes back with its spec fields at their zero values (for example a VulnerabilityManifestSummary reports all severity counts as 0)

(the same applies to ApplicationProfile, whose spec.containers[] is likewise omitted on LIST)

πŸ”„ Steps To Reproduce

  1. Install the Kubescape operator with relevancy enabled (default with node-agent/eBPF), and let the learning period complete for at least one workload.
  2. Invoke kubescape_list_application_profiles through a kagent agent (or the MCP server directly).
  3. Observe that all profiles report total_execs: 0, total_opens: 0, total_syscalls: 0, containers_count: 0.

πŸ€” Expected Behavior

The tool should return the real spec-derived values (e.g., execs, opens, container counts) for each ApplicationProfile, or at minimum must not report 0 when the data is simply not present in a metadata-only LIST response.

Acceptable approaches:

  • Fetch each object with an individual GET (which returns the full spec) before aggregating, or
  • If only metadata is available, clearly distinguish "not fetched / unknown" from "0", instead of emitting 0.

Note: The storage's fullSpec resourceVersion mechanism is not applicable here β€” per the kubescape/storage README, fullSpec is rejected on LIST for applicationprofiles and networkneighborhoods (it is honored only for the summary resources). Individual GET is therefore the appropriate approach for these resources.

πŸ“± Actual Behavior

All ApplicationProfiles are reported with zeroed counters (trimmed):

{
  "application_profiles": [
    {
      "containers_count": 0,
      "created_at": "2026-06-04T08:41:49Z",
      "name": "replicaset-rockylinux-58468585c7",
      "namespace": "kagent",
      "total_capabilities": 0,
      "total_endpoints": 0,
      "total_execs": 0,
      "total_opens": 0,
      "total_syscalls": 0
    },
    { "... same zeros for every profile ..." }
  ]
}

However, an individual GET on the same objects returns real data, and the learning is marked complete:

$ kubectl get applicationprofile replicaset-rockylinux-58468585c7 -n kagent -o json \
  | jq '.spec.containers[]? | {name, execs:(.execs|length), opens:(.opens|length)}'
{
  "name": "rockylinux",
  "execs": 13,
  "opens": 115
}

$ kubectl get applicationprofile replicaset-rockylinux-58468585c7 -n kagent -o json \
  | jq '.metadata.annotations'
{
  "kubescape.io/completion": "complete",
  "kubescape.io/status": "completed",
  ...
}

So the data exists (execs: 13, opens: 115) and the profile is completed; the tool's 0 values come from LIST not returning spec, not from an actually-empty profile.

πŸ’» Environment

  • OS version: Rocky Linux 8.10 (x86_64) bastion; nodes on Amazon Linux
  • Kubernetes: v1.35.6 (EKS)
  • Kubernetes Provider: AWS (EKS)
  • kagent-tools / MCP server version: v0.2.1
  • Kubescape operator: v4.0.5 (Helm chart 1.40.2)

πŸ” Additional Context

Root cause:
In pkg/kubescape/kubescape.go (v0.2.1, around L741), the list handler fetches profiles with a LIST call and then reads spec-derived fields directly from each listed item:

profiles, err := k.spdxClient.ApplicationProfiles(queryNamespace).List(ctx, metav1.ListOptions{})
// ...
for _, profile := range profiles.Items {
    containersCount := len(profile.Spec.Containers)
    // ...
    for _, c := range profile.Spec.Containers {
        totalExecs += len(c.Execs)
        totalOpens += len(c.Opens)
        totalSyscalls += len(c.Syscalls)
        totalCapabilities += len(c.Capabilities)
        totalEndpoints += len(c.Endpoints)
    }
    // ... written into profileMap as containers_count / total_execs / ...
}

The List call uses an empty metav1.ListOptions{}. Since Kubescape Storage serves metadata-only LIST (the payload/spec is not loaded from disk), profile.Spec comes back zero-valued: profile.Spec.Containers is empty, so containers_count and every total_* counter sum to 0. The values are read from a response that, by design, never contains spec.

Suggested fix: GET each object individually to obtain spec before aggregating, or report the counters as "unknown/not fetched" rather than 0 when spec is unavailable.
(The fullSpec LIST mechanism cannot be used here: the storage server rejects it for applicationprofiles and networkneighborhoods.)

References:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions