Documentation
¶
Overview ¶
Package models defines the shared data types produced by the collector and consumed by the analyzers, exclusions, scoring, and report packages. Snapshot holds cluster state; Finding is the unit analyzers emit; EscalationHop and related types describe privilege-escalation chains.
Index ¶
- type AdmissionSummary
- type CSR
- type CSRCondition
- type ConfigMapSnapshot
- type EscalationEdge
- type EscalationGraph
- type EscalationHop
- type EscalationNode
- type EscalationPath
- type EscalationTarget
- type Finding
- type FrameworkRef
- type KubectlPatch
- type MitreTechnique
- type PatchTarget
- type Reference
- type RemediationHint
- type ResourceRef
- type RiskCategory
- type Scope
- type ScopeLevel
- type ScoreFactors
- type SecretMetadata
- type Severity
- type Snapshot
- type SnapshotMetadata
- type SnapshotResources
- type SubjectRef
- type TruncationInfo
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AdmissionSummary ¶
type AdmissionSummary struct {
// Mode mirrors --admission-mode (off, attenuate, suppress).
Mode string `json:"mode"`
// Suppressed counts findings dropped because their namespace's PSA enforce
// label would block the underlying workload. Always 0 unless Mode == suppress.
Suppressed int `json:"suppressed"`
// Attenuated counts findings whose Score was multiplied by the admission
// mitigation factor because PSA would block the underlying workload. Always
// 0 unless Mode == attenuate.
Attenuated int `json:"attenuated"`
// AuditOnly counts findings whose namespace has audit-mode PSA labels (not
// enforce). These are tagged but never suppressed/attenuated, since audit
// does not reject creates or updates.
AuditOnly int `json:"audit_only,omitempty"`
// WarnOnly counts findings whose namespace has warn-mode PSA labels.
WarnOnly int `json:"warn_only,omitempty"`
// SuppressedByNamespace breaks down Suppressed by namespace then RuleID for
// per-namespace tooltips in the HTML report.
SuppressedByNamespace map[string]map[string]int `json:"suppressed_by_namespace,omitempty"`
// PolicyEnginesDetected lists policy engines whose resources were observed in
// the snapshot, sorted alphabetically (e.g. ["gatekeeper", "kyverno", "vap"]).
// Empty when none. Populated by the engine's policy-engine-presence stage.
PolicyEnginesDetected []string `json:"policy_engines_detected,omitempty"`
// PolicyEngineTagged counts findings that received an admission:policy-engine-detected:*
// tag. A single finding may carry tags for multiple engines, but it bumps this
// counter once per finding (not once per engine).
PolicyEngineTagged int `json:"policy_engine_tagged,omitempty"`
}
AdmissionSummary reports what the engine's admission-aware reweight stage did during a scan. It surfaces in the HTML report header, the JSON metadata block, and the SARIF run properties so consumers can audit how much risk was hidden or downweighted by namespace-level admission controls.
type CSR ¶ added in v1.1.0
type CSR struct {
Name string `json:"name"`
SignerName string `json:"signer_name,omitempty"`
Username string `json:"username,omitempty"` // identity that submitted the CSR
Usages []string `json:"usages,omitempty"` // x509 key usages requested
Groups []string `json:"groups,omitempty"` // groups asserted by the submitting identity
Conditions []CSRCondition `json:"conditions,omitempty"` // status conditions: Approved / Denied / Failed
Approved bool `json:"approved,omitempty"` // convenience: true when any Approved condition is present
Annotations map[string]string `json:"annotations,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
}
CSR is the minimal projection of a certificates.k8s.io/v1 CertificateSigningRequest the analyzers need. The raw CSR PEM is omitted (we never inspect signing-request payloads), but SignerName + Usages + the approval Conditions are kept so detectors can identify the kube-apiserver-client signers that lead to cluster-admin via system:masters.
type CSRCondition ¶ added in v1.1.0
type CSRCondition struct {
Type string `json:"type"` // "Approved", "Denied", "Failed"
Status string `json:"status,omitempty"` // "True", "False", "Unknown"
Reason string `json:"reason,omitempty"`
Message string `json:"message,omitempty"`
}
CSRCondition mirrors certificatesv1.CertificateSigningRequestCondition for the minimal CSR snapshot view: type, status, and optional reason / message.
type ConfigMapSnapshot ¶
type ConfigMapSnapshot struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Labels map[string]string `json:"labels,omitempty"`
Annotations map[string]string `json:"annotations,omitempty"`
Data map[string]string `json:"data,omitempty"`
}
ConfigMapSnapshot is a ConfigMap with its data redacted by the collector but keys preserved, so analyzers can still inspect key names.
type EscalationEdge ¶
type EscalationEdge struct {
From string `json:"from"`
To string `json:"to"`
Technique string `json:"technique"` // stable technique identifier, e.g. "KUBE-PRIVESC-001"
Action string `json:"action"` // short machine-friendly action label
Permission string `json:"permission,omitempty"` // RBAC permission or condition that enables this edge
Description string `json:"description"` // human-readable one-liner
Score float64 `json:"score,omitempty"`
}
EscalationEdge is a directed labeled edge describing how one subject can obtain another subject's identity or reach a sink.
type EscalationGraph ¶
type EscalationGraph struct {
Nodes map[string]*EscalationNode `json:"nodes"`
Edges []*EscalationEdge `json:"edges"`
}
EscalationGraph is the directed privilege-escalation graph: subject nodes, sink nodes, and labeled edges.
type EscalationHop ¶
type EscalationHop struct {
Step int `json:"step"` // 1-indexed position in the chain
Action string `json:"action"` // technique identifier, e.g. "pod_exec", "impersonate"
FromSubject SubjectRef `json:"from_subject"`
ToSubject SubjectRef `json:"to_subject"`
Permission string `json:"permission"` // RBAC permission or condition that enables the hop
Gains string `json:"gains"` // human-readable description of what the attacker obtained
}
EscalationHop is one step in a privilege-escalation chain: who moved to whom, which permission enabled it, and why.
type EscalationNode ¶
type EscalationNode struct {
ID string `json:"id"`
Subject SubjectRef `json:"subject,omitempty"`
IsSystem bool `json:"is_system,omitempty"` // built-in control-plane subjects; not traversed during path search
IsSink bool `json:"is_sink,omitempty"`
Target EscalationTarget `json:"target,omitempty"` // set only when IsSink is true
TargetNamespace string `json:"target_namespace,omitempty"` // populated only when Target == TargetNamespaceAdmin to identify which namespace the sink represents
}
EscalationNode represents either a subject (with Subject populated) or a terminal sink (IsSink=true) in the graph.
type EscalationPath ¶
type EscalationPath struct {
Source SubjectRef `json:"source"`
Target EscalationTarget `json:"target"`
TargetNamespace string `json:"target_namespace,omitempty"` // populated only when Target == TargetNamespaceAdmin
Hops []EscalationHop `json:"hops"`
}
EscalationPath is one source → sink chain returned by path search, with each hop annotated.
type EscalationTarget ¶
type EscalationTarget string
EscalationTarget enumerates the high-value "sinks" the privesc module searches for paths to.
const ( TargetClusterAdmin EscalationTarget = "cluster_admin_equivalent" TargetKubeSystemSecrets EscalationTarget = "kube_system_secrets" TargetNamespaceAdmin EscalationTarget = "namespace_admin" TargetNodeEscape EscalationTarget = "node_escape" TargetSystemMasters EscalationTarget = "system_masters" TargetTokenMint EscalationTarget = "token_mint" )
type Finding ¶
type Finding struct {
ID string `json:"id"` // deterministic unique key ("RULE:ns:name")
RuleID string `json:"rule_id"` // rule identifier, stable across runs
Severity Severity `json:"severity"` // bucketed severity used for filtering and display
Score float64 `json:"score"` // numeric 0–10 score, already clamped
Category RiskCategory `json:"category"` // risk category for grouping in the report
Title string `json:"title"`
Description string `json:"description"`
Subject *SubjectRef `json:"subject,omitempty"` // RBAC subject this finding is about, when applicable
Resource *ResourceRef `json:"resource,omitempty"` // cluster resource this finding is about, when applicable
Namespace string `json:"namespace,omitempty"`
Scope Scope `json:"scope,omitzero"` // explicit blast radius (cluster | namespace | workload | object)
Impact string `json:"impact,omitempty"` // one-line concrete blast-radius statement
AttackScenario []string `json:"attack_scenario,omitempty"` // ordered narrative steps an attacker would take
Evidence json.RawMessage `json:"evidence,omitempty"` // analyzer-specific JSON payload describing what was found
Remediation string `json:"remediation"` // one-line summary fix
RemediationSteps []string `json:"remediation_steps,omitempty"` // ordered concrete actions, kubectl/YAML examples allowed
References []string `json:"references,omitempty"`
LearnMore []Reference `json:"learn_more,omitempty"` // structured references (Title + URL)
MitreTechniques []MitreTechnique `json:"mitre_techniques,omitempty"` // ATT&CK technique IDs for Containers / Kubernetes
EscalationPath []EscalationHop `json:"escalation_path,omitempty"` // populated by the privesc module
Frameworks []FrameworkRef `json:"frameworks,omitempty"` // compliance/hardening controls this rule maps to (CIS, NSA, …); populated post-analysis from the static mapping table
Excluded bool `json:"excluded"` // set post-analysis by the exclusions matcher
ExclusionReason string `json:"exclusion_reason,omitempty"`
Tags []string `json:"tags,omitempty"` // free-form labels like "module:rbac", "check:wildcardVerbs"
// RemediationHint is the structured fix payload: a kubectl patch and / or equivalent
// Kyverno / Gatekeeper policies and / or a minimal RBAC diff. Optional and additive —
// nil means the analyzer hasn't supplied a structured fix yet, in which case JSON
// consumers and the HTML report fall back to the prose Remediation + RemediationSteps
// fields. Populated by the per-analyzer remediation generators landed in Wave 1.
RemediationHint *RemediationHint `json:"remediation_hint,omitempty"`
// ScoreFactors carries the composite-formula inputs that produced Finding.Score, when
// the analyzer chose to populate them. Nil keeps the existing hand-picked-score path
// intact; the scoring-tooltip in the HTML report degrades gracefully when nil.
ScoreFactors *ScoreFactors `json:"score_factors,omitempty"`
}
Finding is the common output of every analyzer module: a scored, categorized observation tied to a subject or resource.
The richer fields below (Scope, Impact, AttackScenario, RemediationSteps, LearnMore, MitreTechniques) are optional but strongly preferred over a single-paragraph Description+Remediation. They power the structured HTML report sections so a senior reviewer can grasp blast radius at a glance and a junior engineer can follow the attack scenario and remediation steps.
type FrameworkRef ¶ added in v1.1.0
type FrameworkRef struct {
Framework string `json:"framework"` // canonical framework slug, e.g. "CIS-1.9" or "NSA-CISA-1.2"
Control string `json:"control"` // control identifier within the framework, e.g. "5.1.3"
Title string `json:"title,omitempty"` // human-readable title of the control
URL string `json:"url,omitempty"` // optional deep link to the control description
}
FrameworkRef cites one control in an external compliance or hardening framework that a finding maps to. Decoupled from MitreTechnique because MITRE is an attacker-technique taxonomy whereas Frameworks are auditor-facing compliance claims (CIS Kubernetes Benchmark, NSA/CISA Kubernetes Hardening Guide, …).
type KubectlPatch ¶ added in v1.1.0
type KubectlPatch struct {
// Type is the patch strategy: "strategic" (kubectl default), "merge" (RFC 7396), or
// "json" (RFC 6902). Mirrors kubectl's --type flag.
Type string `json:"type"`
// Target identifies the cluster object the patch applies to.
Target PatchTarget `json:"target"`
// Body is the raw patch payload as JSON. For strategic-merge / merge patches this is
// the partial object spec; for JSON patches it is the operation array.
Body json.RawMessage `json:"body"`
// Command is the pre-rendered shell command (e.g. `kubectl patch deployment foo -n bar
// --type=strategic --patch '{...}'`) so the HTML report can render it inside a copy
// button without reconstructing it client-side.
Command string `json:"command,omitempty"`
}
KubectlPatch is the structured form of a `kubectl patch <kind> <name> -p <body>` command. Body is the raw JSON patch payload; Command is the pre-rendered shell command for HTML display. Holding both lets the JSON output stay machine-readable while the HTML report shows a copy-pasteable string without re-rendering on the client side.
type MitreTechnique ¶
type MitreTechnique struct {
ID string `json:"id"` // e.g. "T1611"
Name string `json:"name"` // e.g. "Escape to Host"
URL string `json:"url"` // e.g. "https://attack.mitre.org/techniques/T1611/"
}
MitreTechnique names a single MITRE ATT&CK technique relevant to the finding (Containers / Kubernetes matrices).
type PatchTarget ¶ added in v1.1.0
type PatchTarget struct {
Kind string `json:"kind"`
APIVersion string `json:"api_version,omitempty"`
Namespace string `json:"namespace,omitempty"`
Name string `json:"name"`
}
PatchTarget identifies one Kubernetes object by Kind + APIVersion + Name and, when namespaced, Namespace. Used by KubectlPatch to scope the patch to a single resource.
type Reference ¶
Reference is a structured external citation (e.g. CIS benchmark, MITRE ATT&CK technique, K8s docs). Title is what a reader sees; URL is the link target.
type RemediationHint ¶ added in v1.1.0
type RemediationHint struct {
// Patch is a kubectl strategic-merge / merge / JSON patch against a specific target.
Patch *KubectlPatch `json:"patch,omitempty"`
// KyvernoPolicy is raw YAML for an equivalent Kyverno ClusterPolicy that would have
// blocked the offending configuration at admission time.
KyvernoPolicy string `json:"kyverno_policy,omitempty"`
// GatekeeperPolicy is raw YAML for an OPA Gatekeeper ConstraintTemplate + Constraint
// pair equivalent to KyvernoPolicy. Both fields can be populated for the same finding.
GatekeeperPolicy string `json:"gatekeeper_policy,omitempty"`
// RBACDiff is a unified diff of the smallest (Cluster)RoleBinding / (Cluster)Role edit
// that breaks the privilege chain. Used by privesc + rbac findings, where a kubectl
// patch is awkward and a "delete subject from binding" change is the right answer.
RBACDiff string `json:"rbac_diff,omitempty"`
}
RemediationHint is the structured fix payload attached to a Finding. Every field is optional so analyzers can fill in whichever surface they support: a kubectl patch (the imperative fix), a Kyverno / Gatekeeper ClusterPolicy (the admission-time prevention), and an RBAC diff (the minimal binding / role edit that breaks a privesc chain). HTML renders these conditionally; JSON serializes them as a single nested object; SARIF surfaces the whole struct as one extra property so downstream tools can parse it.
type ResourceRef ¶
type ResourceRef struct {
Kind string `json:"kind"`
Name string `json:"name"`
Namespace string `json:"namespace,omitempty"`
APIGroup string `json:"api_group,omitempty"`
}
ResourceRef identifies a Kubernetes object by kind, name, and optional namespace/APIGroup.
func (ResourceRef) Key ¶
func (r ResourceRef) Key() string
Key returns the canonical "Kind/[Namespace/]Name" identifier for use in maps and log output.
type RiskCategory ¶
type RiskCategory string
RiskCategory classifies what kind of security impact a Finding represents for use in summaries and dashboards.
const ( CategoryPrivilegeEscalation RiskCategory = "privilege_escalation" CategoryDataExfiltration RiskCategory = "data_exfiltration" CategoryLateralMovement RiskCategory = "lateral_movement" CategoryInfrastructureModification RiskCategory = "infrastructure_modification" CategoryDefenseEvasion RiskCategory = "defense_evasion" )
type Scope ¶
type Scope struct {
Level ScopeLevel `json:"level"` // cluster | namespace | workload | object
Detail string `json:"detail,omitempty"` // human-readable, e.g. "Cluster-wide (all namespaces)" or "Namespace: prod (12 secrets, 4 service accounts)"
}
Scope captures the explicit blast radius of a finding. It is what the reader sees when asking "how much of the cluster is exposed by this single finding?". Level is the bucket used for sorting/filtering; Detail is the human-readable description that names specific namespaces, workload, or object.
type ScopeLevel ¶
type ScopeLevel string
ScopeLevel is the bucketed scope level for filtering and sorting.
const ( ScopeCluster ScopeLevel = "cluster" // affects every namespace / cluster-scoped object ScopeNamespace ScopeLevel = "namespace" // affects one namespace ScopeWorkload ScopeLevel = "workload" // affects one workload (Deployment/DaemonSet/StatefulSet/Job/CronJob/Pod) ScopeObject ScopeLevel = "object" // affects a single object (Secret, ConfigMap, NetworkPolicy, etc.) )
func (ScopeLevel) Label ¶
func (s ScopeLevel) Label() string
Label returns the human-readable label for a scope level.
func (ScopeLevel) Rank ¶
func (s ScopeLevel) Rank() int
Rank returns an integer ordering: cluster > namespace > workload > object. Used to sort/filter by blast radius.
type ScoreFactors ¶ added in v1.1.0
type ScoreFactors struct {
Base float64 `json:"base"`
Exploitability float64 `json:"exploitability"`
BlastRadius float64 `json:"blast_radius"`
ChainModifier float64 `json:"chain_modifier"`
}
ScoreFactors is the JSON-serializable companion to scoring.Factors. It carries the four inputs of the composite formula (base × exploitability × blast + chain) so the HTML report's scoring tooltip can show why a finding got the score it got. Decoupled from scoring.Factors to avoid a models→scoring import cycle: the scoring package converts a Factors into a *ScoreFactors when the analyzer asks for both back via scoring.ComposeWithFactors.
type SecretMetadata ¶
type SecretMetadata struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Type corev1.SecretType `json:"type,omitempty"`
Annotations map[string]string `json:"annotations,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
}
SecretMetadata stores Secret identifying info and labels/annotations only; raw data is intentionally never collected.
type Severity ¶
type Severity string
Severity is the bucketed severity level attached to every Finding.
func ParseSeverity ¶
ParseSeverity parses a case-insensitive severity string; an empty input is accepted as SeverityInfo.
type Snapshot ¶
type Snapshot struct {
Metadata SnapshotMetadata `json:"metadata"`
Resources SnapshotResources `json:"resources"`
}
Snapshot is the in-memory representation of everything the collector pulled from a cluster; analyzers consume Snapshot and never talk to the API server themselves.
func NewSnapshot ¶
func NewSnapshot() Snapshot
NewSnapshot returns a Snapshot seeded with the current UTC timestamp and a neutral CloudProvider default.
type SnapshotMetadata ¶
type SnapshotMetadata struct {
KubesplainingVersion string `json:"kubesplaining_version"`
SnapshotTimestamp string `json:"snapshot_timestamp"`
ClusterName string `json:"cluster_name,omitempty"`
ClusterVersion string `json:"cluster_version,omitempty"`
APIServerURL string `json:"api_server_url,omitempty"`
CloudProvider string `json:"cloud_provider,omitempty"`
CollectorIdentity string `json:"collector_identity,omitempty"`
PermissionsAvailable []string `json:"permissions_available,omitempty"`
PermissionsMissing []string `json:"permissions_missing,omitempty"`
CollectionWarnings []string `json:"collection_warnings,omitempty"`
NamespacesScanned []string `json:"namespaces_scanned,omitempty"`
CollectionDurationSecond float64 `json:"collection_duration_seconds,omitempty"`
}
SnapshotMetadata records provenance and collection-time context so downstream consumers can reason about what is/isn't present.
type SnapshotResources ¶
type SnapshotResources struct {
Roles []rbacv1.Role `json:"roles,omitempty"`
ClusterRoles []rbacv1.ClusterRole `json:"cluster_roles,omitempty"`
RoleBindings []rbacv1.RoleBinding `json:"role_bindings,omitempty"`
ClusterRoleBindings []rbacv1.ClusterRoleBinding `json:"cluster_role_bindings,omitempty"`
ServiceAccounts []corev1.ServiceAccount `json:"service_accounts,omitempty"`
Pods []corev1.Pod `json:"pods,omitempty"`
Deployments []appsv1.Deployment `json:"deployments,omitempty"`
DaemonSets []appsv1.DaemonSet `json:"daemon_sets,omitempty"`
StatefulSets []appsv1.StatefulSet `json:"stateful_sets,omitempty"`
Jobs []batchv1.Job `json:"jobs,omitempty"`
CronJobs []batchv1.CronJob `json:"cron_jobs,omitempty"`
SecretsMetadata []SecretMetadata `json:"secrets_metadata,omitempty"`
ConfigMaps []ConfigMapSnapshot `json:"config_maps,omitempty"`
Namespaces []corev1.Namespace `json:"namespaces,omitempty"`
Nodes []corev1.Node `json:"nodes,omitempty"`
Services []corev1.Service `json:"services,omitempty"`
NetworkPolicies []networkingv1.NetworkPolicy `json:"network_policies,omitempty"`
// PersistentVolumes and PersistentVolumeClaims power the PV-hostPath bypass check
// (KUBE-PV-HOSTPATH-001): a Pod can mount a PVC whose backing PV uses a sensitive
// hostPath (`/`, `/etc`, `/var/run/docker.sock`, `/var/lib/kubelet`, ...). PSA cannot
// see through PVCs, so this is a real Baseline bypass. Both fields are populated by
// the collector; missing-permission errors degrade them to empty (warning, not fatal).
PersistentVolumes []corev1.PersistentVolume `json:"persistent_volumes,omitempty"`
PersistentVolumeClaims []corev1.PersistentVolumeClaim `json:"persistent_volume_claims,omitempty"`
// CertificateSigningRequests are the cluster's pending / approved CSRs. They drive
// the KUBE-PRIVESC-011 detection (a subject that can both create CSRs and approve
// them at the `certificatesigningrequests/approval` subresource can mint a
// kubelet-signed client cert carrying any Subject / Organization, including
// `O=system:masters` which the apiserver hard-codes as cluster-admin).
CertificateSigningRequests []CSR `json:"certificate_signing_requests,omitempty"`
ValidatingWebhookConfigs []admissionregistrationv1.ValidatingWebhookConfiguration `json:"validating_webhook_configs,omitempty"`
MutatingWebhookConfigs []admissionregistrationv1.MutatingWebhookConfiguration `json:"mutating_webhook_configs,omitempty"`
// ValidatingAdmissionPolicies and ValidatingAdmissionPolicyBindings are the in-tree
// CEL-based admission policies (GA in Kubernetes v1.30). Phase 2 collects them for
// presence detection; Phase 3 will evaluate the CEL expressions offline.
ValidatingAdmissionPolicies []admissionregistrationv1.ValidatingAdmissionPolicy `json:"validating_admission_policies,omitempty"`
ValidatingAdmissionPolicyBindings []admissionregistrationv1.ValidatingAdmissionPolicyBinding `json:"validating_admission_policy_bindings,omitempty"`
// KyvernoClusterPolicies and KyvernoPolicies hold Kyverno (Cluster)Policies as
// unstructured.Unstructured so we don't take a typed dependency on Kyverno's CRDs.
// Split mirrors the (Cluster)Role precedent so consumers preserve scope.
KyvernoClusterPolicies []unstructured.Unstructured `json:"kyverno_cluster_policies,omitempty"`
KyvernoPolicies []unstructured.Unstructured `json:"kyverno_policies,omitempty"`
// GatekeeperConstraintTemplates are the cluster's OPA Gatekeeper templates. Phase 2
// uses presence as a "Gatekeeper installed" signal; per-constraint instances are
// dynamically-typed CRDs and are deferred to Phase 3/4.
GatekeeperConstraintTemplates []unstructured.Unstructured `json:"gatekeeper_constraint_templates,omitempty"`
}
SnapshotResources holds the collected Kubernetes objects grouped by kind; empty slices are allowed and common.
type SubjectRef ¶
type SubjectRef struct {
Kind string `json:"kind"`
Name string `json:"name"`
Namespace string `json:"namespace,omitempty"`
}
SubjectRef identifies an RBAC subject (User, Group, or ServiceAccount) and, when applicable, its namespace.
func (SubjectRef) Key ¶
func (s SubjectRef) Key() string
Key returns the canonical "Kind/[Namespace/]Name" identifier for use in maps and log output.
type TruncationInfo ¶ added in v1.1.0
type TruncationInfo struct {
// Truncated is true only when the cap actually fired (Original > Limit
// and AllFindings was not set).
Truncated bool `json:"truncated"`
// Original is the size of the findings slice before the cap was applied,
// after exclusions and severity filtering.
Original int `json:"original"`
// Shown is min(Original, Limit) when Truncated; equals Original otherwise.
Shown int `json:"shown"`
// Limit is the --max-findings value that produced this state. Zero when
// --all-findings was passed.
Limit int `json:"limit"`
}
TruncationInfo records that the report's findings list was capped before being written. It is rendered as a banner in the HTML report, surfaced as a one-line stderr notice during scans, embedded in the SARIF run properties, and persisted as truncation-info.json so `kubesplaining report` can re-render the banner without re-running analysis.
Truncated is the gate flag: when false (the zero value), the struct carries no information and downstream renderers treat it as "no cap was applied."