Skip to content

fix(ci): report what the Kubescape scan actually found - #2847

Merged
devantler merged 8 commits into
mainfrom
claude/kubescape-sarif-code-scanning-2451
Jul 28, 2026
Merged

fix(ci): report what the Kubescape scan actually found#2847
devantler merged 8 commits into
mainfrom
claude/kubescape-sarif-code-scanning-2451

Conversation

@devantler

@devantler devantler commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

🤖 Generated by the Agentic Engineer

Why

Kubescape's console table shows only part of what it finds. Measured on this repository: a scan whose SARIF carried 4 findings printed All controls passed. No issues found, and one carrying 70 findings listed a single control. Since those findings are now uploaded as Code Scanning alerts, the CI log and the Security tab actively contradict each other — and the log is what a developer reads on a PR.

What

Reports the findings from the SARIF the scan already produces, so the log and the alerts cannot disagree. Covers pull requests and pushes to main (the latter is the only default-branch scan). Purely informational — the compliance threshold stays the sole merge gate, and a summary that cannot be built annotates rather than blocking a merge.

Fixes #2846
Part of #2451

The scan's console table shows only a subset of its findings: a scan whose
SARIF carried 4 findings printed 'All controls passed. No issues found', and
one carrying 70 listed a single control. Since those findings became Code
Scanning alerts, the CI log and the Security tab disagreed — and the log is
what a developer reads on a PR.

Summarize the SARIF the run already writes, so both surfaces report from one
source. Informational only; the compliance threshold remains the sole gate.

Fixes #2846
…ith no rule id

Self-review found two gaps: the new script was absent from the path filter that
runs its shellcheck, so editing it alone would never check it; and a SARIF
result carrying no ruleId aborted the summary, because indexing the rule table
with null is an error rather than a miss.
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

⚠️MegaLinter analysis: Success with warnings

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

❌ [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/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
❌ [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-summarize-sarif-findings.sh
✅ [SUCCESS] scripts/validate-alert-coverage.sh
✅ [SUCCESS] scripts/wait-for-platform-flux-revision.sh
⚠️ REPOSITORY / checkov - 73 errors
16 |     - secretKey: red**********

Check: CKV_SECRET_6: "Base64 High Entropy String"
	FAILED for resource: HIDDEN_BY_MEGALINTER	File: /k8s/bases/apps/fleetdm/helm-release.yaml:328-329
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/secrets-policies/secrets-policy-index/git-secrets-6

		328 |         existingSecretPasswordKey: red**********

Check: CKV_SECRET_6: "Base64 High Entropy String"
	FAILED for resource: HIDDEN_BY_MEGALINTER	File: /k8s/bases/apps/headlamp/external-secret.yaml:15-16
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/secrets-policies/secrets-policy-index/git-secrets-6

		15 |     - secretKey: cli**********

Check: CKV_SECRET_6: "Base64 High Entropy String"
	FAILED for resource: HIDDEN_BY_MEGALINTER	File: /k8s/bases/apps/umami/helm-release.yaml:118-119
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/secrets-policies/secrets-policy-index/git-secrets-6

		118 |         existingSecret: umam**********

Check: CKV_SECRET_6: "Base64 High Entropy String"
	FAILED for resource: HIDDEN_BY_MEGALINTER	File: /k8s/bases/apps/wedding-app/external-secret-ghcr-auth.yaml:34-35
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/secrets-policies/secrets-policy-index/git-secrets-6

		34 |     - secretKey: dock**********

Check: CKV_SECRET_6: "Base64 High Entropy String"
	FAILED for resource: HIDDEN_BY_MEGALINTER	File: /k8s/bases/infrastructure/controllers/dex/external-secret.yaml:30-31
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/secrets-policies/secrets-policy-index/git-secrets-6

		30 |     - secretKey: cli**********

Check: CKV_SECRET_6: "Base64 High Entropy String"
	FAILED for resource: HIDDEN_BY_MEGALINTER	File: /k8s/bases/infrastructure/controllers/velero/helm-release.yaml:108-109
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/secrets-policies/secrets-policy-index/git-secrets-6

		108 |       existingSecret: veler**********

Check: CKV_SECRET_6: "Base64 High Entropy String"
	FAILED for resource: HIDDEN_BY_MEGALINTER	File: /k8s/bases/infrastructure/external-secrets/ghcr-auth.yaml:25-26
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/secrets-policies/secrets-policy-index/git-secrets-6

		25 |     - secretKey: dock**********

Check: CKV_SECRET_6: "Base64 High Entropy String"
	FAILED for resource: HIDDEN_BY_MEGALINTER	File: /k8s/bases/infrastructure/resource-graph-definitions/tenant/resource-graph-definition.yaml:166-167
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/secrets-policies/secrets-policy-index/git-secrets-6

		166 |             - secretKey: dock**********

Check: CKV_SECRET_6: "Base64 High Entropy String"
	FAILED for resource: HIDDEN_BY_MEGALINTER	File: /k8s/bases/infrastructure/vault-config/external-secret.yaml:15-16
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/secrets-policies/secrets-policy-index/git-secrets-6

		15 |     - secretKey: dex-**********

Check: CKV_SECRET_6: "Base64 High Entropy String"
	FAILED for resource: HIDDEN_BY_MEGALINTER	File: /k8s/bases/infrastructure/vault-seed/push-secret-seed-actual-budget-enablebanking.yaml:26-27
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/secrets-policies/secrets-policy-index/git-secrets-6

		26 |         secretKey: app**********

Check: CKV_SECRET_6: "Base64 High Entropy String"
	FAILED for resource: HIDDEN_BY_MEGALINTER	File: /k8s/bases/infrastructure/vault-seed/push-secret-seed-oauth2-proxy-cookie-secret.yaml:17-18
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/secrets-policies/secrets-policy-index/git-secrets-6

		17 |         secretKey: oauth2**********

Check: CKV_SECRET_6: "Base64 High Entropy String"
	FAILED for resource: HIDDEN_BY_MEGALINTER	File: /k8s/bases/infrastructure/vault-seed/push-secret-seed-r2-credentials.yaml:44-45
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/secrets-policies/secrets-policy-index/git-secrets-6

		44 |         secretKey: r2_a**********

Check: CKV_SECRET_6: "Base64 High Entropy String"
	FAILED for resource: HIDDEN_BY_MEGALINTER	File: /k8s/bases/infrastructure/vault-seed/push-secret-seed-r2-credentials.yaml:49-50
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/secrets-policies/secrets-policy-index/git-secrets-6

		49 |         secretKey: r2_se**********

Check: CKV_SECRET_6: "Base64 High Entropy String"
	FAILED for resource: HIDDEN_BY_MEGALINTER	File: /k8s/bases/infrastructure/vault-seed/push-secret-seed-velero-repo-credentials.yaml:36-37
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/secrets-policies/secrets-policy-index/git-secrets-6

		36 |         secretKey: repo**********

Check: CKV_SECRET_6: "Base64 High Entropy String"
	FAILED for resource: HIDDEN_BY_MEGALINTER	File: /k8s/bases/infrastructure/vault-seed/secret-actual-budget-encryption-placeholder.yaml:25-26
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/secrets-policies/secrets-policy-index/git-secrets-6

		25 |   password: PL**********

Check: CKV_SECRET_6: "Base64 High Entropy String"
	FAILED for resource: HIDDEN_BY_MEGALINTER	File: /k8s/providers/hetzner/apps/unifi/external-secret-wireguard.yaml:26-27
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/secrets-policies/secrets-policy-index/git-secrets-6

		26 |     - secretKey: pr**********

Check: CKV_SECRET_6: "Base64 High Entropy String"
	FAILED for resource: HIDDEN_BY_MEGALINTER	File: /k8s/providers/hetzner/apps/unifi/external-secret-wireguard.yaml:30-31
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/secrets-policies/secrets-policy-index/git-secrets-6

		30 |     - secretKey: pee**********

Check: CKV_SECRET_6: "Base64 High Entropy String"
	FAILED for resource: HIDDEN_BY_MEGALINTER	File: /k8s/providers/hetzner/infrastructure/cluster-issuers/cloudflare-api-token-external-secret.yaml:15-16
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/secrets-policies/secrets-policy-index/git-secrets-6

		15 |     - secretKey: ap**********

Check: CKV_SECRET_6: "Base64 High Entropy String"
	FAILED for resource: HIDDEN_BY_MEGALINTER	File: /k8s/providers/hetzner/infrastructure/external-dns/external-secret.yaml:24-25
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/secrets-policies/secrets-policy-index/git-secrets-6

		24 |     - secretKey: ap**********

github_actions scan results:

Passed checks: 215, Failed checks: 0, Skipped checks: 1

(Truncated to last 6666 characters out of 30398)
⚠️ SPELL / cspell - 2482 errors
"Sigstore's",
        "Synchronised",
        "Syscall",
        "TOCTOU",
        "Talosctl",
        "Testkube",
        "UNCORDON",
        "Umami",
        "Uncordon",
        "Uncordoned",
        "Unifi",
        "Upjet",
        "VXLAN",
        "Valkey",
        "Velero",
        "Velero's",
        "Virt",
        "WLANs",
        "Yubi",
        "ZIZMOR",
        "admissionregistration",
        "alertmanager",
        "alertname",
        "analysed",
        "anchore",
        "antiaffinity",
        "apid",
        "apiserver",
        "apiserver's",
        "argjson",
        "artipacked",
        "ascoachingogvaner",
        "ascoachingogvaner's",
        "authorised",
        "automerged",
        "automount",
        "autoscalers",
        "awspca",
        "backupstoragelocations",
        "baselining",
        "bcdfghjklmnpqrstvwxz",
        "behaviour",
        "behavioural",
        "betterleaks",
        "bitnami",
        "blocklist",
        "browsable",
        "buildx",
        "burstable",
        "cadvisor",
        "callsite",
        "certificaterequest",
        "chokepoint",
        "ciphertext",
        "clickhouse",
        "cloudnative",
        "cloudprovider",
        "clusterissuer",
        "clusterpolicies",
        "clusterrolebindings",
        "clusterroles",
        "cmdline",
        "cnpg",
        "coderabbit",
        "configmap",
        "controlplane",
        "controlplaneio",
        "cooldown",
        "coroot",
        "covermode",
        "coverprofile",
        "crashloop",
        "crashloops",
        "creds",
        "crossplane",
        "crossview",
        "customise",
        "cutover",
        "cyclonedx",
        "daemonset",
        "datapath",
        "datreeio",
        "dbname",
        "dedupe",
        "defence",
        "deletecollection",
        "deploymentruntimeconfigs",
        "descheduler",
        "descheduling",
        "desynchronising",
        "devantler",
        "diffable",
        "dockerconfigjson",
        "docstrings",
        "dorny",
        "dpkg",
        "emptydir",
        "endgroup",
        "envsubst",
        "esac",
        "etcdctl",
        "evictable",
        "externalsecret",
        "externalsecrets",
        "fanout",
        "featureflagsource",
        "fleetdm",
        "fromdateiso",
        "gatewayapi",
        "generatable",
        "generatingpolicies",
        "genkey",
        "gethomepage",
        "gitops",
        "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",
        "microtime",
        "misconfigs",
        "mktemp",
        "mlock",
        "mutatingpolicies",
        "mutatingwebhookconfigurations",
        "myapp",
        "najsk",
        "neighbour",
        "netlink",
        "netpol",
        "netpols",
        "nftables",
        "nilnil",
        "nodepod",
        "nodeport",
        "nolint",
        "openbao",
        "opencost",
        "openfeature",
        "oras",
        "overprovisioning",
        "parallelised",
        "pasteable",
        "permissioning",
        "pipefail",
        "policyignore",
        "policyreports",
        "portforward",
        "preemptible",
        "preservingly",
        "prioritised",
        "privesc",
        "providerconfigs",
        "publishapp",
        "publishprovider",
        "pushsecret",
        "pushsecrets",
        "rdqwpktr",
        "readyz",
        "reassertions",
        "recognise",
        "recognised",
        "reconverges",
        "refreshfluxghcrauth",
        "regenerable",
        "releaserc",
        "rematerialise",
        "rematerialised",
        "repoint",
        "repointed",
        "repoints",
        "repositoryrulesets",
        "resizer",
        "restrictor",
        "retabbed",
        "rmem",
        "rolebindings",
        "rollouts",
        "rshared",
        "sarif",
        "schedulability",
        "schedulable",
        "scopeable",
        "seccomp",
        "secretbox",
        "secretstore",
        "seedable",
        "serialised",
        "serverside",
        "serviceaccount",
        "serviceaccounts",
        "sgdisk",
        "shellcheck",
        "shfmt",
        "siderolabs",
        "siderolink",
        "sigstore",
        "skmde",
        "slurpfile",
        "spiffe",
        "srole",
        "startswith",
        "statefulset",
        "statemanager",
        "stdlib",
        "storageclass",
        "syft",
        "syft's",
        "syscall",
        "sysctls",
        "talosconfig",
        "talosctl",
        "tanzu",
        "templatesyncignore",
        "thresholded",
        "tmpl",
        "tostring",
        "tracepoints",
        "trixie",
        "trustd",
        "umami",
        "umami's",
        "uncertifiable",
        "unclickable",
        "uncordon",
        "uncordoned",
        "unifi",
        "unmarshalling",
        "unparseable",
        "unrecognised",
        "unreviewed",
        "unroutable",
        "unshippable",
        "untrackable",
        "upbound",
        "updatekeys",
        "upjet",
        "upstreaming",
        "urlencode",
        "userns",
        "ushfn",
        "validatable",
        "validatealertcoverage",
        "validatingwebhookconfigurations",
        "vcunav",
        "velero",
        "volumesnapshot",
        "vxlan",
        "wffc",
        "wgpolicyk",
        "wmem",
        "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 433077)
⚠️ COPYPASTE / jscpd - 118 errors
- 420:28] (9 lines, 58 tokens)
   scripts/tests/refresh-flux-ghcr-auth/rollout_core_test.go [465:2 - 473:28]
Clone found (go)
 - scripts/tests/refresh-flux-ghcr-auth/rollout_core_test.go [463:87 - 477:5] (15 lines, 72 tokens)
   scripts/tests/refresh-flux-ghcr-auth/rollout_core_test.go [488:83 - 502:5]
Clone found (go)
 - scripts/tests/refresh-flux-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 [92:21 - 124: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 [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 [943:58 - 949: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 [1056:5 - 1062: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 [881:33 - 889:11] (9 lines, 118 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [893:130 - 901:11]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [1088:47 - 1093:2] (6 lines, 166 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [1142:46 - 1147: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   │ 19             │ 7616        │ 31069        │ 14           │ 190 (2.49%)      │ 1115 (3.59%)      │
├────────┼────────────────┼─────────────┼──────────────┼──────────────┼──────────────────┼───────────────────┤
│ go     │ 20             │ 11852       │ 90507        │ 38           │ 330 (2.78%)      │ 3350 (3.70%)      │
├────────┼────────────────┼─────────────┼──────────────┼──────────────┼──────────────────┼───────────────────┤
│ python │ 4              │ 807         │ 5606         │ 2            │ 14 (1.73%)       │ 111 (1.98%)       │
├────────┼────────────────┼─────────────┼──────────────┼──────────────┼──────────────────┼───────────────────┤
│ txt    │ 57             │ 3378        │ 109125       │ 64           │ 2417 (71.55%)    │ 80219 (73.51%)    │
├────────┼────────────────┼─────────────┼──────────────┼──────────────┼──────────────────┼───────────────────┤
│ Total: │ 100            │ 23653       │ 236307       │ 118          │ 2951 (12.48%)    │ 84795 (35.88%)    │
└────────┴────────────────┴─────────────┴──────────────┴──────────────┴──────────────────┴───────────────────┘
Found 118 clones.
HTML report saved to megalinter-reports/copy-paste/jscpd-report.html
ERROR: jscpd found too many duplicates (12.5%) over threshold (0.0%)
time: 735.790ms

(Truncated to last 6666 characters out of 27618)
⚠️ 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:487: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 801752)

✅ 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 Jul 28, 2026

Copy link
Copy Markdown
Contributor

@devantler I’ll review the changes in #2847.

✅ 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 Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@devantler, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

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

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: aaa0bda8-3d80-49de-acdd-47dd81a91595

📥 Commits

Reviewing files that changed from the base of the PR and between 0137bfa and 4b8aaaf.

📒 Files selected for processing (4)
  • .github/workflows/ci.yaml
  • .github/workflows/validate-main.yaml
  • scripts/summarize-sarif-findings.sh
  • scripts/tests/test-summarize-sarif-findings.sh
📝 Walkthrough

Walkthrough

Adds a Bash utility that parses Kubescape SARIF output, aggregates findings by control, prints reports, and optionally writes GitHub step summaries. Adds tests for SARIF shapes, failure modes, rule resolution, output formatting, normalization behavior, and CI wiring. Updates validation workflows to preserve raw SARIF before normalization and run the summary as a non-blocking post-scan step. Compliance-threshold gating and uploaded SARIF remain unchanged.

Possibly related issues

  • Issue 2829 — Extends the Kubescape SARIF-to-Code-Scanning workflow with post-scan finding reporting.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: reporting what Kubescape actually found in CI logs.
Description check ✅ Passed The description is on-topic and matches the SARIF reporting changes in this pull request.
Linked Issues check ✅ Passed The changes meet #2846 by summarizing raw SARIF findings, including zero findings and locationless results, without changing the gate or upload.
Out of Scope Changes check ✅ Passed The added workflow, script, and tests stay aligned with the reporting fix and do not appear to introduce unrelated scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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/summarize-sarif-findings.sh`:
- Around line 48-85: Add a dedicated test script for the jq report logic in
summarize-sarif-findings.sh, following the repository’s existing script-test
conventions. Cover minimal SARIF fixtures for zero findings, a result without
ruleId, and multiple controls, asserting the generated summaries and counts so
regressions between SARIF findings and console output are detected.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b1a343e1-d682-4028-9029-6f793e49c6e3

📥 Commits

Reviewing files that changed from the base of the PR and between 0137bfa and 3b111dc.

📒 Files selected for processing (2)
  • .github/workflows/ci.yaml
  • scripts/summarize-sarif-findings.sh
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
**/*

📄 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/summarize-sarif-findings.sh
.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
🧠 Learnings (1)
📚 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
🔇 Additional comments (3)
scripts/summarize-sarif-findings.sh (1)

1-99: LGTM!

The jq pipeline is carefully defended against the SARIF shape variance it documents (missing .runs, missing rule metadata, results with no ruleId), and the null-ruleId guard before indexing $rules[$id] is required — jq raises Cannot index object with null otherwise, so this isn't just defensive noise.

.github/workflows/ci.yaml (2)

63-63: LGTM!


457-482: 🩺 Stability & Availability | ⚡ Quick win

Step can still fail the build despite being described as "cannot fail the build over findings."

scripts/summarize-sarif-findings.sh deliberately exits non-zero on a parse/usage error (its own header: "a broken summary is worth surfacing, but the caller decides whether that is fatal"). This step has no continue-on-error, so under the runner's default run: shell (bash -e), such a failure fails the validate job — a path unrelated to the compliance-threshold gate the PR objectives say must be preserved. In practice this should be rare since normalize-sarif already validates the file, but as written the "informational only, cannot fail the build" comment (lines 474-476) isn't strictly guaranteed.

If a broken summary should never block merge, add continue-on-error: true; if it's intentional (a truly malformed SARIF is worth failing loudly), consider updating the comment to say so explicitly.

🛡️ Optional fix to guarantee non-blocking behavior
       - name: 📋 Summarize Kubescape findings
+        continue-on-error: true
         # Kubescape's own console table shows only a subset of what it finds:

Comment thread scripts/summarize-sarif-findings.sh
The summary fails by under-reporting rather than by crashing, so a dropped
control reads as a clean scan. Pin the grouping, counts and ordering, plus the
two SARIF shapes that abort a naive jq pipeline: a result with no ruleId, and a
ruleId absent from the rule table.

Also stop the summary step from acting as a second merge gate. The script exits
non-zero on a SARIF it cannot parse and leaves the caller to decide whether that
is fatal; this caller had made no decision, so a reporting bug failed the
validate job the step's own comment said it could not fail.
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Fixed in f45f1637 — valid finding, and the gap was real: the fixtures behind this logic were only ever exercised by hand.

scripts/tests/test-summarize-sarif-findings.sh now pins the report, following the scripts/tests/test-*.sh convention (bash + the runner's jq, no cluster). Cases: zero findings, a SARIF with no runs at all, grouping/counts/descending order across two controls, level+description resolved from the rule table, a result with no ruleId, a ruleId absent from the rule table, the GITHUB_STEP_SUMMARY copy matching the log, no stray writes when that variable is unset, and each exit-2 path (unparseable, missing file, no argument, too many arguments).

Wired in both directions, so it cannot decay silently: a new 📋 Validate Kubescape finding summary step invokes it, and the test file itself is in the k8s path filter — the test asserts both, so deleting either wiring turns it red.

8 ablations, each RED on its own assertion (not merely red):

ablation failing assertion
null-ruleId guard removed a result with no ruleId must not abort the summary, got exit 2
sort_by(-.count) removed controls must be ordered by descending count
zero-findings message emptied an empty SARIF must state zero findings explicitly
// "unknown" fallback removed a finding whose rule is missing from the table must report an unknown level
step-summary block disabled the step summary must carry a heading
parse failure exits 0 unparseable SARIF must exit 2, got 0
CI step invocation removed CI must invoke this test, or it never runs
path-filter entry removed this test must be in the k8s path filter

Also acted on the 457-482 note in the same review, which I judged correct on merit rather than informational: the step comment claimed it "cannot fail the build" while the script exits 2 on a SARIF it cannot parse and explicitly leaves that decision to its caller — a decision this caller had never actually made. Added continue-on-error: true so a reporting bug annotates loudly without becoming a second merge gate, and rewrote the comment to state the decision instead of asserting an outcome the YAML did not produce.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

@coderabbitai review

Head f45f1637 adds the regression test requested in the previous review, plus continue-on-error on the summary step so it cannot act as a second merge gate.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

@devantler I’ll review the updates in #2847, including the regression coverage and the non-blocking summary behavior.

✅ 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

User evaluation at f45f1637 — exercised in real CI on this PR, not reasoned from the diff.

🧪 Validate Manifests run 30397158763, job Validate Manifests:

step result observed output
11. 📋 Validate Kubescape finding summary (new) success PASS: test-summarize-sarif-findings.sh
20. 📋 Summarize Kubescape findings success Kubescape: 4 finding(s) across 2 control(s).

So the new guard runs where it was wired rather than only locally, and adding continue-on-error did not change what the summary reports — the same 4 findings across 2 controls this PR was opened to surface.

Locally: 8 ablations each RED on their own assertion (table in the thread above), shellcheck clean, actionlint clean on the workflow.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

CodeRabbit lane: cr:no-gate@f45f1637. Its summary comment updated in place at 20:44:41Z carrying the rate limited by coderabbit.ai marker and a 38-minute window — the review did not run.

Worth recording precisely, because that same comment contains between 3b111dc6c5… and f45f1637…, i.e. it names the current head inside the rate-limit block. Head-naming is the walkthrough scope, not evidence a review happened; scoring it as a green here would have promoted this PR on a review that never ran.

Advancing to Codex (lane 2) rather than waiting out the window.

@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: f45f163717

ℹ️ 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 .github/workflows/ci.yaml Outdated
Comment thread .github/workflows/ci.yaml Outdated
normalize-sarif-paths.sh drops results with an empty artifactLocation.uri
because Code Scanning cannot anchor an alert without a file. Summarizing that
file therefore omitted cluster-level controls with no manifest behind them —
under-reporting the exact findings this summary exists to surface. Summarize a
copy taken before normalization; the upload still reads the normalized file.

Also summarize on push to main. ci.yaml scans on pull_request only, so the
kubescape-baseline job is the sole default-branch scan; without a summary there
the log still reads as a clean scan while the same run uploads findings.
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Both P2s verified against the code and both fixed in ffcf6531. Neither was cosmetic — the first one falsified this PR's own premise.

1. "Summarize before dropping locationless findings" — correct, and the more serious of the two.
scripts/normalize-sarif-paths.sh:92 does exactly what you describe:

| .results |= map(select((.locations[0].physicalLocation.artifactLocation.uri // "") != ""))

So summarizing the normalized file omits cluster-level controls with no manifest behind them — the precise class of finding that is otherwise invisible, which is what this PR exists to fix. The step comment at ci.yaml:440-445 already documented the shape (C-0002, measured 0 of 4 findings with exceptions applied, 1 of 70 without), so this was latent rather than currently firing — but "currently zero" is not a property worth depending on.

Fixed by splitting the two consumers: cp the scan output to kubescape.raw.sarif before normalization, upload the normalized file, summarize the raw one.

2. "Summarize the default-branch scan too" — also correct. validate-main.yaml's kubescape-baseline job scans, normalizes and uploads on push to main, and its own header says ci.yaml is pull_request-only. Without a summary there, the contradiction stands on the one path nobody opens a PR to read. Added the same step (reading the raw copy, continue-on-error).

The guard is wiring-level, because that is what broke. A test over the script alone would have passed throughout. Both workflows are now asserted to copy before normalizing and to summarize the raw file, with an explicit rejection of the normalized path. Plus a differential that keeps the split honest: it runs the real normalizer over a fixture carrying one located and one locationless finding, asserts the normalizer drops it (1 finding across 1 control), then asserts the summary over the raw fixture keeps it (2 findings across 2 controls). If the normalizer ever stops dropping, that precondition fails loudly and says the raw copy is no longer needed, rather than leaving a cargo-culted cp behind.

4 new ablations, each RED on its own assertion:

ablation failing assertion
ci.yaml summarizes the normalized file ci.yaml must summarize the raw copy, not the normalized file
validate-main summary step deleted validate-main.yaml must summarize the raw copy, not the normalized file
cp removed ci.yaml must copy the scan output before normalization
normalizer stops dropping empty-uri PRECONDITION: the normalizer must drop the locationless finding

shellcheck and actionlint clean on both workflows.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

@coderabbitai review

Head ffcf6531 fixes both P2s from the previous Codex review: the summary now reads the pre-normalization SARIF (the normalizer drops empty-uri results), and validate-main.yaml summarizes the default-branch scan too.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Changed files and patch at requested head ==='
git diff --stat f45f1637..ffcf6531
git diff --find-renames --find-copies f45f1637..ffcf6531 -- \
  .github/workflows/ci.yaml \
  .github/workflows/validate-main.yaml \
  scripts/summarize-sarif-findings.sh \
  scripts/normalize-sarif-paths.sh \
  scripts/tests/test-summarize-sarif-findings.sh

echo '=== Workflow/script outlines ==='
ast-grep outline .github/workflows/ci.yaml --items all || true
ast-grep outline .github/workflows/validate-main.yaml --items all || true
ast-grep outline scripts/summarize-sarif-findings.sh --items all || true
ast-grep outline scripts/tests/test-summarize-sarif-findings.sh --items all || true

echo '=== Relevant workflow references ==='
rg -n -C 8 \
  'kubescape\.(raw\.)?sarif|normalize-sarif|summarize-sarif|Summarize Kubescape|Validate Kubescape|continue-on-error' \
  .github/workflows/ci.yaml .github/workflows/validate-main.yaml

echo '=== Summary script and regression test ==='
cat -n scripts/summarize-sarif-findings.sh
cat -n scripts/tests/test-summarize-sarif-findings.sh

echo '=== Normalizer result filtering ==='
rg -n -C 12 'results|artifactLocation|physicalLocation|locations' scripts/normalize-sarif-paths.sh

echo '=== Repository test conventions and workflow references ==='
fd '^test-.*\.sh$' scripts/tests -x sh -c 'echo --- "$1"; sed -n "1,100p" "$1"' sh {} \; | head -n 500
rg -n -C 4 'test-summarize-sarif-findings\.sh|summarize-sarif-findings\.sh' .

Length of output: 75762


🏁 Script executed:

#!/bin/bash
set -euo pipefail

base="$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main)"
echo "=== Base and complete PR file list ==="
printf 'base=%s\nhead=%s\n' "$base" "$(git rev-parse HEAD)"
git diff --name-status "$base"..HEAD
git diff --check "$base"..HEAD

echo '=== Complete PR patches ==='
git diff --find-renames "$base"..HEAD -- \
  .github/workflows/ci.yaml \
  .github/workflows/validate-main.yaml \
  scripts/summarize-sarif-findings.sh \
  scripts/tests/test-summarize-sarif-findings.sh \
  scripts/normalize-sarif-paths.sh

echo '=== validate-main workflow context ==='
sed -n '1,235p' .github/workflows/validate-main.yaml

echo '=== ci validate-job context ==='
sed -n '300,515p' .github/workflows/ci.yaml

echo '=== SARIF producer/consumer inventory ==='
rg -n -C 3 \
  'ksail workload scan|kubescape\.sarif|kubescape\.raw\.sarif|upload-sarif|normalize-sarif-paths|summarize-sarif-findings' \
  .github scripts

echo '=== Shell static diagnostics, if available ==='
if command -v shellcheck >/dev/null 2>&1; then
  shellcheck scripts/summarize-sarif-findings.sh scripts/normalize-sarif-paths.sh scripts/tests/test-summarize-sarif-findings.sh
else
  echo 'shellcheck unavailable'
fi

echo '=== YAML syntax validation using Ruby Psych, if available ==='
if command -v ruby >/dev/null 2>&1; then
  ruby -e 'require "yaml"; ARGV.each { |p| YAML.load_file(p); puts "valid: #{p}" }' \
    .github/workflows/ci.yaml .github/workflows/validate-main.yaml
else
  echo 'ruby unavailable'
fi

Length of output: 74350


@devantler Reviewed ffcf6531; no findings.

Verified that both workflows preserve the pre-normalization SARIF for informational reporting while continuing to upload the normalized SARIF. The summary remains non-blocking, and the new regression test exercises the locationless-finding differential plus both workflow paths. Static checks also passed: git diff --check, ShellCheck, and YAML parsing.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

CodeRabbit lane: cr:no-gate@ffcf6531 — and deliberately so, even though its reply says "Reviewed ffcf6531; no findings."

That verdict arrived on the chat surface (<!-- This is an auto-generated reply by CodeRabbit -->), and there are zero CodeRabbit review objects at this head. The gate is defined on the artifact, not the sentence: a command reply is explicitly not a qualifying green, however diff-accurate it reads — and this one is accurate, it ran shellcheck and parsed both workflows. It is still the shape CodeRabbit produces when its review pipeline is throttled but its chat is not, so promoting on it would be promoting on a review that never entered the record.

Advancing to Codex. Not counting this as a failure of the change — only as a lane that did not deliver a gate-satisfying artifact.

@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: ffcf65317f

ℹ️ 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 .github/workflows/ci.yaml Outdated
Comment thread scripts/summarize-sarif-findings.sh Outdated
Comment thread scripts/summarize-sarif-findings.sh Outdated
…lizer abort

Three ways the summary could still under-report or read falsely clean:

A file that is valid JSON but not a SARIF document ({}, {"runs":null}) flowed
through the optional iteration and reported 'Kubescape: 0 findings' — a broken
scan indistinguishable from a clean one, which is the ambiguity this reporting
exists to remove. Require .runs to be an array; an empty array stays a
legitimate zero.

SARIF lets a result name its rule by ruleIndex instead of ruleId. Grouping on
ruleId alone collapsed every such result into one '<no rule id>' bucket and
discarded the metadata. Resolve per run, since the index is run-scoped, and
bounds-check it so an out-of-range index is a miss rather than an attribution to
the last rule in the table.

The summary was gated on the normalizer succeeding, so an unresolvable path
skipped it exactly when the findings mattered most. Gate it on the raw copy
instead, emitted before the normalizer runs.
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

All three P2s reproduced first, then fixed in 91bdac55. None were speculative — here is the before, measured on this branch:

{}                     rc=0  Kubescape: 0 findings in this scan.
{"version":"2.1.0"}    rc=0  Kubescape: 0 findings in this scan.
{"runs":null}          rc=0  Kubescape: 0 findings in this scan.
two ruleIndex results  →     "<no rule id>  unknown  — 2 finding(s)"   (one bucket, metadata gone)

1. Structurally invalid SARIF read as a clean scan. This was the worst of the three, because it is the failure this PR exists to remove wearing the PR's own output. .runs must now be an array or the script exits 2 naming the reason; "runs": [] stays a legitimate zero.

This one also corrected my own test, which had asserted {"version":"2.1.0"} → exit 0 as desired behaviour. It was not; it was the bug, written down as an expectation. The case now asserts exit 2, and the comment says plainly that an earlier revision had it backwards.

2. ruleIndex collapsed into one bucket. Resolved per run, since the index is scoped to its own run.tool.driver.rules and resolving against a concatenated list would mis-attribute across runs. Bounds-checked, because jq indexes backwards from -1 — an unguarded ruleIndex: -1 would have silently labelled the finding with the last rule in the table, which is worse than not resolving it at all.

3. Summary skipped when normalization aborts. Correct, and it was a consequence of my own previous commit: I changed the summary's input to the raw copy but left its gate on sarif=ready. Now gated on raw=ready, emitted before the normalizer runs — after it would be unreachable, since an unresolvable path aborts the step. Both workflows.

5 new ablations, each RED on its own assertion:

ablation failing assertion
.runs shape check removed a non-SARIF document {} must name the reason
ruleIndex resolution removed ruleIndex results must resolve to distinct controls, not collapse into one
bounds check removed an out-of-range ruleIndex must not be attributed to a real control
gate back on sarif=ready must gate the summary on the raw copy, not on normalization succeeding
raw=ready moved after the normalizer must emit raw=ready BEFORE the normalizer (raw at 427, normalizer at 426)

shellcheck and actionlint clean.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

@coderabbitai review

Head 91bdac55 fixes three P2s from the last Codex round: a valid-JSON non-SARIF file no longer reports as a clean scan, ruleIndex-identified results resolve per run instead of collapsing into one bucket, and the summary is gated on the raw copy rather than on normalization succeeding.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

User evaluation re-run at 91bdac55 — necessary, not a formality: the last two commits changed the script's behaviour (strict .runs validation, ruleIndex resolution), so the evidence from f45f1637 no longer covers it.

🧪 Validate Manifests run 30400022118:

step result observed output
11. 📋 Validate Kubescape finding summary success PASS: test-summarize-sarif-findings.sh
20. 📋 Summarize Kubescape findings success Kubescape: 4 finding(s) across 2 control(s).

The second row is the one worth reading: identical to the output at f45f1637 and at 2486ebd2. Stricter validation and per-run ruleIndex resolution changed nothing on this repository's actual scan output — they are behaviour-preserving on real data and only fire on the malformed shapes that previously read as clean. That is the outcome I wanted from these fixes: no new noise on the happy path, no silent zero on a broken one.

Full check state at this head: 19 pass, 11 skipped, 0 failing, 0 unresolved threads, mergeStateStatus: CLEAN, and mega-linter passed without pushing (so the head is stable).

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

base="$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main)"
printf '=== Revision ===\nbase=%s\nhead=%s\n' "$base" "$(git rev-parse HEAD)"

printf '\n=== Changed files ===\n'
git diff --name-status "$base"..HEAD
git diff --check "$base"..HEAD

printf '\n=== Relevant outlines ===\n'
ast-grep outline scripts/summarize-sarif-findings.sh --items all || true
ast-grep outline scripts/tests/test-summarize-sarif-findings.sh --items all || true

printf '\n=== Complete PR patch: relevant files ===\n'
git diff --find-renames "$base"..HEAD -- \
  .github/workflows/ci.yaml \
  .github/workflows/validate-main.yaml \
  scripts/summarize-sarif-findings.sh \
  scripts/tests/test-summarize-sarif-findings.sh \
  scripts/normalize-sarif-paths.sh

printf '\n=== Summary script ===\n'
cat -n scripts/summarize-sarif-findings.sh

printf '\n=== Summary regression tests ===\n'
cat -n scripts/tests/test-summarize-sarif-findings.sh

printf '\n=== CI Kubescape contexts ===\n'
rg -n -C 18 \
  'kubescape\.(raw\.)?sarif|normalize-sarif|summarize-sarif|raw=ready|sarif=ready|Summarize Kubescape|Validate Kubescape' \
  .github/workflows/ci.yaml .github/workflows/validate-main.yaml

printf '\n=== Normalizer filtering context ===\n'
rg -n -C 12 'results|artifactLocation|physicalLocation|locations' scripts/normalize-sarif-paths.sh

printf '\n=== Static diagnostics (when installed) ===\n'
if command -v shellcheck >/dev/null 2>&1; then
  shellcheck scripts/summarize-sarif-findings.sh scripts/tests/test-summarize-sarif-findings.sh scripts/normalize-sarif-paths.sh
else
  echo 'shellcheck unavailable'
fi
if command -v actionlint >/dev/null 2>&1; then
  actionlint .github/workflows/ci.yaml .github/workflows/validate-main.yaml
else
  echo 'actionlint unavailable'
fi

Length of output: 98287


@devantler I found one correctness issue in the multi-run SARIF handling.

P2 — Keep rule metadata scoped to each SARIF run. scripts/summarize-sarif-findings.sh resolves ruleIndex against each run correctly, but Line 59 then flattens every run’s tool.driver.rules into one $rules object keyed only by id. When two runs define the same rule ID with different level or description, the later run silently overwrites the earlier metadata, and all grouped findings are reported with that last definition.

Carry the resolved rule metadata alongside each result before grouping (or key the lookup by run plus rule ID), rather than looking it up from a global ID-only table. A regression fixture with two runs sharing a rule ID but differing metadata would pin this behavior.

✅ 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.

Two runs may define the same rule id with different metadata. The rule table was
built by flattening every run and calling from_entries, which keeps the LAST
definition of a duplicate key — so findings from the first run were reported with
the second run's level and description. A confident wrong attribution, of the
same kind the ruleIndex bounds check prevents.

Each result now resolves against its own run and carries the answer forward, and
grouping keys on the resolved triple: runs that agree still merge into one
control, runs that disagree are reported separately instead of one silently
overwriting the other.
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

CodeRabbit lane at 91bdac55: cr:no-gate again — second time on this PR. Zero review objects at that head; the response was another auto-generated reply by CodeRabbit, and its own footer explains why: "CodeRabbit is an incremental review system and does not re-review already reviewed commits."

But the content was right, and it is fixed in 9986fa74. Not counting it as the gate does not mean ignoring it — the claim was that rule metadata is looked up from a table flattened across runs, so a duplicate rule id resolves to the last definition. Reproduced before changing anything:

two runs, both defining C-0001, different metadata:
  before →  Kubescape: 2 finding(s) across 1 control(s).
              C-0001  note  SECOND-RUN-DESC  — 2 finding(s)     ← first run's finding, second run's metadata
  after  →  Kubescape: 2 finding(s) across 2 control(s).
              C-0001  error  FIRST-RUN-DESC   — 1 finding(s)
              C-0001  note   SECOND-RUN-DESC  — 1 finding(s)

from_entries keeps the last value for a duplicate key, so this was a confident wrong attribution — the same failure mode the ruleIndex bounds check exists to prevent, and worse than an absent one. Each result now resolves against its own run and carries the answer forward; grouping keys on the resolved triple.

The other half matters as much: runs that agree must still merge, or every ordinary multi-run report fragments. Pinned both directions —

fixture expected
two runs, conflicting metadata 2 finding(s) across 2 control(s), each keeping its own run's level/description
two runs, identical metadata 2 finding(s) across 1 control(s)

2 ablations, each RED on its own assertion: restoring the flattened table fails the conflicting-runs case, and grouping on .id alone fails it too. Also corrected the now-stale header comment that still described a single id-indexed table.

Advancing to Codex for the gate.

@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/tests/test-summarize-sarif-findings.sh`:
- Around line 57-62: Update run_summary to unset or clear GITHUB_STEP_SUMMARY
only for fixture executions, preventing non-summary tests from modifying the
ambient GitHub Actions step summary. Preserve the explicit fixture’s ability to
set GITHUB_STEP_SUMMARY when it intentionally tests summary output.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a453657d-4a58-4cd9-b681-02cb22b92308

📥 Commits

Reviewing files that changed from the base of the PR and between 3b111dc and 91bdac5.

📒 Files selected for processing (4)
  • .github/workflows/ci.yaml
  • .github/workflows/validate-main.yaml
  • scripts/summarize-sarif-findings.sh
  • scripts/tests/test-summarize-sarif-findings.sh
📜 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/validate-main.yaml
  • .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/summarize-sarif-findings.sh
  • scripts/tests/test-summarize-sarif-findings.sh
🧠 Learnings (1)
📚 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/validate-main.yaml
  • .github/workflows/ci.yaml
🔇 Additional comments (5)
.github/workflows/ci.yaml (1)

64-64: LGTM!

Also applies to: 252-260, 411-426, 483-518

.github/workflows/validate-main.yaml (1)

164-171: LGTM!

Also applies to: 192-211

scripts/summarize-sarif-findings.sh (2)

58-59: Rule metadata still flattened into a global ID-keyed table.

$rules is built once from every .runs[].tool.driver.rules flattened together and keyed only by .id (line 59); from_entries keeps the last entry on a key collision. When two runs carry the same rule id with different metadata, $rules[$id] (line 88) reports whichever run's metadata happened to load last, mis-attributing level/description for the other run's findings. This is the exact scenario the PR's own review discussion flagged as a remaining P2.

A fix needs to resolve rule metadata per-run at the same point $runRules is already in scope (lines 66-75), carrying {id, level, desc} per result instead of just the id string, so grouping never needs to fall back to a merged global table.

Also applies to: 87-88


1-57: LGTM!

Also applies to: 60-108, 110-122

scripts/tests/test-summarize-sarif-findings.sh (1)

1-56: LGTM!

Also applies to: 64-231, 239-415

Comment thread scripts/tests/test-summarize-sarif-findings.sh
GITHUB_STEP_SUMMARY is set for every Actions step and inherited by child
processes, so each fixture run appended its output to the real job summary —
2772 bytes across 12 blocks, including 'Kubescape: 4 finding(s) across 2
control(s)', which is indistinguishable from the genuine scan report on the same
run page. A test for a findings reporter must not publish fabricated findings.

Clear the variable for every fixture run; the one case that exercises the
step-summary output sets its own path. An ambient sentinel now stands in for the
job summary and is asserted empty at the end, so the isolation is proven rather
than assumed.
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Valid, and 🟠 Major is the right severity — fixed in 4b8aaafd. Measured before changing anything:

GITHUB_STEP_SUMMARY=<sentinel> bash scripts/tests/test-summarize-sarif-findings.sh
  → 2772 bytes, 12 "### Kubescape findings" blocks, including:
      Kubescape: 4 finding(s) across 2 control(s).
      Kubescape: 2 finding(s) across 2 control(s).

GITHUB_STEP_SUMMARY is set for every Actions step and inherited by the child, so under CI every fixture run was appending to the real job summary. The reason this is more than tidiness: Kubescape: 4 finding(s) across 2 control(s) is character-for-character what the genuine scan prints in step 20 of the same job. The test would have published fabricated findings onto the run page of the PR whose entire purpose is a findings report you can trust — the failure mode this PR exists to remove, reintroduced by its own test.

Fixed by clearing the variable for every fixture run (env -u), leaving the one case that deliberately exercises the step-summary output to set its own path.

Guarded rather than just fixed. The suite now points the ambient variable at a sentinel that stands in for the job summary and asserts it is empty at the end, so the isolation is proven every run instead of assumed. Ablation — restoring the inherited environment — fails with the fixtures wrote 2772 bytes to the ambient GITHUB_STEP_SUMMARY, i.e. the guard reproduces the exact defect you reported.

After: 0 bytes.

One process note, since it affected how I scored your lane: this review object landed at 21:33:17Z for 91bdac55, about ten minutes after the chat-surface reply for the same head. I had recorded cr:no-gate at 21:23 on the strength of zero review objects at that point. The finding was still worth acting on, and the head has since moved twice — so the gate still needs a review at the current head 4b8aaafd.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

@coderabbitai full review

Using full review rather than review: the previous request at 91bdac55 returned a chat reply whose footer said CodeRabbit "does not re-review already reviewed commits", and the branch has since gained two commits — the per-run rule-metadata fix and the step-summary isolation fix for your 🟠 Major.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

@devantler I’ll perform a full review of the current head, including the per-run metadata resolution and test-summary isolation changes.

✅ Action performed

Full review finished.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 42 minutes.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

CodeRabbit lane: cr:no-gate@4b8aaafd — this one on a genuine failure signal, unlike my earlier premature call on 91bdac55. The summary comment carries the rate limited by coderabbit.ai marker and a 42-minute window, with 0 review objects at this head.

Worth stating the distinction, because I got it wrong once on this PR: at 91bdac55 I recorded no-gate 41 seconds after the request because a chat reply had arrived and no review object had. That was premature — CodeRabbit's real review object landed ~10 minutes later carrying the 🟠 Major about the fixtures writing to the job summary, which was well worth having. A chat reply is not a terminal state; a quota marker is.

42 minutes exceeds what this run can hold, so advancing to Codex rather than waiting it out.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: 4b8aaafdf7

ℹ️ 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".

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Readiness at 4b8aaafd — all three conditions, evidenced.

1. Programmatically tested. scripts/tests/test-summarize-sarif-findings.sh pins the report end to end, and every property was proven non-vacuous by ablation — 20 across the five rounds, each RED on its own assertion, not merely red.

2. Reviewed. green_review=codex@4b8aaafdf7Codex Review: Didn't find any major issues, reviewed commit matching headRefOid[0:10]. CodeRabbit recorded cr:no-gate on a genuine 42-minute quota marker with zero review objects at head.

Both inline comments still anchored to this head are already-resolved CodeRabbit findings that GitHub re-anchored when the head moved (created 20:29 and 21:33, before this commit existed). All 7 review threads are resolved; nothing is outstanding.

3. Tried and evaluated as a user — run 30401273612:

step result observed
11. 📋 Validate Kubescape finding summary success PASS: test-summarize-sarif-findings.sh
20. 📋 Summarize Kubescape findings success Kubescape: 4 finding(s) across 2 control(s).
job log, fixture-zero lines 0 confirms the step-summary isolation fix — the fixtures no longer emit into the real job summary

The middle row is unchanged from 2486ebd2, f45f1637 and 91bdac55: five rounds of hardening altered nothing about this repository's actual scan output. The strictness only fires on the malformed shapes that previously read as clean.

Nine findings were raised across five rounds and every one was real — three of them defects in this PR's own premise (a broken scan reported as clean, locationless findings dropped, the summary skipped exactly when normalization failed), two mis-attribution bugs where a confident wrong answer beats an absent one, and the 🟠 Major where the test itself was publishing fabricated findings onto the run page. Promoting and merging.

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.

bug(ci): the Kubescape scan log reports "All controls passed" while the same run raises Code Scanning alerts

1 participant