Skip to content

feat(security): derive themed, deduped backlog entries from live Kubescape findings - #2876

Merged
devantler merged 40 commits into
mainfrom
claude/kubescape-backlog-bridge-2854
Aug 2, 2026
Merged

feat(security): derive themed, deduped backlog entries from live Kubescape findings#2876
devantler merged 40 commits into
mainfrom
claude/kubescape-backlog-bridge-2854

Conversation

@devantler

@devantler devantler commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

🤖 Generated by the Agentic Engineer

Why

Live-only security findings — posture controls that only fail once workloads run, and CVEs that only exist against a pulled image — never reach the backlog on their own, because the engineer's survey is GitHub-only. Today they get worked when a human happens to notice, which is the structural reason posture drift goes unnoticed for stretches.

While building this I measured something that changes the design: Kubescape's apiserver returns spec-stripped objects on LIST. The same workload reads critical=0 via kubectl get <crd> -A and critical=18 via a per-object GET. A bridge fed from LIST output would report a spotless cluster forever while looking perfectly healthy — the exact "a broken scanner and a compliant one read identically" failure this program exists to prevent. So the tool refuses that shape rather than trusting it.

What

The report-only half of the bridge. It reads per-object Kubescape summaries and derives themed backlog entries — one per failed control, one per CVE severity class, never one per resource — each with a fingerprint that deliberately excludes counts and timestamps, so unchanged cluster state re-derives byte-identical output and a fluctuating count updates rather than re-files.

Controls the platform has already accepted are excluded before anything is queued: the tool reads the same declared-exception artifact the CI scan uses, so the two cannot disagree about what counts as accepted. Filtering is per-workload, so an exception scoped to one workload never silences the same control elsewhere.

Issue writes are the default-off half and are refused behind -mode; both states are tested. Findings carry only the sanitized minimum (class + affected public component), with reachability evidence left out.

Exercised against the live prod cluster in both shapes: the stripped LIST fails closed with an actionable message, and real per-object output produces correctly grouped, sanitized themes.

Part of #2451 and #2854.

This slice deliberately leaves #2854 open: the write half (at-most-one-issue-per-theme, close-when-gone) is still outstanding and carries its own acceptance criteria.

…scape findings

Report-only first slice of the live-finding ingestion bridge (#2854).
Groups findings into themes (one per failed posture control, one per CVE
severity class) rather than per resource, and gives each a fingerprint
that excludes counts and timestamps so unchanged state never re-files.

Fails closed on Kubescape's spec-stripped LIST shape: measured on prod,
one workload reads critical=0 via LIST and critical=18 via a per-object
GET, so a LIST-fed bridge would report a spotless cluster forever.

Writes stay off behind -mode; both states are tested.
… input

Self-review findings. A control reported at different severities by
different workloads took whichever the map yielded first, so the field
depended on iteration order in a struct whose whole contract is
determinism; it now keeps the highest, and the report surfaces it.

An invocation with no input reported "nothing to file", which is the
same false all-clear the stripped-LIST guard exists to prevent.
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Exercised as a user against the live prod cluster at b72fb0954fb80e20c59abf259250b6458cad65f5:

  • Fed kubectl get vulnerabilitymanifestsummaries -A -o json (the stripped LIST shape, 120 objects): exit 1, naming the per-object GET it needs. It does not report a clean cluster.
  • Fed real per-object GET output: produced correctly grouped themes, one per severity class, components rendered as namespace/kind/name only.
  • Ran twice on unchanged input: byte-identical output — the anti-churn acceptance check.
  • No input: exit 1 rather than an all-clear.

Ablation — each guarantee was removed in place and the covering test confirmed to go red: component sorting (fingerprint stability), the stripped-LIST guard, the failed-only control filter, severity-count independence, the highest-severity rule, and the empty-input guard. Restored green after each.

Self-review found and fixed two defects before requesting review: theme severity depended on map iteration order, and an empty invocation reported "nothing to file".

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

⚠️MegaLinter analysis: Success with warnings

⚠️ BASH / bash-exec - 5 errors
Results of bash-exec linter (version 5.3.9)
See documentation on https://megalinter.io/9.6.0/descriptors/bash_bash_exec/
-----------------------------------------------

✅ [SUCCESS] scripts/dr-rebuild-supersession-guard.sh
❌ [ERROR] scripts/ghcr-auth-lib.sh
    Error: File:[scripts/ghcr-auth-lib.sh] is not executable

✅ [SUCCESS] scripts/guard-cilium-homogeneous-device-rollout.sh
✅ [SUCCESS] scripts/megalinter-scan-counts.sh
✅ [SUCCESS] scripts/normalize-sarif-paths.sh
❌ [ERROR] scripts/refresh-flux-ghcr-auth-safety.sh
    Error: File:[scripts/refresh-flux-ghcr-auth-safety.sh] is not executable

✅ [SUCCESS] scripts/refresh-flux-ghcr-auth.sh
✅ [SUCCESS] scripts/run-ksail-prod-with-pull-auth.sh
✅ [SUCCESS] scripts/summarize-sarif-findings.sh
✅ [SUCCESS] scripts/tests/test-cilium-bandwidth-manager-component.sh
✅ [SUCCESS] scripts/tests/test-cilium-homogeneous-devices-activation.sh
✅ [SUCCESS] scripts/tests/test-cilium-homogeneous-devices-autoscaler-gate.sh
✅ [SUCCESS] scripts/tests/test-cilium-homogeneous-devices-flux-wait.sh
✅ [SUCCESS] scripts/tests/test-dr-rebuild-supersession-guard.sh
✅ [SUCCESS] scripts/tests/test-kyverno-admission-vpa.sh
❌ [ERROR] scripts/tests/test-normalize-sarif-paths.sh
    Error: File:[scripts/tests/test-normalize-sarif-paths.sh] is not executable

✅ [SUCCESS] scripts/tests/test-openbao-oidc-role.sh
❌ [ERROR] scripts/tests/test-opencost-usage-scraper.sh
    Error: File:[scripts/tests/test-opencost-usage-scraper.sh] is not executable

❌ [ERROR] scripts/tests/test-refresh-flux-ghcr-auth-safety.sh
    Error: File:[scripts/tests/test-refresh-flux-ghcr-auth-safety.sh] is not executable

✅ [SUCCESS] scripts/tests/test-restrict-tenant-secret-stores.sh
✅ [SUCCESS] scripts/tests/test-summarize-sarif-findings.sh
✅ [SUCCESS] scripts/validate-alert-coverage.sh
✅ [SUCCESS] scripts/wait-for-platform-flux-revision.sh
⚠️ REPOSITORY / checkov - 37 errors
oo many. Please use IDE of your choice to review the file.
Check: CKV_K8S_38: "Ensure that Service Account Tokens are only mounted where necessary"
	FAILED for resource: Deployment.cdi.cdi-operator
	File: /k8s/bases/infrastructure/controllers/cdi/cdi-operator.yaml:5671-5779
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-35

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_15: "Image Pull Policy should be Always"
	FAILED for resource: Deployment.cdi.cdi-operator
	File: /k8s/bases/infrastructure/controllers/cdi/cdi-operator.yaml:5671-5779
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-14

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_13: "Memory limits should be set"
	FAILED for resource: Deployment.cdi.cdi-operator
	File: /k8s/bases/infrastructure/controllers/cdi/cdi-operator.yaml:5671-5779
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-12

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_11: "CPU limits should be set"
	FAILED for resource: Deployment.cdi.cdi-operator
	File: /k8s/bases/infrastructure/controllers/cdi/cdi-operator.yaml:5671-5779
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-10

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_22: "Use read-only filesystem for containers where possible"
	FAILED for resource: Deployment.cdi.cdi-operator
	File: /k8s/bases/infrastructure/controllers/cdi/cdi-operator.yaml:5671-5779
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-21

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_40: "Containers should run as a high UID to avoid host conflict"
	FAILED for resource: Deployment.cdi.cdi-operator
	File: /k8s/bases/infrastructure/controllers/cdi/cdi-operator.yaml:5671-5779
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-37

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_43: "Image should use digest"
	FAILED for resource: Deployment.cdi.cdi-operator
	File: /k8s/bases/infrastructure/controllers/cdi/cdi-operator.yaml:5671-5779
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-39

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_155: "Minimize ClusterRoles that grant control over validating or mutating admission webhook configurations"
	FAILED for resource: ClusterRole.default.kubevirt-operator
	File: /k8s/bases/infrastructure/controllers/kubevirt/kubevirt-operator.yaml:7135-8469
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/ensure-clusterroles-that-grant-control-over-validating-or-mutating-admission-webhook-configurations-are-minimized

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_38: "Ensure that Service Account Tokens are only mounted where necessary"
	FAILED for resource: Deployment.kubevirt.virt-operator
	File: /k8s/bases/infrastructure/controllers/kubevirt/kubevirt-operator.yaml:8486-8620
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-35

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_15: "Image Pull Policy should be Always"
	FAILED for resource: Deployment.kubevirt.virt-operator
	File: /k8s/bases/infrastructure/controllers/kubevirt/kubevirt-operator.yaml:8486-8620
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-14

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_13: "Memory limits should be set"
	FAILED for resource: Deployment.kubevirt.virt-operator
	File: /k8s/bases/infrastructure/controllers/kubevirt/kubevirt-operator.yaml:8486-8620
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-12

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_11: "CPU limits should be set"
	FAILED for resource: Deployment.kubevirt.virt-operator
	File: /k8s/bases/infrastructure/controllers/kubevirt/kubevirt-operator.yaml:8486-8620
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-10

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_22: "Use read-only filesystem for containers where possible"
	FAILED for resource: Deployment.kubevirt.virt-operator
	File: /k8s/bases/infrastructure/controllers/kubevirt/kubevirt-operator.yaml:8486-8620
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-21

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_40: "Containers should run as a high UID to avoid host conflict"
	FAILED for resource: Deployment.kubevirt.virt-operator
	File: /k8s/bases/infrastructure/controllers/kubevirt/kubevirt-operator.yaml:8486-8620
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-37

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_43: "Image should use digest"
	FAILED for resource: Deployment.kubevirt.virt-operator
	File: /k8s/bases/infrastructure/controllers/kubevirt/kubevirt-operator.yaml:8486-8620
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-39

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
github_actions scan results:

Passed checks: 100, Failed checks: 0, Skipped checks: 0

(Truncated to last 6666 characters out of 17621)
⚠️ SPELL / cspell - 2738 errors
"behavioural",
        "betterleaks",
        "bitnami",
        "blocklist",
        "browsable",
        "buildx",
        "burstable",
        "cadvisor",
        "callsite",
        "capitalised",
        "certificaterequest",
        "chokepoint",
        "ciphertext",
        "clickhouse",
        "cloudnative",
        "cloudprovider",
        "clusterissuer",
        "clusterpolicies",
        "clusterrolebindings",
        "clusterroles",
        "cmdline",
        "cnpg",
        "coderabbit",
        "configmap",
        "controlplane",
        "controlplaneio",
        "cooldown",
        "coroot",
        "covermode",
        "coverprofile",
        "crashloop",
        "crashloops",
        "creds",
        "criticals",
        "crossplane",
        "crossview",
        "ctrls",
        "customise",
        "cutover",
        "cyclonedx",
        "daemonset",
        "datapath",
        "datreeio",
        "dbname",
        "dedupe",
        "defence",
        "deletecollection",
        "deploymentruntimeconfigs",
        "descheduler",
        "descheduling",
        "desynchronising",
        "devantler",
        "diffable",
        "dockerconfigjson",
        "docstrings",
        "dorny",
        "dpkg",
        "drainable",
        "emptydir",
        "endgroup",
        "envsubst",
        "esac",
        "etcdctl",
        "evictable",
        "externalsecret",
        "externalsecrets",
        "fanout",
        "featureflagsource",
        "fleetdm",
        "fromdateiso",
        "gatewayapi",
        "generatable",
        "generatingpolicies",
        "genkey",
        "gethomepage",
        "golangci",
        "gosec",
        "grjtvs",
        "growfs",
        "growpart",
        "healthchecks",
        "healthz",
        "helmrelease",
        "helmreleases",
        "helmv",
        "homelab",
        "hostnames",
        "httproute",
        "imagetools",
        "imagevalidatingpolicy",
        "imranismail",
        "inspectable",
        "iscsi",
        "ivpol",
        "keypair",
        "kprobes",
        "kptr",
        "krew",
        "ksail",
        "ksail's",
        "ksailcd",
        "kubeconfig",
        "kubeconform",
        "kubeconform's",
        "kubelet",
        "kubelet's",
        "kubelets",
        "kubelogin",
        "kubescape",
        "kubespan",
        "kubevirt",
        "kubevuln",
        "kustomization",
        "kustomizations",
        "kyverno",
        "letsencrypt",
        "libc",
        "libgnutls",
        "lintable",
        "livez",
        "loadtester",
        "locationless",
        "luks",
        "lycheeignore",
        "machineconfig",
        "materialise",
        "materialised",
        "materialises",
        "maxage",
        "maxbackup",
        "maxsize",
        "maxx",
        "microtime",
        "misconfig",
        "misconfigs",
        "mktemp",
        "mlock",
        "mutatingpolicies",
        "mutatingwebhookconfigurations",
        "myapp",
        "najsk",
        "neighbour",
        "netlink",
        "netpol",
        "netpols",
        "nftables",
        "nilnil",
        "nobuckets",
        "nodepod",
        "nodeport",
        "nolint",
        "nonroot",
        "normalisation",
        "normalised",
        "normalises",
        "normalising",
        "nosec",
        "nullglob",
        "openbao",
        "opencost",
        "openfeature",
        "overprovisioning",
        "parallelised",
        "pasteable",
        "permissioning",
        "pipefail",
        "policyignore",
        "policyreports",
        "portforward",
        "preemptible",
        "preservingly",
        "prioritisable",
        "prioritisation",
        "prioritised",
        "privesc",
        "providerconfigs",
        "publishapp",
        "publishprovider",
        "pushsecret",
        "pushsecrets",
        "randomises",
        "rdqwpktr",
        "readyz",
        "reassertions",
        "recognise",
        "recognised",
        "reconverges",
        "refreshfluxghcrauth",
        "regenerable",
        "releaserc",
        "rematerialise",
        "rematerialised",
        "replicaset",
        "repoint",
        "repointed",
        "repoints",
        "repositoryrulesets",
        "resizer",
        "restrictor",
        "retabbed",
        "rmem",
        "rolebindings",
        "rollouts",
        "rshared",
        "sarif",
        "schedulability",
        "schedulable",
        "scopeable",
        "scopeless",
        "seccomp",
        "secretbox",
        "secretstore",
        "seedable",
        "serialised",
        "serverside",
        "serviceaccount",
        "serviceaccounts",
        "sgdisk",
        "shellcheck",
        "shfmt",
        "shopt",
        "siderolabs",
        "siderolink",
        "sigstore",
        "skmde",
        "slurpfile",
        "sngle",
        "specnull",
        "spiffe",
        "srole",
        "startswith",
        "statefulset",
        "statemanager",
        "stdlib",
        "storageclass",
        "subtest",
        "summarised",
        "surfaceless",
        "syft",
        "syscall",
        "sysctls",
        "talosconfig",
        "talosctl",
        "tanzu",
        "templatesyncignore",
        "thresholded",
        "tmpl",
        "tostring",
        "tracepoints",
        "trixie",
        "trueish",
        "trustd",
        "umami",
        "umami's",
        "uncertifiable",
        "unclickable",
        "uncompilable",
        "uncordon",
        "uncordoned",
        "undecoded",
        "undercounts",
        "unexcepted",
        "ungenerated",
        "unifi",
        "unmarshalling",
        "unmarshals",
        "unmodelled",
        "unparseable",
        "unrecognisable",
        "unrecognised",
        "unreviewed",
        "unroutable",
        "unrun",
        "unshippable",
        "untrackable",
        "upbound",
        "updatekeys",
        "upjet",
        "upstreaming",
        "urlencode",
        "userinfo",
        "userns",
        "ushfn",
        "validatable",
        "validatealertcoverage",
        "validatingwebhookconfigurations",
        "vcunav",
        "velero",
        "volumesnapshot",
        "vpas",
        "vulnerabilitymanifests",
        "vulnerabilitymanifestsummary",
        "vulns",
        "vxlan",
        "wffc",
        "wgpolicyk",
        "wildcarded",
        "wlid",
        "wmem",
        "workloadconfigurationscansummary",
        "worktrees",
        "yannh",
        "yubikey",
        "yzwvjjmcyfnl",
        "zizmor"
    ]
}


You can also copy-paste megalinter-reports/.cspell.json at the root of your repository

(Truncated to last 6666 characters out of 483132)
⚠️ COPYPASTE / jscpd - 152 errors
-ghcr-auth/rollout_safety_test.go [52:25 - 57:11] (6 lines, 53 tokens)
   scripts/tests/refresh-flux-ghcr-auth/rollout_safety_test.go [66:28 - 71:11]
Clone found (go)
 - scripts/tests/refresh-flux-ghcr-auth/rollout_safety_test.go [128:47 - 134:22] (7 lines, 50 tokens)
   scripts/tests/refresh-flux-ghcr-auth/rollout_safety_test.go [340:21 - 346:22]
Clone found (go)
 - scripts/tests/refresh-flux-ghcr-auth/rollout_safety_test.go [261:3 - 268:37] (8 lines, 56 tokens)
   scripts/tests/refresh-flux-ghcr-auth/rollout_safety_test.go [285:3 - 292:37]
Clone found (go)
 - scripts/tests/refresh-flux-ghcr-auth/rollout_safety_test.go [370:69 - 379:5] (10 lines, 57 tokens)
   scripts/tests/refresh-flux-ghcr-auth/rollout_safety_test.go [384:83 - 393:5]
Clone found (go)
 - scripts/tests/refresh-flux-ghcr-auth/rollout_safety_test.go [422:68 - 430:5] (9 lines, 51 tokens)
   scripts/tests/refresh-flux-ghcr-auth/rollout_safety_test.go [435:83 - 443:5]
Clone found (go)
 - scripts/tests/refresh-flux-ghcr-auth/rollout_safety_test.go [447:52 - 464:5] (18 lines, 123 tokens)
   scripts/tests/refresh-flux-ghcr-auth/rollout_safety_test.go [468:57 - 485:5]
Clone found (go)
 - scripts/tests/refresh-flux-ghcr-auth/rollout_safety_test.go [492:77 - 508:11] (17 lines, 118 tokens)
   scripts/tests/refresh-flux-ghcr-auth/rollout_safety_test.go [519:110 - 535:11]
Clone found (bash)
 - scripts/tests/test-cilium-bandwidth-manager-component.sh [9:1 - 54:2] (46 lines, 202 tokens)
   scripts/tests/test-cilium-homogeneous-devices-activation.sh [9:1 - 54:2]
Clone found (bash)
 - scripts/tests/test-cilium-bandwidth-manager-component.sh [52:5 - 72:2] (21 lines, 67 tokens)
   scripts/tests/test-cilium-homogeneous-devices-activation.sh [80:23 - 100:2]
Clone found (bash)
 - scripts/tests/test-cilium-bandwidth-manager-component.sh [54:1 - 72:2] (19 lines, 65 tokens)
   scripts/tests/test-opencost-usage-scraper.sh [15:1 - 33:2]
Clone found (bash)
 - scripts/tests/test-refresh-flux-ghcr-auth-safety.sh [177:5 - 185:51] (9 lines, 76 tokens)
   scripts/tests/test-refresh-flux-ghcr-auth-safety.sh [186:5 - 194:51]
Clone found (python)
 - scripts/tests/test_validate_homepage_bookmarks.py [46:57 - 54:54] (9 lines, 58 tokens)
   scripts/tests/test_validate_homepage_bookmarks.py [100:53 - 109:54]
Clone found (go)
 - scripts/validate-dr-signing/main.go [141:21 - 173:5] (33 lines, 145 tokens)
   scripts/validate-merge-group-heal/main.go [69:40 - 101:5]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [244:50 - 249:24] (6 lines, 103 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [756:31 - 761:24]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [276:14 - 282:4] (7 lines, 115 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [603:43 - 609:4]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [302:13 - 311:7] (10 lines, 158 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [828:18 - 837:7]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [311:1 - 316:8] (6 lines, 93 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [838:1 - 843:8]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [388:5 - 393:2] (6 lines, 82 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [574:8 - 579:9]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [389:1 - 399:23] (11 lines, 220 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [666:60 - 677:3]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [393:15 - 404:2] (12 lines, 185 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [494:17 - 505:2]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [440:15 - 451:12] (12 lines, 264 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [649:1 - 662:3]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [503:2 - 509:19] (7 lines, 89 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [688:7 - 694:19]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [503:2 - 509:33] (7 lines, 103 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [1013:58 - 1019:33]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [503:2 - 509:4] (7 lines, 74 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [1126:5 - 1132:4]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [596:30 - 601:8] (6 lines, 50 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [623:44 - 628:8]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [951:33 - 959:11] (9 lines, 118 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [963:130 - 971:11]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [1158:47 - 1163:2] (6 lines, 166 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [1215:46 - 1220:2]
Clone found (python)
 - scripts/validate-naming.py [126:52 - 132:25] (7 lines, 53 tokens)
   scripts/validate-naming.py [171:82 - 177:29]
┌────────┬────────────────┬─────────────┬──────────────┬──────────────┬──────────────────┬───────────────────┐
│ Format │ Files analyzed │ Total lines │ Total tokens │ Clones found │ Duplicated lines │ Duplicated tokens │
├────────┼────────────────┼─────────────┼──────────────┼──────────────┼──────────────────┼───────────────────┤
│ bash   │ 26             │ 8778        │ 35108        │ 15           │ 195 (2.22%)      │ 1188 (3.38%)      │
├────────┼────────────────┼─────────────┼──────────────┼──────────────┼──────────────────┼───────────────────┤
│ go     │ 27             │ 17598       │ 123942       │ 71           │ 566 (3.22%)      │ 5917 (4.77%)      │
├────────┼────────────────┼─────────────┼──────────────┼──────────────┼──────────────────┼───────────────────┤
│ python │ 4              │ 807         │ 5606         │ 2            │ 14 (1.73%)       │ 111 (1.98%)       │
├────────┼────────────────┼─────────────┼──────────────┼──────────────┼──────────────────┼───────────────────┤
│ txt    │ 57             │ 3378        │ 109125       │ 64           │ 2417 (71.55%)    │ 80219 (73.51%)    │
├────────┼────────────────┼─────────────┼──────────────┼──────────────┼──────────────────┼───────────────────┤
│ Total: │ 114            │ 30561       │ 273781       │ 152          │ 3192 (10.44%)    │ 87435 (31.94%)    │
└────────┴────────────────┴─────────────┴──────────────┴──────────────┴──────────────────┴───────────────────┘
Found 152 clones.
HTML report saved to megalinter-reports/copy-paste/jscpd-report.html
ERROR: jscpd found too many duplicates (10.4%) over threshold (0.0%)
time: 1.16s

(Truncated to last 6666 characters out of 33635)
⚠️ MARKDOWN / markdownlint - 63 errors
hould have a language specified [Context: "```"]
AGENTS.md:101:401 error MD013/line-length Line length [Expected: 400; Actual: 1769]
AGENTS.md:105:401 error MD013/line-length Line length [Expected: 400; Actual: 1126]
AGENTS.md:106:401 error MD013/line-length Line length [Expected: 400; Actual: 1447]
AGENTS.md:121:401 error MD013/line-length Line length [Expected: 400; Actual: 649]
AGENTS.md:123:401 error MD013/line-length Line length [Expected: 400; Actual: 971]
AGENTS.md:150:401 error MD013/line-length Line length [Expected: 400; Actual: 970]
AGENTS.md:153:401 error MD013/line-length Line length [Expected: 400; Actual: 453]
AGENTS.md:172:401 error MD013/line-length Line length [Expected: 400; Actual: 846]
AGENTS.md:241:401 error MD013/line-length Line length [Expected: 400; Actual: 491]
AGENTS.md:242:401 error MD013/line-length Line length [Expected: 400; Actual: 468]
AGENTS.md:248:401 error MD013/line-length Line length [Expected: 400; Actual: 532]
AGENTS.md:250:401 error MD013/line-length Line length [Expected: 400; Actual: 523]
AGENTS.md:253:401 error MD013/line-length Line length [Expected: 400; Actual: 613]
AGENTS.md:254:401 error MD013/line-length Line length [Expected: 400; Actual: 714]
AGENTS.md:258:401 error MD013/line-length Line length [Expected: 400; Actual: 502]
AGENTS.md:262:401 error MD013/line-length Line length [Expected: 400; Actual: 441]
AGENTS.md:267:401 error MD013/line-length Line length [Expected: 400; Actual: 427]
AGENTS.md:370:401 error MD013/line-length Line length [Expected: 400; Actual: 1012]
AGENTS.md:372:401 error MD013/line-length Line length [Expected: 400; Actual: 1240]
AGENTS.md:377:401 error MD013/line-length Line length [Expected: 400; Actual: 430]
AGENTS.md:388:401 error MD013/line-length Line length [Expected: 400; Actual: 1230]
AGENTS.md:399:401 error MD013/line-length Line length [Expected: 400; Actual: 790]
AGENTS.md:404:401 error MD013/line-length Line length [Expected: 400; Actual: 515]
CLAUDE.md:1 error MD041/first-line-heading/first-line-h1 First line in a file should be a top-level heading [Context: "@AGENTS.md"]
docs/dr/alerting.md:213:28 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/crypto-custody.md:22:389 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/crypto-custody.md:23:264 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/crypto-custody.md:27:35 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/crypto-custody.md:27:161 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/crypto-custody.md:27:239 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/crypto-custody.md:114 error MD024/no-duplicate-heading Multiple headings with the same content [Context: "Custody recommendations"]
docs/dr/crypto-custody.md:245 error MD024/no-duplicate-heading Multiple headings with the same content [Context: "Custody recommendations"]
docs/dr/crypto-custody.md:251 error MD024/no-duplicate-heading Multiple headings with the same content [Context: "What to do if it leaks"]
docs/dr/crypto-custody.md:258 error MD024/no-duplicate-heading Multiple headings with the same content [Context: "What to do if it is *lost* (no..."]
docs/dr/restore-drill.md:42 error MD028/no-blanks-blockquote Blank line inside blockquote
docs/dr/runbook.md:23:102 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/runbook.md:23:487 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/runbook.md:34 error MD028/no-blanks-blockquote Blank line inside blockquote
docs/dr/runbook.md:41 error MD028/no-blanks-blockquote Blank line inside blockquote
docs/dr/runbook.md:50 error MD028/no-blanks-blockquote Blank line inside blockquote
docs/dr/runbook.md:489:92 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/spire-server-ha.md:93 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/dr/velero-cnpg.md:11 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/dr/velero-cnpg.md:56:78 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/velero-cnpg.md:56:166 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/velero-cnpg.md:57:78 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/velero-cnpg.md:57:227 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/velero-cnpg.md:58:78 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/velero-cnpg.md:58:166 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/github-management.md:38:401 error MD013/line-length Line length [Expected: 400; Actual: 419]
docs/github-management.md:40:401 error MD013/line-length Line length [Expected: 400; Actual: 522]
docs/node-autoscaling.md:14 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/oidc-kubectl.md:95 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/runtime-security.md:114 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/rwx-storage.md:9 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/unifi-management.md:14 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/unifi-management.md:62 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
README.md:116:401 error MD013/line-length Line length [Expected: 400; Actual: 540]
README.md:237:32 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
README.md:237:36 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]

(Truncated to last 6666 characters out of 6992)
⚠️ REPOSITORY / trivy - 1 error
er/infrastructure/vertical-pod-autoscalers/spire-agent.yaml:9-13
────────────────────────────────────────
   9 ┌ spec:
  10 │   targetRef:
  11 │     apiVersion: apps/v1
  12 │     kind: DaemonSet
  13 └     name: spire-agent
────────────────────────────────────────



k8s/providers/hetzner/infrastructure/vertical-pod-autoscalers/spire-server.yaml (kubernetes)
============================================================================================
Tests: 118 (SUCCESSES: 116, FAILURES: 2)
Failures: 2 (UNKNOWN: 0, LOW: 1, MEDIUM: 1, HIGH: 0, CRITICAL: 0)

KSV-0037 (MEDIUM): VerticalPodAutoscaler 'spire-server' should not be set with 'kube-system' namespace
════════════════════════════════════════
ensure that user resources are not placed in kube-system namespace

See https://avd.aquasec.com/misconfig/ksv-0037
────────────────────────────────────────
 k8s/providers/hetzner/infrastructure/vertical-pod-autoscalers/spire-server.yaml:7-11
────────────────────────────────────────
   7 ┌ spec:
   8 │   targetRef:
   9 │     apiVersion: apps/v1
  10 │     kind: StatefulSet
  11 └     name: spire-server
────────────────────────────────────────


KSV-0039 (LOW): A LimitRange policy with a default requests and limits for each container should be configured
════════════════════════════════════════
Ensure that a LimitRange policy is configured to limit resource usage for namespaces or nodes

See https://avd.aquasec.com/misconfig/ksv-0039
────────────────────────────────────────
 k8s/providers/hetzner/infrastructure/vertical-pod-autoscalers/spire-server.yaml:7-11
────────────────────────────────────────
   7 ┌ spec:
   8 │   targetRef:
   9 │     apiVersion: apps/v1
  10 │     kind: StatefulSet
  11 └     name: spire-server
────────────────────────────────────────



k8s/providers/hetzner/infrastructure/vertical-pod-autoscalers/tetragon-operator.yaml (kubernetes)
=================================================================================================
Tests: 118 (SUCCESSES: 116, FAILURES: 2)
Failures: 2 (UNKNOWN: 0, LOW: 1, MEDIUM: 1, HIGH: 0, CRITICAL: 0)

KSV-0037 (MEDIUM): VerticalPodAutoscaler 'tetragon-operator' should not be set with 'kube-system' namespace
════════════════════════════════════════
ensure that user resources are not placed in kube-system namespace

See https://avd.aquasec.com/misconfig/ksv-0037
────────────────────────────────────────
 k8s/providers/hetzner/infrastructure/vertical-pod-autoscalers/tetragon-operator.yaml:7-11
────────────────────────────────────────
   7 ┌ spec:
   8 │   targetRef:
   9 │     apiVersion: apps/v1
  10 │     kind: Deployment
  11 └     name: tetragon-operator
────────────────────────────────────────


KSV-0039 (LOW): A LimitRange policy with a default requests and limits for each container should be configured
════════════════════════════════════════
Ensure that a LimitRange policy is configured to limit resource usage for namespaces or nodes

See https://avd.aquasec.com/misconfig/ksv-0039
────────────────────────────────────────
 k8s/providers/hetzner/infrastructure/vertical-pod-autoscalers/tetragon-operator.yaml:7-11
────────────────────────────────────────
   7 ┌ spec:
   8 │   targetRef:
   9 │     apiVersion: apps/v1
  10 │     kind: Deployment
  11 └     name: tetragon-operator
────────────────────────────────────────



k8s/providers/hetzner/infrastructure/vertical-pod-autoscalers/tetragon.yaml (kubernetes)
========================================================================================
Tests: 118 (SUCCESSES: 116, FAILURES: 2)
Failures: 2 (UNKNOWN: 0, LOW: 1, MEDIUM: 1, HIGH: 0, CRITICAL: 0)

KSV-0037 (MEDIUM): VerticalPodAutoscaler 'tetragon' should not be set with 'kube-system' namespace
════════════════════════════════════════
ensure that user resources are not placed in kube-system namespace

See https://avd.aquasec.com/misconfig/ksv-0037
────────────────────────────────────────
 k8s/providers/hetzner/infrastructure/vertical-pod-autoscalers/tetragon.yaml:9-13
────────────────────────────────────────
   9 ┌ spec:
  10 │   targetRef:
  11 │     apiVersion: apps/v1
  12 │     kind: DaemonSet
  13 └     name: tetragon
────────────────────────────────────────


KSV-0039 (LOW): A LimitRange policy with a default requests and limits for each container should be configured
════════════════════════════════════════
Ensure that a LimitRange policy is configured to limit resource usage for namespaces or nodes

See https://avd.aquasec.com/misconfig/ksv-0039
────────────────────────────────────────
 k8s/providers/hetzner/infrastructure/vertical-pod-autoscalers/tetragon.yaml:9-13
────────────────────────────────────────
   9 ┌ spec:
  10 │   targetRef:
  11 │     apiVersion: apps/v1
  12 │     kind: DaemonSet
  13 └     name: tetragon
────────────────────────────────────────



ksail.prod.yaml (kubernetes)
============================
Tests: 118 (SUCCESSES: 117, FAILURES: 1)
Failures: 1 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 0, CRITICAL: 0)

KSV-0039 (LOW): A LimitRange policy with a default requests and limits for each container should be configured
════════════════════════════════════════
Ensure that a LimitRange policy is configured to limit resource usage for namespaces or nodes

See https://avd.aquasec.com/misconfig/ksv-0039
────────────────────────────────────────
 ksail.prod.yaml:7-257
────────────────────────────────────────
   7 ┌   name: prod
   8 │ spec:
   9 │   cluster:
  10 │     distributionConfig: talos
  11 │     connection:
  12 │       context: admin@prod
  13 │       timeout: 30m
  14 │     distribution: Talos
  15 └     provider: Hetzner
  ..   
────────────────────────────────────────



ksail.yaml (kubernetes)
=======================
Tests: 118 (SUCCESSES: 117, FAILURES: 1)
Failures: 1 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 0, CRITICAL: 0)

KSV-0039 (LOW): A LimitRange policy with a default requests and limits for each container should be configured
════════════════════════════════════════
Ensure that a LimitRange policy is configured to limit resource usage for namespaces or nodes

See https://avd.aquasec.com/misconfig/ksv-0039
────────────────────────────────────────
 ksail.yaml:7-55
────────────────────────────────────────
   7 ┌   name: local
   8 │ spec:
   9 │   cluster:
  10 │     distributionConfig: talos-local
  11 │     connection:
  12 │       context: admin@local
  13 │       timeout: 40m
  14 │     distribution: Talos
  15 └     provider: Docker
  ..   
────────────────────────────────────────



📣 Notices:
  - Version 0.72.0 of Trivy is now available, current version is 0.71.2

To suppress version checks, run Trivy scans with the --skip-version-check flag

(Truncated to last 6666 characters out of 805003)

✅ Linters with no issues

actionlint, betterleaks, git_diff, golangci-lint, grype, jsonlint, lychee, osv-scanner, prettier, prettier, revive, secretlint, shellcheck, shfmt, syft, trivy-sbom, trufflehog, v8r, v8r, yamllint, zizmor

Notices

📣 MegaLinter 9.5.0 is out! Discover the new features and security recommendations in the release announcement. (Skip this info by defining SECURITY_SUGGESTIONS: false)

See detailed reports in MegaLinter artifacts

MegaLinter is graciously provided by OX Security
Show us your support by starring ⭐ the repository

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

@devantler I will review the changes.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds the report-only kubescape-backlog-bridge command. It validates posture and CVE JSON, applies posture exceptions, derives grouped findings, sanitizes components, and generates stable fingerprints. It rejects write mode and invalid or incomplete input. Tests and CI cover deterministic output, exception handling, scan-surface validation, and fail-closed behavior.

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 67.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description explains the report-only Kubescape bridge, themed findings, validation, exceptions, sanitization, and deferred write functionality.
Linked Issues check ✅ Passed The description links the work to #2451 and #2854 and clearly states that write functionality remains outstanding.
Out of Scope Changes check ✅ Passed The changes remain within the report-only Kubescape bridge, its tests, documentation, and CI coverage.
Title check ✅ Passed The title clearly and concisely describes deriving themed, deduplicated backlog entries from live Kubescape findings.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/kubescape-backlog-bridge/main.go`:
- Around line 407-417: The readList function must reject JSON documents that are
not list-shaped before returning l.Items. Validate that the top-level input
contains an items field represented as an array, return a parse/validation error
otherwise, and add a test covering a bare single-object JSON document to ensure
it cannot produce zero findings with a successful exit.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 992a69cf-a547-45ed-bff8-2dcb9c955807

📥 Commits

Reviewing files that changed from the base of the PR and between a9562d8 and b72fb09.

📒 Files selected for processing (3)
  • .github/workflows/ci.yaml
  • scripts/kubescape-backlog-bridge/main.go
  • scripts/kubescape-backlog-bridge/main_test.go
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
.github/workflows/*.yaml

📄 CodeRabbit inference engine (AGENTS.md)

CI for Kubernetes-related changes must remain static: run validation and Kubescape scanning without starting a cluster.

Files:

  • .github/workflows/ci.yaml
**/*

📄 CodeRabbit inference engine (AGENTS.md)

**/*: Use Conventional Commit messages and titles, keep changes small and focused, and create pull requests as drafts.
Land new behavior disabled, validate it, then enable it using the appropriate layer: OpenFeature for per-request flags, Flagger for traffic shifting, Helm values/Kustomize for component toggles, and Kubernetes feature gates for platform behavior.

Files:

  • scripts/kubescape-backlog-bridge/main.go
  • scripts/kubescape-backlog-bridge/main_test.go
🧠 Learnings (2)
📚 Learning: 2026-07-28T00:33:10.828Z
Learnt from: devantler
Repo: devantler-tech/platform PR: 2825
File: .github/workflows/dr-rebuild.yaml:70-73
Timestamp: 2026-07-28T00:33:10.828Z
Learning: For GitHub Actions concurrency configuration in this repo, don’t use `queue: single` on a shared production concurrency group (e.g., `prod-deploy`) that is used for normal production deployments—`queue: single` will cancel/replace any pending run in that group. Normal production deployments must use a uniform `queue: max` for that shared group to ensure serialization without coalescing. If you need DR rebuild coalescing (replacement of pending DR rebuild runs), it must use a separate DR-only concurrency scope/group (not the shared production `prod-deploy` group).

Applied to files:

  • .github/workflows/ci.yaml
📚 Learning: 2026-07-29T07:29:34.804Z
Learnt from: devantler
Repo: devantler-tech/platform PR: 2848
File: scripts/validate-publication-order/main.go:315-353
Timestamp: 2026-07-29T07:29:34.804Z
Learning: When reviewing GitHub Actions workflow steps with `run:` blocks, remember that the default non-interactive Bash invocation won’t expand shell aliases unless the script explicitly enables it (e.g., `shopt -s expand_aliases`). Therefore, when assessing potential command-name shadowing (e.g., whether an alias could change what command runs), only treat alias-based behavior as possible if the step explicitly opts into alias expansion via `shopt -s expand_aliases` (or equivalent).

Applied to files:

  • .github/workflows/ci.yaml
🪛 golangci-lint (2.12.2)
scripts/kubescape-backlog-bridge/main.go

[medium] 408-408: G304: Potential file inclusion via variable

(gosec)

scripts/kubescape-backlog-bridge/main_test.go

[high] 100-100: G404: Use of weak random number generator (math/rand or math/rand/v2 instead of crypto/rand)

(gosec)

🔇 Additional comments (3)
scripts/kubescape-backlog-bridge/main.go (1)

1-119: LGTM!

Also applies to: 124-406, 418-434

scripts/kubescape-backlog-bridge/main_test.go (1)

1-329: LGTM!

.github/workflows/ci.yaml (1)

81-81: LGTM!

Also applies to: 439-447

Comment thread scripts/kubescape-backlog-bridge/main.go
… it clean

A per-object `kubectl get <crd> <name> -n <ns> -o json` — the form this
command's own documentation asks for — emits a bare object with no
"items" key. Decoding it into a list left Items nil, so a document
carrying critical=18 printed "nothing to file" and exited 0.

That is the same false all-clear the stripped-LIST guard exists to
prevent, reached by following the instructions. The document shape is
now determined explicitly: a list, a single object, or a hard error.
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

@devantler I will review the changes.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

CodeRabbit rate-limited this request rather than reviewing it. The acknowledgement at 15:00:38Z
was not a review starting: its summary comment was updated in place at 15:00:41Z to
Review limit reached with a 44-minute window, so nothing was ever queued at 4c56f5b9.

Worth recording because the freshness signal lies here — the summary comment's updated_at moves
when the refusal is written, so a check that trusts recency reads a refusal as a fresh review.

A stated retry window still counts as not-delivering, so recording CodeRabbit as no-gate at this
head and advancing to the next lane.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4c56f5b98c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/kubescape-backlog-bridge/main.go Outdated
Comment thread scripts/kubescape-backlog-bridge/main.go Outdated
Comment thread scripts/kubescape-backlog-bridge/main.go Outdated
Comment thread scripts/kubescape-backlog-bridge/main.go
Comment thread scripts/kubescape-backlog-bridge/main.go Outdated
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Codex round at 4c56f5b9 — 5 × P2, all valid. Not promoting.

The important thing is not the count, it is that two of these are the same defect class as the
Critical fixed last round
, which means that fix was narrower than it looked.

That fix taught readList to determine the document shape explicitly instead of assuming a wrapper.
But the false-all-clear does not come from shape alone:

  • :225 — a non-empty LIST with fewer than 10 objects still passes the spec-stripped check,
    because that check is a size heuristic. So the fail-closed guarantee holds on a big cluster and
    quietly does not hold on a small one, or on a namespace-scoped query.
  • :391 — a CVE document passed to -posture satisfies checkHydration (it sees nonzero
    severities), then derivePosture finds no controls and the tool exits 0 with nothing to file.
    A swapped file becomes an all-clear.

Both end where the last one did: exit 0, "nothing to file", over data that was never examined.
The lesson I recorded last round was about running the exact command the docs prescribe; the wider
one is that this tool's success path and its genuinely-empty path are indistinguishable, and every
new input mistake lands in the safe-looking one. That is worth fixing structurally — distinguish
"examined and empty" from "did not examine" — rather than patching the third instance.

The other three are real and independent:

  • :359 — the promised cluster-wide grouping is not reachable. Each surface flag takes one path
    and repeating it overwrites, while the only native bulk document is the LIST this tool rejects. So
    a multi-workload run has no supported input.
  • :310 — critical count is used as a zero/nonzero predicate, so 1 and 999 produce byte-identical
    reports. Excluding the count from the fingerprint is right; dropping it from the rendered theme is
    not, and it defeats the point of updating an existing entry.
  • :285 — declared ClusterSecurityExceptions are not applied before deriving themes, so
    intentionally accepted controls queue as actionable work. That recreates exactly the noise the
    exception pipeline exists to remove, and it matters more once write mode lands.

Leaving this a draft and carrying it forward. Fixing five findings — one of which is a structural
change to how emptiness is represented — is not something to append at the end of a run without
RED/GREEN proof and ablation for each.

The bridge's success path and its genuinely-empty path were
indistinguishable, so every input mistake landed in exit 0 "nothing to
file" — a false all-clear on live security findings. Five review
findings were instances of that one root cause or of gaps around it.

Measured against prod: a stripped LIST differs from a real per-object
GET by SHAPE, not by size. 2215/2215 posture LIST objects carry
`controls: null` while a genuinely compliant GET still carries its
control map; 121/121 CVE LIST objects blank `vulnerabilitiesRef` while
a genuinely clean image still names its manifest. So "examined and
empty" is separable from "never examined" at any input size, and the
size heuristic (which held only on a large cluster) is gone.

The same markers identify which surface a document belongs to, so a
CVE file passed to -posture is rejected instead of deriving no controls
and exiting 0.

Also: the surface flags are repeatable, because a cluster-wide run
produces one per-object GET per workload and a single-valued flag made
the promised grouping unreachable; CVE counts are summed and rendered
(still never fingerprinted) so an update can show 1 critical becoming
999; and declared ClusterSecurityExceptions are applied before deriving
themes, filtered PER-COMPONENT so a kind/name-scoped exception cannot
silence a workload it does not name.

Exceptions are read from the artifact
scripts/generate-kubescape-exceptions already produces, so the CI scan
and this bridge cannot drift apart in what they consider accepted.
readList decodes the two document shapes explicitly, and the tests build
fixtures as raw JSON, so nothing references it. golangci-lint's unused
linter flagged it; the compiler does not.
revive requires one per non-test file, and this is the repo's only
multi-file Go package, so the rule had never fired before.

Putting the comment on exceptions.go alone displaced the command's own
documentation: Go concatenates package comments in filename order, so
`go doc` led with the exceptions blurb. The package documentation now
lives in doc.go, which is both idiomatic and deterministically first.
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

@devantler I will review the changes.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/kubescape-backlog-bridge/main_test.go`:
- Line 190: Annotate the deterministic rand.New initialization in the test with
the project’s standard inline gosec G404 suppression comment, preserving the
fixed seed and reproducible shuffle behavior.

In `@scripts/kubescape-backlog-bridge/main.go`:
- Around line 585-592: Update the items parsing logic around probe["items"] to
reject an explicit JSON null before unmarshalling, while continuing to accept an
explicit empty array as valid. Return the existing malformed-items error for
null values, and preserve the current json.Unmarshal handling for non-null
arrays.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3c2cae02-762d-4a8c-9c87-9d83fed54b72

📥 Commits

Reviewing files that changed from the base of the PR and between b72fb09 and ccbe6a6.

📒 Files selected for processing (5)
  • scripts/kubescape-backlog-bridge/doc.go
  • scripts/kubescape-backlog-bridge/exceptions.go
  • scripts/kubescape-backlog-bridge/exceptions_test.go
  • scripts/kubescape-backlog-bridge/main.go
  • scripts/kubescape-backlog-bridge/main_test.go
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
**/*

📄 CodeRabbit inference engine (AGENTS.md)

**/*: Use Conventional Commit messages and titles, keep changes small and focused, and create pull requests as drafts.
Land new behavior disabled, validate it, then enable it using the appropriate layer: OpenFeature for per-request flags, Flagger for traffic shifting, Helm values/Kustomize for component toggles, and Kubernetes feature gates for platform behavior.

Files:

  • scripts/kubescape-backlog-bridge/exceptions_test.go
  • scripts/kubescape-backlog-bridge/doc.go
  • scripts/kubescape-backlog-bridge/exceptions.go
  • scripts/kubescape-backlog-bridge/main.go
  • scripts/kubescape-backlog-bridge/main_test.go
🪛 golangci-lint (2.12.2)
scripts/kubescape-backlog-bridge/main_test.go

[high] 190-190: G404: Use of weak random number generator (math/rand or math/rand/v2 instead of crypto/rand)

(gosec)

🔇 Additional comments (6)
scripts/kubescape-backlog-bridge/doc.go (1)

1-72: LGTM!

scripts/kubescape-backlog-bridge/main.go (1)

25-121: LGTM!

Also applies to: 124-167, 169-229, 231-287, 289-297, 330-437, 439-457, 459-533, 535-558, 594-608, 610-629

scripts/kubescape-backlog-bridge/main_test.go (1)

11-143: LGTM!

Also applies to: 145-201, 205-273, 275-290, 292-351, 353-368, 372-397, 401-424, 426-452, 454-513, 515-552, 554-595

scripts/kubescape-backlog-bridge/exceptions.go (2)

1-56: LGTM!

Also applies to: 84-131, 133-206


57-82: 🗄️ Data Integrity & Integration

No change needed for designator attributes.

resolveMatch only emits Attributes designators with namespace from namespaceSelector or kind/name from resources, so matches covers the current generator output.

scripts/kubescape-backlog-bridge/exceptions_test.go (1)

1-50: LGTM!

Also applies to: 52-134, 136-173, 175-230, 232-263

Comment thread scripts/kubescape-backlog-bridge/main_test.go
Comment thread scripts/kubescape-backlog-bridge/main.go
… result

It decodes into a nil slice WITHOUT error, so every guard passed
vacuously and the command reported a clean cluster at exit 0 — the same
false all-clear as the stripped skeleton, reached by a different shape.
Reproduced on the previous head: `{"items":null}` printed
"nothing to file" and exited 0 for both surfaces.

`items: []` stays a legitimate pass; that distinction is the point.
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

@devantler I will review the changes.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Lane state at 400fa98f — CodeRabbit quota, advancing to Codex

CodeRabbit reviewed the previous head (ccbe6a67) and returned 2 actionable findings, both now handled — so this is a genuine quota exhaustion after real work, not a dead lane. The re-request at 16:48:10Z was refused: Review limit reached … Next review available in: 47 minutes, which would park a finished change past the end of this run.

Recording cr:no-gate@400fa98f and advancing to Codex per lane priority, rather than waiting out the window.

For the next run, since this is easy to misread: CodeRabbit refuses by updating its summary comment in place. That update names the current head and is freshly timestamped, so it looks like a review unless the body is read. The distinguishing marker is Review limit reached in the body — not the timestamp, and not the presence of a review object.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

@codex review

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

@devantler I will review the changes at 7706a0d41693545526f4e6c71c36fa298c4a2481.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

CodeRabbit did not deliver at this head — rate limit, not a verdict. Its summary comment was
updated in place at 16:52:46Z with "Review limit reached … Next review available in: 42 minutes",
so the review never started. Recording cr:no-gate reason=rate-limit and advancing to Codex rather
than waiting out a window that would park finished work.

CI is green at this head (25/25, 0 failures), mergeStateStatus: CLEAN, 0 unresolved threads.

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7706a0d416

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/kubescape-backlog-bridge/main.go Outdated
Comment thread scripts/kubescape-backlog-bridge/main.go
…ying exceptions

An identity check comparing against the empty string accepted " ". The generated
cluster-wide designator uses kind: ".*", which full-matches a whitespace value
just as it does an empty one, so a failed control was suppressed against an
identity that had never been established and the run exited clean with nothing
to file — the false all-clear this command exists to prevent.

Both the guard and the diagnostic that names the missing fields now trim before
deciding, so the error stays accurate for a blank value.
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Round 16's P2 is fixed (whitespace-only workload identity was accepted and then suppressed by the
cluster-wide designator — RED→GREEN with an end-to-end suppression proof); the P3 is refuted on the
thread with the source reading. Restarting the lane at the new head.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

@devantler I will review the changes at 4c48dd868084716c2c4a351294f06006e7e87162.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

CodeRabbit is still rate-limited at this head — its ack said it would review 4c48dd8680, but the
summary comment then updated in place to "Review limit reached … Next review available in: 17
minutes"
. Recording cr:no-gate reason=rate-limit and advancing.

CI green at this head (25/25, 0 failures), mergeStateStatus: CLEAN, 0 unresolved threads.

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4c48dd8680

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/kubescape-backlog-bridge/main.go Outdated
The previous commit trimmed the workload-identity check on the posture path and
in the shared diagnostic, but left deriveCVE comparing against the empty string —
so a whitespace-only kind or name still produced a valid-looking theme naming an
unidentified component, and several blank identities collapsed into one another
in the component set.

deriveCVE's own comment already stated the rule this violated: the check belongs
on both paths or it covers only one of two.
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Round 17's P2 is fixed: the trimmed identity check now applies to the CVE path too, which my previous
commit had missed. RED→GREEN with a single-line ablation. Restarting the lane at the new head.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

@devantler I will review the changes at 59b0611b6acc346fe4e029b8630e36a1835bd8b1.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Retriggering after the stated rate-limit window elapsed (it reported 2 minutes).

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

@devantler I will review the changes at 59b0611b6acc346fe4e029b8630e36a1835bd8b1.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Readiness — self-promoting

Condition Evidence
1. Programmatically tested 25/25 checks green, 0 failures. Each fix landed RED→GREEN with a single-line ablation: whitespace identity on the posture path (4 cases + an end-to-end suppression proof), and the same on the CVE path.
2. Reviewed cr@59b0611b — CodeRabbit summary at this head, "No actionable comments were generated", reviewed span 8a7e21c8…59b0611b (covers every commit I pushed), no quota marker, only the non-actionable 🔇 Additional comments section. 0 unresolved threads.
3. Tried and evaluated as a user Built the binary and ran it — see below.

User evaluation, with controls (a guard that rejected everything would look identical to a correct one, so each case had to come out differently):

Input Result
whitespace kind + cluster-wide exception, failed control refused, exit 1, diagnostic correctly naming kind — so the trimmed missingIdentityFields works too
valid kind + matching cluster-wide exception accepted, correctly excepted, exit 0, with the posture-scoping caveat
valid kind + non-matching exception filed: posture severity=High total=1 … components=app/Deployment/api
empty exceptions artifact refused by its own guard (an empty artifact would silently disable all filtering)

Rounds 15–17 found four things; three were real and fixed, one refuted on the thread. Round 17's was
mine
— my round-16 fix covered the posture path but not the CVE path, which is the self-introduced
class this branch has produced before.

The residual regex-satisfiability analysis is split out to #2910 rather than spun for an eighteenth
round, per the convergence call already on this PR.

mergeStateStatus: CLEAN. Promoting and merging.

@devantler
devantler marked this pull request as ready for review August 2, 2026 17:40
@devantler
devantler added this pull request to the merge queue Aug 2, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 2, 2026
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Evicted from the merge queue — root-caused, deliberately NOT re-queued

This PR was promoted and enqueued at 59b0611b (position 2). It did not merge: the merge_group run
30760329199 failed, which is
what evicts.

The failing job is 🚀 Deploy to Prod (step "🚀 Deploy to Production"), which then failed
CI - Required Checks. Every other job in that run passed or skipped:

Job Result
🔍 Detect Changes success
🔐 Validate EKS Authorization success
🏷️ / 🧪 Validate Naming / Manifests / Talos skipped
🚀 Deploy to Prod failure
🩹 Heal Prod started 18:48:36Z

main is healthy — its own runs are green, including the 18:11Z scheduled run. The heal job exists
precisely to restore main after an unsuccessful merge-group deploy, so this is a queue-local failure,
not live breakage.

Why I am not simply re-queuing

Re---auto-ing without diagnosing is how a known systemic failure gets hit repeatedly, so the rule
here is root-cause first.

This PR is a poor candidate for having caused it. Its entire diff is a standalone Go tool under
scripts/ plus two ci.yaml additions — a path-filter entry and a go test ./scripts/kubescape-backlog-bridge
step in a validation job. Nothing it changes is on the deploy path, and nothing it adds is deployed to
the cluster. It also passed 25/25 on the pull_request event; Deploy to Prod only runs in the
merge_group context, so this was its first contact with that path.

The discriminator is already running: pr-2908 entered the queue right behind this one and its
Deploy to Prod (30761978944)
is in flight. If that deploy also fails, the cause is environmental and shared, and re-queuing anything
is futile until it is fixed. If it succeeds, this PR needs a closer look despite the reasoning above.

Run logs were not readable while the run was still pending (the heal job holds it open), so the next
step is to read 30760329199's deploy log once it completes and compare against pr-2908's outcome.

Leaving the PR promoted, green, and pentad-clear at 59b0611b so it can be re-queued the moment the
deploy path is known-good.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Merge-queue recovery

The first merge-group attempt (run 30760329199) failed while apps waited on the independently broken github-config tenant. Root cause: .github PR #129 changed deploy/ under a no-bump refactor: squash title, so production remained on stale artifact v1.21.4.

Recovered at the root:

  • fix(release): prevent deploy changes from skipping publication .github#131 merged as verified-signed 9787b94937422d24caeb6939a2d8024f8b76edb8
  • semantic-release published v1.21.5
  • CD pushed and signed ghcr.io/devantler-tech/github-config/manifests@sha256:5f4179b2a62863c165d994b0a0ac7646c2b78568ae22c254f84db7f1b9bcad74
  • Flux reported SourceVerified=True, then applied that exact revision; github-config and apps are Ready/Healthy
  • the stale TeamRepository objects are gone and both platform-tenant-template replacements are Available
  • the failed merge-group's Heal Prod job succeeded, restoring the reviewed Platform main artifact; all Flux Kustomizations are Ready

This PR remains unchanged at reviewed head 59b0611b6acc346fe4e029b8630e36a1835bd8b1, with green PR checks and 0 unresolved threads. Re-enqueuing once behind the queue item already ahead of it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

1 participant