Skip to content

feat(flux): contrast legs for all six Flux controllers - #181

Open
entlein wants to merge 25 commits into
mainfrom
feat/flux-contrast
Open

entlein wants to merge 25 commits into
mainfrom
feat/flux-contrast

Conversation

@entlein

@entlein entlein commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Flux v2.9.3 gets the same treatment as Argo CD: one attack suite, one functional suite and one tuned SBoB per controller.

Result

controller tune score opens
source-controller 0 17
kustomize-controller 0 12
helm-controller 0 12
notification-controller 0 11
image-reflector-controller 0 11
image-automation-controller 0 11

42 asserted detections, 42 found, 0 missed, 0 false positives.

Every assertion was verified before being written

Fires: R0001 R0005 R0010 R0011 R1004 R1010 R1012
Does not fire: R0006 R0008 R1000 — shipped as probes with expectedDetections: []

This overturns an assumption in #178

R0010, R1010 and R1012 all fire here despite cat /etc/shadow returning EACCES and the hardlink failing with EXDEV. They trigger on the attempt, and they fire with a learned profile present.

#178 dropped those same assertions on argocd reasoning that a failed open cannot be observed. That reasoning is wrong. Whatever suppressed them on argocd was not the EACCES — needs revisiting separately.

Services added

Four of the seven Flux Deployments ship no Service. bobctl resolves its exec pod through one, so every exec attack would have bailed silently. example/flux-vulnerable.yaml adds a ClusterIP for kustomize, helm, image-reflector and image-automation.

Known limitation

Every container in flux-system is named manager. Alerts cannot be attributed to a component by container name alone, so the tuner's rule+container matching can cross-talk between the six legs. Not addressed here.

Verification

  • SBoBs: no unanchored opens, no YAML aliases, no cluster-specific literals, none masking /etc/shadow / SA token / /proc/1/environ
  • All six round-trip through live storage:sbob-rc5s unchanged
  • 37/37 suites pass schema validation
  • Representativeness gate: 6/6

🤖 Generated with Claude Code

https://claude.ai/code/session_01Jd9m962b1JAHdAy3rGe7nm

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds Flux deployment and benchmark wiring, Flux controller workload and validation assets, Kubescape runtime profiles and rules, and tuning scripts and configuration for Flux controllers.

Changes

Flux coverage

Layer / File(s) Summary
Flux deployment and benchmark wiring
.github/workflows/ci-bobctl-autotune.yaml, Makefile, example/flux-vulnerable.yaml, example/flux/benchmark/*
Adds Flux path coverage in CI, deployment and benchmark targets, controller metric Services, benchmark bundle and registry manifests, and the benchmark runner script.
GitOps workload driver
example/flux/drive-gitops-workload.sh
Seeds Flux sources, Kustomizations, HelmReleases, notification resources, and image automation resources, then drives reconciliations and reports controller state.
Controller attack and functional validation
example/flux-*-controller-attacks.yaml, example/flux-*-controller-functional-tests.yaml
Adds attack suites for Flux controllers and HTTP validation suites for metrics, pprof, artifact-server, and notification event paths.
Flux runtime profiles
example/flux/sbobs/*
Adds Kubescape ContainerProfile resources for Flux controllers, with filesystem, process, endpoint, ingress, egress, and selector rules.
Tuning baselines and validation
kubescape/*, scripts/*, pkg
Adds Flux tuning baselines, rule and binding changes, profile checker and restore scripts, local CI updates, and a pkg submodule update.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Makefile
  participant BenchmarkScript
  participant Registry
  participant FluxControllers
  Makefile->>FluxControllers: deploy Flux installation
  Makefile->>BenchmarkScript: run benchmark targets
  BenchmarkScript->>Registry: start local registry and publish artifacts
  BenchmarkScript->>FluxControllers: apply benchmark bundle and run phases
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.70% 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
Title check ✅ Passed The title directly describes the main change: adding contrast test suites and tuned profiles for all six Flux controllers, which is the core focus of the changeset.
Description check ✅ Passed The description is related to the changeset. It explains what was added for Flux controllers, reports verification results, and documents known limitations and findings from testing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch feat/flux-contrast
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/flux-contrast

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

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🤖 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 `@example/flux/drive-gitops-workload.sh`:
- Around line 56-58: Update the OCIRepository manifest’s apiVersion to
source.toolkit.fluxcd.io/v1, while leaving the existing ImageRepository and
ImagePolicy apiVersions unchanged.

In `@example/flux/sbobs/cp-flux-helm-controller.yaml`:
- Around line 65-71: Update the inbound metrics profile entries for the Flux
controllers, including helm-controller and the corresponding
kustomize-controller, notification-controller, and source-controller entries, to
use port 8080 matching their Service definitions; if retaining :6443, also add a
separate :8080/metrics entry so functional probes are covered.

In `@example/flux/sbobs/cp-flux-notification-controller.yaml`:
- Around line 62-68: Confirm whether the honey Alertmanager egress rule in the
shipped profile is intended to be included; if it is demo-only, remove the
outbound entries targeting alertmanager.honey.svc.cluster.local:9093 and the
honey namespace selector, otherwise ensure the profile remains valid without
requiring the repository’s demo Alert or fixed namespace.
- Line 46: Update the syscall lists at
example/flux/sbobs/cp-flux-notification-controller.yaml:46-46,
example/flux/sbobs/cp-flux-image-reflector-controller.yaml:46-46, and
example/flux/sbobs/cp-flux-source-controller.yaml:66-66 with the complete
captured syscall sets, including the observed openat, read, write, and socket
syscalls; replace the empty notification list and extend the existing lists in
the other two profiles. If the merge is intentional and syscall rules do not
consume these lists, document that behavior in the profile documentation
instead.

In `@kubescape/collapse-config.yaml`:
- Around line 16-18: Ensure the prefix rules in kubescape/collapse-config.yaml
lines 16-18 are evaluated before the generic wildcard-open collapse, then
regenerate the profiles. Update
example/flux/sbobs/cp-flux-kustomize-controller.yaml lines 52-53 to use
/tmp/kustomization-*, example/flux/sbobs/cp-flux-helm-controller.yaml lines
52-53 to use /tmp/helmrelease-*, and
example/flux/sbobs/cp-flux-source-controller.yaml lines 60-61 to use only the
prefixes its controller actually uses.

In `@kubescape/representativeness.yaml`:
- Around line 58-62: Update the function description for
flux-notification-controller to state that it receives events from other
controllers and dispatches alerts to configured providers, replacing the
inaccurate reconciliation description while leaving the remaining profile fields
unchanged.
- Around line 63-67: Update the flux-source-controller entry in
representativeness.yaml to add public-egress assertions for github.com, ghcr.io,
pkg-containers.githubusercontent.com, and stefanprodan.github.io, matching the
assertion structure used by argocd-repo-server. Preserve its existing function,
executable, egress port, and opens_min settings.

In `@pkg`:
- Line 1: Update the pkg submodule pointer to a commit reachable from its
configured remote, replacing 085f6c3f2fdf032b88563337fef901207e45defa, or
publish that exact commit to the remote before merging. Verify CI can
successfully initialize and checkout the submodule.

In `@scripts/fast-tune.sh`:
- Around line 71-118: Update the Flux controller cases and the bobctl tune
command path in fast-tune.sh so each branch’s SERVICE and SERVICE_PORT values
are passed as the exec attack target to bobctl tune. Ensure all Flux controller
targets use their configured service name and port, and remove these assignments
only if the tune command cannot consume them.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5795ed8b-c2d5-4b78-8355-3660b40da829

📥 Commits

Reviewing files that changed from the base of the PR and between bdbcb93 and 6123c79.

📒 Files selected for processing (26)
  • .github/workflows/ci-bobctl-autotune.yaml
  • Makefile
  • example/flux-helm-controller-attacks.yaml
  • example/flux-helm-controller-functional-tests.yaml
  • example/flux-image-automation-controller-attacks.yaml
  • example/flux-image-automation-controller-functional-tests.yaml
  • example/flux-image-reflector-controller-attacks.yaml
  • example/flux-image-reflector-controller-functional-tests.yaml
  • example/flux-kustomize-controller-attacks.yaml
  • example/flux-kustomize-controller-functional-tests.yaml
  • example/flux-notification-controller-attacks.yaml
  • example/flux-notification-controller-functional-tests.yaml
  • example/flux-source-controller-attacks.yaml
  • example/flux-source-controller-functional-tests.yaml
  • example/flux-vulnerable.yaml
  • example/flux/drive-gitops-workload.sh
  • example/flux/sbobs/cp-flux-helm-controller.yaml
  • example/flux/sbobs/cp-flux-image-automation-controller.yaml
  • example/flux/sbobs/cp-flux-image-reflector-controller.yaml
  • example/flux/sbobs/cp-flux-kustomize-controller.yaml
  • example/flux/sbobs/cp-flux-notification-controller.yaml
  • example/flux/sbobs/cp-flux-source-controller.yaml
  • kubescape/collapse-config.yaml
  • kubescape/representativeness.yaml
  • pkg
  • scripts/fast-tune.sh

Comment on lines +56 to +58
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: OCIRepository
metadata: {name: podinfo-oci-artifact, namespace: $NS}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check which apiVersions are served for the Flux CRDs used in this file.
kubectl get crd ocirepositories.source.toolkit.fluxcd.io -o jsonpath='{.spec.versions[*].name}{"\n"}'
kubectl get crd imagerepositories.image.toolkit.fluxcd.io -o jsonpath='{.spec.versions[*].name}{"\n"}'
kubectl get crd imagepolicies.image.toolkit.fluxcd.io -o jsonpath='{.spec.versions[*].name}{"\n"}'

Repository: k8sstormcenter/bob

Length of output: 283


🏁 Script executed:

#!/bin/bash
set -eu

echo "Locate the example file and Flux references"
git ls-files | rg '(^|/)drive-gitops-workload\.sh$|flux|kap|README|requirements|go\.mod|helm|kustomize|setup|install' | sed -n '1,200p'

echo
echo "Relevant file excerpt"
if [ -f example/flux/drive-gitops-workload.sh ]; then
  wc -l example/flux/drive-gitops-workload.sh
  sed -n '1,240p' example/flux/drive-gitops-workload.sh | cat -n
fi

echo
echo "Search for Flux/CRD version docs or comments"
rg -n "Flux v2\.9\.3|fluxcd|2\.9\.3|OCIRepository|ImageRepository|ImagePolicy|v1beta2|v1beta1|storageVersion|serve|versions" . --glob '!vendor/**' --glob '!node_modules/**' --glob '!dist/**' | sed -n '1,240p'

Repository: k8sstormcenter/bob

Length of output: 38088


🌐 Web query:

Flux v2.9.3 CRD apiVersions OCIRepository ImageRepository ImagePolicy source.toolkit.fluxcd.io v1beta2 v1 image.toolkit.fluxcd.io v1beta2

💡 Result:

In Flux v2.9.3, the Custom Resource Definitions (CRDs) for the specified APIs utilize the following apiVersions: 1. source.toolkit.fluxcd.io/v1: This group includes OCIRepository, GitRepository, HelmRepository, HelmChart, and Bucket [1][2]. These core source components were promoted to v1 (GA) to provide a stable API [3][4]. 2. image.toolkit.fluxcd.io/v1beta2: This group includes ImageRepository, ImagePolicy, and ImageUpdateAutomation [5][2][6][7]. These components, managed by the image-reflector-controller and image-automation-controller, continue to use the v1beta2 API version [2]. To summarize, while the core source controller APIs have moved to v1, the image-related APIs remain on v1beta2 [2]. When working with these resources, ensure your manifests specify the correct apiVersion (e.g., source.toolkit.fluxcd.io/v1 for an OCIRepository and image.toolkit.fluxcd.io/v1beta2 for an ImagePolicy) to align with the Flux v2.9.3 distribution [5][8][2].

Citations:


🌐 Web query:

github.com fluxcd source-controller v2.9.3 cluster manifests OCIRepository source.toolkit.fluxcd.io/v1

💡 Result:

In Flux v2.9.3, the OCIRepository custom resource remains part of the source.toolkit.fluxcd.io/v1 API [1][2][3]. The OCIRepository resource is managed by the source-controller, which provides the GitOps Toolkit source management functionality [4][5]. As of the v2.9.3 release of Flux (published July 23, 2026), the cluster manifests for Flux include the source-controller and related toolkit components [6][7]. Specifically, the v2.9.3 release included an update to the OCI flux-manifests artifact to include the source-watcher [6][8][9]. The source-controller component associated with this Flux release is version 1.9.3 [10][11][4]. The OCIRepository resource at source.toolkit.fluxcd.io/v1 is used to define a source that produces an artifact from an OCI repository [1][2]. Key fields for an OCIRepository include [2][3]: - metadata: Standard Kubernetes metadata (name, namespace) [1][3]. - spec.url: The address of the OCI repository (e.g., oci:///) [1][2][3]. - spec.interval: The frequency at which the OCI repository is checked for updates [1][2][3]. - spec.ref: Optional field to specify the OCI reference (tag, semver, or digest) to watch [1][2][3]. - spec.secretRef: Optional field to reference a Secret for OCI repository authentication [2][3]. Documentation for the OCIRepository API, including all available spec fields, can be found in the official Flux documentation [1][3].

Citations:


Use the Flux v2.9.3 CRD versions.

For Flux v2.9.3, OCIRepository is served under source.toolkit.fluxcd.io/v1, while ImageRepository and ImagePolicy remain under image.toolkit.fluxcd.io/v1beta2. The source.toolkit.fluxcd.io/v1beta2 OCIRepository in line 56 may fail with no matches for kind; update it to v1.

🤖 Prompt for 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.

In `@example/flux/drive-gitops-workload.sh` around lines 56 - 58, Update the
OCIRepository manifest’s apiVersion to source.toolkit.fluxcd.io/v1, while
leaving the existing ImageRepository and ImagePolicy apiVersions unchanged.

Comment on lines +65 to +71
- direction: inbound
endpoint: :6443/metrics
headers:
Accept-Encoding: [gzip]
Host: ['*:6443']
internal: true
methods: [GET]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Compare Flux Service port wiring with the metrics endpoints recorded in the SBoBs.
set -euo pipefail

# Find Flux Service definitions and their port/targetPort values.
fd -t f -e yaml -e yml . example | xargs rg -n -C 6 'kind:\s*Service' -g '*flux*'

# Show the functional suite targets.
fd -t f 'flux-.*functional-tests\.yaml' example --exec rg -n 'target:|port:|scheme:' {}

# Show the metrics endpoints recorded in each SBoB.
fd -t f 'cp-flux-.*\.yaml' example --exec rg -n -B 2 -A 4 '/metrics' {}

Repository: k8sstormcenter/bob

Length of output: 42322


Align the Helm-controller metrics profile with port 8080.

The Flux SBoBs record inbound :6443/metrics for helm-controller, kustomize-controller, notification-controller, and source-controller, but the Flux manifest exposes each controller Service as port: 8080 / targetPort: http-prom. Functional probes use those Service ports, so the profile misses the endpoint being hit. If port 6443 is intentional here, add an 8080 metrics endpoint entry to avoid false positives.

🤖 Prompt for 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.

In `@example/flux/sbobs/cp-flux-helm-controller.yaml` around lines 65 - 71, Update
the inbound metrics profile entries for the Flux controllers, including
helm-controller and the corresponding kustomize-controller,
notification-controller, and source-controller entries, to use port 8080
matching their Service definitions; if retaining :6443, also add a separate
:8080/metrics entry so functional probes are covered.

Comment thread example/flux/sbobs/cp-flux-notification-controller.yaml Outdated
Comment on lines +62 to +68
- direction: outbound
endpoint: :9093/api/v2/alerts
headers:
Accept-Encoding: [gzip]
Host: ['alertmanager.honey.svc.cluster.local:9093']
internal: true
methods: [POST]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Confirm that the honey Alertmanager egress belongs in the shipped profile.

This profile pins an outbound rule to alertmanager.honey.svc.cluster.local:9093 and to a pod selector in the honey namespace. This target depends on the demo Alert resource in this repository. If a user applies these profiles without that Alert, the rule stays unused. If a user renames the namespace, the profile no longer matches.

Also applies to: 109-116

🤖 Prompt for 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.

In `@example/flux/sbobs/cp-flux-notification-controller.yaml` around lines 62 -
68, Confirm whether the honey Alertmanager egress rule in the shipped profile is
intended to be included; if it is demo-only, remove the outbound entries
targeting alertmanager.honey.svc.cluster.local:9093 and the honey namespace
selector, otherwise ensure the profile remains valid without requiring the
repository’s demo Alert or fixed namespace.

Comment thread kubescape/collapse-config.yaml Outdated
Comment on lines +16 to +18
- {prefix: /data, threshold: 1}
- {prefix: /tmp/kustomization-, threshold: 1}
- {prefix: /tmp/helmrelease-, threshold: 1}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

The new /tmp collapse prefixes do not narrow the produced profiles. The configuration adds /tmp/kustomization- and /tmp/helmrelease- with threshold 1, yet three profiles still record a full /tmp/* wildcard with create, write, and truncate flags. The shared root cause is a wildcard-open collapse that runs ahead of, or in place of, the prefix rules, so the prefixes have no effect.

  • kubescape/collapse-config.yaml#L16-L18: confirm that the prefix rules apply before the generic wildcard collapse, and regenerate the profiles after the fix.
  • example/flux/sbobs/cp-flux-kustomize-controller.yaml#L52-L53: replace /tmp/* with the /tmp/kustomization-* prefix form.
  • example/flux/sbobs/cp-flux-helm-controller.yaml#L52-L53: replace /tmp/* with the /tmp/helmrelease-* prefix form.
  • example/flux/sbobs/cp-flux-source-controller.yaml#L60-L61: narrow /tmp/* to the prefixes that the controller actually uses.
📍 Affects 4 files
  • kubescape/collapse-config.yaml#L16-L18 (this comment)
  • example/flux/sbobs/cp-flux-kustomize-controller.yaml#L52-L53
  • example/flux/sbobs/cp-flux-helm-controller.yaml#L52-L53
  • example/flux/sbobs/cp-flux-source-controller.yaml#L60-L61
🤖 Prompt for 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.

In `@kubescape/collapse-config.yaml` around lines 16 - 18, Ensure the prefix rules
in kubescape/collapse-config.yaml lines 16-18 are evaluated before the generic
wildcard-open collapse, then regenerate the profiles. Update
example/flux/sbobs/cp-flux-kustomize-controller.yaml lines 52-53 to use
/tmp/kustomization-*, example/flux/sbobs/cp-flux-helm-controller.yaml lines
52-53 to use /tmp/helmrelease-*, and
example/flux/sbobs/cp-flux-source-controller.yaml lines 60-61 to use only the
prefixes its controller actually uses.

Comment thread kubescape/representativeness.yaml Outdated
Comment on lines +58 to +62
flux-notification-controller:
function: reconcile notification-controller resources against the apiserver
execs_include: [/usr/local/bin/notification-controller]
egress_ports: [TCP-443]
opens_min: 8

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The function text does not describe the notification-controller role.

The notification-controller receives events from the other controllers and dispatches alerts to providers. It does not reconcile notification-controller resources against the apiserver. The profile confirms this: it records an inbound :80/ POST endpoint and outbound alerts to Alertmanager. Correct the description so the representativeness file stays a reliable reference.

♻️ Proposed change
 flux-notification-controller:
-  function: reconcile notification-controller resources against the apiserver
+  function: receive controller events and dispatch alerts to providers
   execs_include: [/usr/local/bin/notification-controller]
   egress_ports: [TCP-443]
   opens_min: 8
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
flux-notification-controller:
function: reconcile notification-controller resources against the apiserver
execs_include: [/usr/local/bin/notification-controller]
egress_ports: [TCP-443]
opens_min: 8
flux-notification-controller:
function: receive controller events and dispatch alerts to providers
execs_include: [/usr/local/bin/notification-controller]
egress_ports: [TCP-443]
opens_min: 8
🤖 Prompt for 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.

In `@kubescape/representativeness.yaml` around lines 58 - 62, Update the function
description for flux-notification-controller to state that it receives events
from other controllers and dispatches alerts to configured providers, replacing
the inaccurate reconciliation description while leaving the remaining profile
fields unchanged.

Comment thread kubescape/representativeness.yaml Outdated
Comment thread pkg Outdated
Comment thread scripts/fast-tune.sh
Comment on lines +71 to +118
flux-source-controller)
NS=flux-system
SUITE=example/flux-source-controller-attacks.yaml
FUNCTESTS=example/flux-source-controller-functional-tests.yaml
PROFILE_MATCH=source-controller-.*-manager
SERVICE=source-controller
SERVICE_PORT=80
;;
flux-kustomize-controller)
NS=flux-system
SUITE=example/flux-kustomize-controller-attacks.yaml
FUNCTESTS=example/flux-kustomize-controller-functional-tests.yaml
PROFILE_MATCH=kustomize-controller-.*-manager
SERVICE=kustomize-controller
SERVICE_PORT=8080
;;
flux-helm-controller)
NS=flux-system
SUITE=example/flux-helm-controller-attacks.yaml
FUNCTESTS=example/flux-helm-controller-functional-tests.yaml
PROFILE_MATCH=helm-controller-.*-manager
SERVICE=helm-controller
SERVICE_PORT=8080
;;
flux-notification-controller)
NS=flux-system
SUITE=example/flux-notification-controller-attacks.yaml
FUNCTESTS=example/flux-notification-controller-functional-tests.yaml
PROFILE_MATCH=notification-controller-.*-manager
SERVICE=notification-controller
SERVICE_PORT=80
;;
flux-image-reflector-controller)
NS=flux-system
SUITE=example/flux-image-reflector-controller-attacks.yaml
FUNCTESTS=example/flux-image-reflector-controller-functional-tests.yaml
PROFILE_MATCH=image-reflector-controller-.*-manager
SERVICE=image-reflector-controller
SERVICE_PORT=8080
;;
flux-image-automation-controller)
NS=flux-system
SUITE=example/flux-image-automation-controller-attacks.yaml
FUNCTESTS=example/flux-image-automation-controller-functional-tests.yaml
PROFILE_MATCH=image-automation-controller-.*-manager
SERVICE=image-automation-controller
SERVICE_PORT=8080
;;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check whether SERVICE / SERVICE_PORT are referenced anywhere else in fast-tune.sh.
rg -n 'SERVICE\b|SERVICE_PORT\b' scripts/fast-tune.sh

Repository: k8sstormcenter/bob

Length of output: 532


🏁 Script executed:

#!/bin/bash
# Inspect the relevant script structure and nearby target-resolution/attack execution code without running repository scripts.
set -euo pipefail

echo "## script line count"
wc -l scripts/fast-tune.sh

echo "## broad variable names/usages in script"
rg -n 'NS|SUITE|FUNCTESTS|PROFILE_MATCH|SERVICE|SERVICE_PORT|exec|attack|kubectl|get|pod|namespace|port' scripts/fast-tune.sh

echo "## case statement"
sed -n '1,150p' scripts/fast-tune.sh

Repository: k8sstormcenter/bob

Length of output: 11734


Wire the exec attack target for Flux controllers.

SERVICE and SERVICE_PORT are only assigned in the Flux controller branches and are never referenced elsewhere in scripts/fast-tune.sh. If these values are meant to enable the new ClusterIP Services for exec attacks, pass an equivalent target into the bobctl tune command path; otherwise remove them instead of leaving unused per-branch state.

🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 75-75: Brace expansions and globs are literal in assignments. Quote it or use an array.

(SC2125)


[warning] 83-83: Brace expansions and globs are literal in assignments. Quote it or use an array.

(SC2125)


[warning] 91-91: Brace expansions and globs are literal in assignments. Quote it or use an array.

(SC2125)


[warning] 99-99: Brace expansions and globs are literal in assignments. Quote it or use an array.

(SC2125)


[warning] 107-107: Brace expansions and globs are literal in assignments. Quote it or use an array.

(SC2125)


[warning] 115-115: Brace expansions and globs are literal in assignments. Quote it or use an array.

(SC2125)


[warning] 116-116: SERVICE appears unused. Verify use (or export if used externally).

(SC2034)


[warning] 117-117: SERVICE_PORT appears unused. Verify use (or export if used externally).

(SC2034)

🤖 Prompt for 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.

In `@scripts/fast-tune.sh` around lines 71 - 118, Update the Flux controller cases
and the bobctl tune command path in fast-tune.sh so each branch’s SERVICE and
SERVICE_PORT values are passed as the exec attack target to bobctl tune. Ensure
all Flux controller targets use their configured service name and port, and
remove these assignments only if the tune command cannot consume them.

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
example/flux/sbobs/cp-flux-image-automation-controller.yaml (1)

78-86: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Allow configured notification event traffic.

Both controllers configure --events-addr=http://notification-controller.flux-system.svc.cluster.local./. The profiles omit the required outbound POST :80/ endpoint and internal TCP/80 egress to app: notification-controller, so reconciliation events can produce runtime-policy alerts.

  • example/flux/sbobs/cp-flux-image-automation-controller.yaml#L78-L86: add outbound POST :80/ for notification-controller.flux-system.svc.cluster.local. and internal TCP/80 egress to app: notification-controller.
  • example/flux/sbobs/cp-flux-image-reflector-controller.yaml#L82-L90: add the same outbound endpoint and internal TCP/80 egress rule.
🤖 Prompt for 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.

In `@example/flux/sbobs/cp-flux-image-automation-controller.yaml` around lines 78
- 86, Add the configured notification event traffic to both profiles: in
example/flux/sbobs/cp-flux-image-automation-controller.yaml lines 78-86 and
example/flux/sbobs/cp-flux-image-reflector-controller.yaml lines 82-90, add the
outbound POST port 80 endpoint for
notification-controller.flux-system.svc.cluster.local. and an internal TCP/80
egress rule targeting app: notification-controller.
🤖 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.

Outside diff comments:
In `@example/flux/sbobs/cp-flux-image-automation-controller.yaml`:
- Around line 78-86: Add the configured notification event traffic to both
profiles: in example/flux/sbobs/cp-flux-image-automation-controller.yaml lines
78-86 and example/flux/sbobs/cp-flux-image-reflector-controller.yaml lines
82-90, add the outbound POST port 80 endpoint for
notification-controller.flux-system.svc.cluster.local. and an internal TCP/80
egress rule targeting app: notification-controller.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e14b27b8-1586-4082-83ab-275364b74927

📥 Commits

Reviewing files that changed from the base of the PR and between 6123c79 and c7d7d2f.

📒 Files selected for processing (6)
  • example/flux/sbobs/cp-flux-helm-controller.yaml
  • example/flux/sbobs/cp-flux-image-automation-controller.yaml
  • example/flux/sbobs/cp-flux-image-reflector-controller.yaml
  • example/flux/sbobs/cp-flux-kustomize-controller.yaml
  • example/flux/sbobs/cp-flux-notification-controller.yaml
  • example/flux/sbobs/cp-flux-source-controller.yaml

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
example/flux/sbobs/cp-flux-kustomize-controller.yaml (1)

79-85: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Align the metrics profiles with the Service contracts.

The profiles record metrics on :6443, but the functional suites use different Service targets. Replace these entries with the Service port values used by each functional test, then update the Host constraints to the resolved container endpoints.

  • example/flux/sbobs/cp-flux-kustomize-controller.yaml#L79-L85: use the Service target on port 8080.
  • example/flux/sbobs/cp-flux-source-controller.yaml#L114-L120: use the Service target on port 80.
🤖 Prompt for 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.

In `@example/flux/sbobs/cp-flux-kustomize-controller.yaml` around lines 79 - 85,
Update the metrics profile in
example/flux/sbobs/cp-flux-kustomize-controller.yaml lines 79-85 to target
Service port 8080 and constrain Host to the resolved container endpoint. Apply
the same alignment in example/flux/sbobs/cp-flux-source-controller.yaml lines
114-120, using Service port 80 and its resolved container endpoint.
🤖 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 `@example/flux/sbobs/cp-flux-kustomize-controller.yaml`:
- Line 56: Update the syscall allow-lists in
example/flux/sbobs/cp-flux-kustomize-controller.yaml:56-56 and
example/flux/sbobs/cp-flux-source-controller.yaml:70-70 to cover normal HTTP and
backend container runtime syscalls, or document the base/profile merge semantics
that supplies them. Apply the same resolution consistently to both
ContainerProfile specs so legitimate kustomize/source-controller activity is not
flagged.

---

Outside diff comments:
In `@example/flux/sbobs/cp-flux-kustomize-controller.yaml`:
- Around line 79-85: Update the metrics profile in
example/flux/sbobs/cp-flux-kustomize-controller.yaml lines 79-85 to target
Service port 8080 and constrain Host to the resolved container endpoint. Apply
the same alignment in example/flux/sbobs/cp-flux-source-controller.yaml lines
114-120, using Service port 80 and its resolved container endpoint.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d401e41d-faf5-4509-a22a-b3d6f8379736

📥 Commits

Reviewing files that changed from the base of the PR and between c7d7d2f and f997a2b.

📒 Files selected for processing (3)
  • example/flux/sbobs/cp-flux-helm-controller.yaml
  • example/flux/sbobs/cp-flux-kustomize-controller.yaml
  • example/flux/sbobs/cp-flux-source-controller.yaml

Comment thread example/flux/sbobs/cp-flux-kustomize-controller.yaml Outdated
kind: FunctionalTestSuite
metadata: {name: kustomize-controller-functional}
target: {service: kustomize-controller, namespace: flux-system, port: 8080, scheme: http}
tests:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

you gotta be joking,

internal: true
methods: [GET]
- direction: inbound
endpoint: :80/helmchart/helm-benchmark/helm-benchmark-podinfo-hr-4/podinfo-6.5.3.tgz

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We need to add helm syntax to these cps, such that this entry (which is the flux-loadtests) can be compiled out if sbob.validate=false is set

@coderabbitai coderabbitai 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.

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
example/flux/sbobs/cp-flux-image-automation-controller.yaml (1)

47-56: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

The two image controllers record no DNS egress. Both profiles list only the Kubernetes API server, while the helm, kustomize, notification, and source profiles in this PR each record a kube-dns UDP-53 peer. The shared root cause is an incomplete network capture for these two controllers.

  • example/flux/sbobs/cp-flux-image-automation-controller.yaml#L47-L56: add the kube-dns UDP-53 egress entry, and add the Git remote peer if the controller pushes commits during the benchmark.
  • example/flux/sbobs/cp-flux-image-reflector-controller.yaml#L49-L58: add the kube-dns UDP-53 egress entry and the container registry peers that the controller scans.
🤖 Prompt for 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.

In `@example/flux/sbobs/cp-flux-image-automation-controller.yaml` around lines 47
- 56, The network profiles for both image controllers are incomplete. In
example/flux/sbobs/cp-flux-image-automation-controller.yaml lines 47-56, add the
kube-dns UDP-53 egress entry and the Git remote peer if benchmark behavior
pushes commits; in example/flux/sbobs/cp-flux-image-reflector-controller.yaml
lines 49-58, add the kube-dns UDP-53 egress entry and the container registry
peers scanned by the controller.
example/flux/sbobs/cp-flux-helm-controller.yaml (1)

29-99: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

The collapsed Flux profiles retain run-specific endpoints. The collapse step produced no effective change for these controllers, so the shipped profiles pin benchmark indices, chart versions, Git commit SHAs, and blob digests. Any benchmark regeneration invalidates them and produces false positives.

  • example/flux/sbobs/cp-flux-helm-controller.yaml#L29-L99: collapse the helm-benchmark-podinfo-hr-N index and the podinfo-<version>.tgz segment into a wildcard chart path.
  • example/flux/sbobs/uncollapsed/cp-flux-helm-controller.yaml#L29-L99: keep this file as the raw capture, and confirm the collapsed twin no longer matches it byte for byte.
  • example/flux/sbobs/cp-flux-kustomize-controller.yaml#L39-L136: collapse the podinfo-ks-N Kustomization index, the sha256: digest, and the Git commit SHA in the gitrepository paths.
  • example/flux/sbobs/cp-flux-source-controller.yaml#L40-L235: collapse the same index, version, digest, and commit-SHA segments across the inbound artifact endpoints and the outbound registry manifest and blob endpoints.
🤖 Prompt for 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.

In `@example/flux/sbobs/cp-flux-helm-controller.yaml` around lines 29 - 99, Update
example/flux/sbobs/cp-flux-helm-controller.yaml lines 29-99 to wildcard the
helm-benchmark-podinfo-hr-N index and podinfo-<version>.tgz chart segments. Keep
example/flux/sbobs/uncollapsed/cp-flux-helm-controller.yaml lines 29-99
unchanged as the raw capture, and ensure the collapsed file no longer matches it
byte-for-byte. In example/flux/sbobs/cp-flux-kustomize-controller.yaml lines
39-136, wildcard podinfo-ks-N, sha256 digests, and Git commit SHA path segments.
Apply the same index, version, digest, and commit-SHA wildcarding to inbound
artifact and outbound registry manifest/blob endpoints in
example/flux/sbobs/cp-flux-source-controller.yaml lines 40-235.
example/flux/sbobs/cp-flux-source-controller.yaml (1)

253-315: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Nine external egress entries collapse into four distinct peers.

github.com appears twice, stefanprodan.github.io three times, ghcr.io twice, and pkg-containers.githubusercontent.com twice. Each pair carries identical dnsNames, ports, namespaceSelector, podSelector, and type, and differs only by identifier. The identifier is therefore derived from a field that the serialized entry no longer retains, most likely the resolved IP address dropped during collapse. Merge the duplicates, or retain the distinguishing field so the entries are meaningful.

🤖 Prompt for 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.

In `@example/flux/sbobs/cp-flux-source-controller.yaml` around lines 253 - 315,
Collapse the duplicate external egress entries in the peer list so each distinct
dnsNames/ports/selector/type combination appears once, or retain the omitted
distinguishing field that makes each identifier meaningful. Update the entries
for github.com, stefanprodan.github.io, ghcr.io, and
pkg-containers.githubusercontent.com, preserving valid identifiers and peer
semantics.
🤖 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 `@example/flux-helm-controller-functional-tests.yaml`:
- Around line 3-4: Update the paired SBoB profile for helm-controller so it
includes inbound endpoint entries for port 8080, specifically the /metrics and
/debug/pprof/* surface driven by the flux-helm-controller-functional target. Use
the existing sbobs profile symbols and ingress configuration as the source of
truth, and either add the missing :8080 endpoint records or explicitly document
that 8080 endpoint entries are intentionally not asserted.
- Around line 26-47: Enable pprof in the controller configuration before these
HTTP checks run, or remove the pprof test cases. Apply the same change to the
pprof test blocks in example/flux-helm-controller-functional-tests.yaml (lines
26-47) and example/flux-image-automation-controller-functional-tests.yaml (lines
26-47), ensuring the relevant controller args include --enable-pprof if the
checks are retained.

In `@example/flux-notification-controller-functional-tests.yaml`:
- Around line 6-12: Update the event-kustomization-reconciled baseline request
body so it is unique from the body used by event-duplicate-rate-limited,
including its timestamp or another event field, while preserving the expected
202 response. Ensure the baseline cannot consume the duplicate-event rate-limit
quota before the 429 assertion runs.

In `@example/flux-source-controller-functional-tests.yaml`:
- Around line 26-27: Update the artifact-traversal-blocked test request path to
percent-encode the traversal dot segments, ensuring the encoded ../ components
remain on the wire and the expected 404 verifies artifact-server traversal
rejection rather than a normalized /etc/passwd request.

In `@example/flux/benchmark/run-flux-benchmark.sh`:
- Around line 22-24: Update the required-command validation in
run-flux-benchmark.sh so the preflight loop also checks git and curl before any
benchmark work starts. Keep the existing command -v pattern used for kubectl,
timoni, and crane, and extend the same guard so fetch_upstream and
start_registry fail early with a clear missing-tool message.

In `@example/flux/sbobs/cp-flux-kustomize-controller.yaml`:
- Line 13: Update the path entry in the kustomize-controller profile from the
invalid root-level /.yaml location to the correct /tmp/...
temporary-working-directory prefix, using the raw learned profile to restore the
exact captured path and retain the intended collapse-threshold behavior.

In `@example/flux/sbobs/cp-flux-notification-controller.yaml`:
- Around line 97-136: Update the ingress entries from-source-controller,
from-kustomize-controller, from-helm-controller,
from-image-reflector-controller, and from-image-automation-controller to use the
notification-controller Service port: rename each port to TCP-80 and set its
port value to 80, while leaving the controller selectors and protocol unchanged.

In `@example/flux/sbobs/uncollapsed/cp-flux-source-controller.yaml`:
- Around line 36-49: Document in a README within the sbobs example tree that the
uncollapsed variant is evidence-only and must never be applied to a cluster,
while the collapsed variant is the deployment profile. Preserve the existing
profile files and avoid adding deployment references to uncollapsed; verify no
scripts, Makefile targets, or workflows wire it into the apply path.

In `@kubescape/default-rules.yaml`:
- Line 312: Update the loopback destination exclusions in rules R0011 and R0012
to also reject the IPv6 loopback address ::1, while preserving the existing 127.
prefix check for IPv4 loopback traffic.

In `@kubescape/values.yaml`:
- Around line 8-9: Update the node-agent image pullPolicy to IfNotPresent while
retaining its digest-pinned tag. Align the storage image tag with the same
digest pin, unless the floating sbob-rc5s tag is explicitly intentional.

In `@Makefile`:
- Line 121: Update the Makefile recipe containing the kubectl apply for
flux-collapse-config.yaml to ensure Kubescape is installed before applying the
CollapseConfiguration resource, or move this apply into the target that runs
after Kubescape installation. Preserve the existing rollout checks and
validation steps so they execute after the prerequisite succeeds.
- Around line 449-452: Update the kubescape-vendor target to invoke the existing
ks-runc-mount-values generation step before the Helm upgrade when KS_RUNC_MNT is
set, ensuring the /tmp/ks-runc-mount-values.yaml overlay exists and is freshly
generated before KS_RUNC_MNT_FLAGS is consumed.

In `@scripts/local-ci.sh`:
- Around line 174-175: Update scripts/local-ci.sh so required prerequisite
commands are allowed to fail fast instead of being masked by `|| true`: the
pre-learning inventory read in the `PRE_PROFILES` block must abort on
kubectl/API errors rather than being treated as empty, the Flux
restart/readiness checks must fail if any required deployment does not restart
or become ready, the load-driver execution path must stop on a non-zero exit
before tuning continues, and the `RAW_PROFILE` export path must fail if the
selected profile cannot be saved. Keep `|| true` only on non-essential
diagnostics, and preserve the existing control flow in the surrounding local-ci
steps and profile-handling logic.
- Around line 217-224: Update the current ReplicaSet lookup in the local-ci
script’s matching flow to use ReplicaSets owned by deploy/$APP_SERVICE instead
of reading ownerReferences from the first Pod. Resolve the active revision from
the ReplicaSet set, ensure the result is unique before assigning MATCH, and fail
the script if more than one candidate matches so an old rollout pod cannot pin
the wrong learned profile.

---

Outside diff comments:
In `@example/flux/sbobs/cp-flux-helm-controller.yaml`:
- Around line 29-99: Update example/flux/sbobs/cp-flux-helm-controller.yaml
lines 29-99 to wildcard the helm-benchmark-podinfo-hr-N index and
podinfo-<version>.tgz chart segments. Keep
example/flux/sbobs/uncollapsed/cp-flux-helm-controller.yaml lines 29-99
unchanged as the raw capture, and ensure the collapsed file no longer matches it
byte-for-byte. In example/flux/sbobs/cp-flux-kustomize-controller.yaml lines
39-136, wildcard podinfo-ks-N, sha256 digests, and Git commit SHA path segments.
Apply the same index, version, digest, and commit-SHA wildcarding to inbound
artifact and outbound registry manifest/blob endpoints in
example/flux/sbobs/cp-flux-source-controller.yaml lines 40-235.

In `@example/flux/sbobs/cp-flux-image-automation-controller.yaml`:
- Around line 47-56: The network profiles for both image controllers are
incomplete. In example/flux/sbobs/cp-flux-image-automation-controller.yaml lines
47-56, add the kube-dns UDP-53 egress entry and the Git remote peer if benchmark
behavior pushes commits; in
example/flux/sbobs/cp-flux-image-reflector-controller.yaml lines 49-58, add the
kube-dns UDP-53 egress entry and the container registry peers scanned by the
controller.

In `@example/flux/sbobs/cp-flux-source-controller.yaml`:
- Around line 253-315: Collapse the duplicate external egress entries in the
peer list so each distinct dnsNames/ports/selector/type combination appears
once, or retain the omitted distinguishing field that makes each identifier
meaningful. Update the entries for github.com, stefanprodan.github.io, ghcr.io,
and pkg-containers.githubusercontent.com, preserving valid identifiers and peer
semantics.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d6ec11d2-aa23-4a23-8312-335c300a16c1

📥 Commits

Reviewing files that changed from the base of the PR and between f997a2b and 692d218.

📒 Files selected for processing (36)
  • Makefile
  • example/flux-helm-controller-functional-tests.yaml
  • example/flux-image-automation-controller-functional-tests.yaml
  • example/flux-image-reflector-controller-functional-tests.yaml
  • example/flux-kustomize-controller-functional-tests.yaml
  • example/flux-notification-controller-functional-tests.yaml
  • example/flux-source-controller-functional-tests.yaml
  • example/flux-vulnerable.yaml
  • example/flux/benchmark/flux-benchmark.cue
  • example/flux/benchmark/registry.yaml
  • example/flux/benchmark/run-flux-benchmark.sh
  • example/flux/flux-collapse-config.yaml
  • example/flux/sbobs/cp-flux-helm-controller.yaml
  • example/flux/sbobs/cp-flux-image-automation-controller.yaml
  • example/flux/sbobs/cp-flux-image-reflector-controller.yaml
  • example/flux/sbobs/cp-flux-kustomize-controller.yaml
  • example/flux/sbobs/cp-flux-notification-controller.yaml
  • example/flux/sbobs/cp-flux-source-controller.yaml
  • example/flux/sbobs/uncollapsed/cp-flux-helm-controller.yaml
  • example/flux/sbobs/uncollapsed/cp-flux-image-automation-controller.yaml
  • example/flux/sbobs/uncollapsed/cp-flux-image-reflector-controller.yaml
  • example/flux/sbobs/uncollapsed/cp-flux-kustomize-controller.yaml
  • example/flux/sbobs/uncollapsed/cp-flux-notification-controller.yaml
  • example/flux/sbobs/uncollapsed/cp-flux-source-controller.yaml
  • kubescape/collapse-config.yaml
  • kubescape/default-rule-binding.yaml
  • kubescape/default-rules.yaml
  • kubescape/post-render.sh
  • kubescape/values.yaml
  • pkg
  • scripts/check-no-overbroad.py
  • scripts/gen-runc-mount-values.py
  • scripts/local-ci.sh
  • scripts/restore-overcollapsed.py
  • scripts/sbob-from-learned.py
  • scripts/validate-suites.sh

Comment thread example/flux-helm-controller-functional-tests.yaml
Comment thread example/flux-helm-controller-functional-tests.yaml
Comment on lines +6 to +12
- name: event-kustomization-reconciled
http:
method: POST
path: /
expectedStatus: 202
body: '{"involvedObject":{"kind":"Kustomization","namespace":"flux-system","name":"podinfo-kustomize","apiVersion":"kustomize.toolkit.fluxcd.io/v1"},"severity":"info","timestamp":"2026-01-01T00:00:00Z","message":"Reconciliation finished in 1.2s, next run in 1m0s","reason":"ReconciliationSucceeded","reportingController":"kustomize-controller"}'
headers: &id001 {Content-Type: application/json}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Determine test execution order/retry semantics and any window handling around the notification suite.
set -euo pipefail

rg -n -C 5 'functional-tests|FUNCTESTS|retry|rate.?limit' --glob 'scripts/*.sh' --glob 'example/flux/benchmark/*.sh'

Repository: k8sstormcenter/bob

Length of output: 156


🏁 Script executed:

#!/bin/bash
set -euo eou pipefail

echo "Repository files matching notification/yaml/sh:"
git ls-files | rg -n '(^|/)example/flux-notification-controller-functional-tests\.yaml$|functional-tests|benchmark|scripts/.*\.sh$|notify|notification' || true

echo
echo "Candidate yaml header:"
if [ -f example/flux-notification-controller-functional-tests.yaml ]; then
  sed -n '1,90p' example/flux-notification-controller-functional-tests.yaml | cat -n
fi

echo
echo "Search all tracked files for relevant keywords:"
rg -n -C 4 'event-duplicate-rate-limited|event-kustomization-reconciled|flux-notification-controller-functional-tests|FUNCTESTS|functional-tests|kubectl-needle|needle|rate.?limit|reconciled|duplicate' . || true

Repository: k8sstormcenter/bob

Length of output: 200


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Repository files matching notification/yaml/sh:"
git ls-files | rg -n '(^|/)example/flux-notification-controller-functional-tests\.yaml$|functional-tests|benchmark|scripts/.*\.sh$|notify|notification' || true

echo
echo "Candidate yaml header:"
if [ -f example/flux-notification-controller-functional-tests.yaml ]; then
  sed -n '1,90p' example/flux-notification-controller-functional-tests.yaml | cat -n
fi

echo
echo "Search all tracked files for relevant keywords:"
rg -n -C 4 'event-duplicate-rate-limited|event-kustomization-reconciled|flux-notification-controller-functional-tests|FUNCTESTS|functional-tests|kubectl-needle|needle|rate.?limit|reconciled|duplicate' . || true

Repository: k8sstormcenter/bob

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Flux benchmark files:"
fd -a 'flux-benchmark|run-flux-benchmark' . | sed 's#^\./##'

for f in example/flux/benchmark/flux-benchmark.cue example/flux/benchmark/run-flux-benchmark.sh; do
  if [ -f "$f" ]; then
    echo
    echo "--- $f ($(wc -l < "$f") lines) ---"
    sed -n '1,260p' "$f" | cat -n
  fi
done

echo
echo "Relevant script snippets for functional test calls and max-rounds:"
sed -n '220,270p' scripts/local-ci.sh | cat -n
sed -n '340,375p' scripts/local-ci.sh | cat -n
sed -n '240,264p' scripts/fast-tune.sh | cat -n

echo
echo "Occurrences of max-rounds / retry semantics:"
rg -n -C 4 '--max-rounds|max-rounds|retry|FunctionalTestSuite|bobctl test|bobctl learn' --glob '!vendor/**' . || true

Repository: k8sstormcenter/bob

Length of output: 11237


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Go module references:"
rg -n 'github.com/kubescape/kubescape|kubescape|bobctl|bobctl.k8sstormcenter' go.mod go.sum scripts/example --glob '!go.work.sum' 2>/dev/null || true

echo
echo "Repository root files likely defining FunctionalTestSuite / bobctl test:"
git ls-files | rg '(^|/)(cmd|pkg|internal|go|Makefile|.*\.go)$' | head -200 || true

echo
echo "Parse FunctionalTestSuite type and bobctl test/learn command definitions:"
rg -n 'type FunctionalTestSuite|FunctionalTestSuite|Apply |bobctl test|bobctl learn|rate.?limit|expectedStatus|expectStatus' --glob '*.go' .

Repository: k8sstormcenter/bob

Length of output: 628


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "pkg top-level:"
git ls-files pkg | sed 's#^\./##' | sort | head -300

echo
echo "Detailed definitions in pkg:"
rg -n -C 6 'FunctionalTestSuite|FunctionalTests|bobctl test|bobctl learn|learn|test' pkg 2>/dev/null || true

echo
echo "Files below pkg that mention FunctionalTestSuite/API version:"
rg -n -C 4 'bobctl\.k8sstormcenter\.io/v1alpha1|FunctionalTestSuite|expectedStatus|reconciled|rate|retry|max-rounds' pkg 2>/dev/null || true

Repository: k8sstormcenter/bob

Length of output: 268


🌐 Web query:

flux notification controller duplicate event rate limit POST / HTTP 429

💡 Result:

The Flux notification-controller returns an HTTP 429 (Too Many Requests) status code when it receives duplicate events within a configured rate-limit interval [1][2]. This mechanism is designed to prevent spamming external notification systems (such as Slack or Sentry) with repeated alerts for the same issue [1][3]. Key details regarding this behavior include: Rate Limiting Criteria: The controller identifies duplicate events based on the involved object's name, namespace, kind, the specific message, and associated metadata [1][3]. Default Interval: The rate limit interval defaults to 5 minutes [1][4]. During this window, subsequent identical events sent to the notification-controller's API endpoint will receive a 429 response [1][2]. Configuration: You can modify this interval by adjusting the --rate-limit-interval controller flag [1][4]. This is typically done by patching the deployment arguments for the notification-controller in your Flux configuration (e.g., via a Kustomization patch in flux-system) [5]. Monitoring: The event server exposes metrics that allow you to track rate-limited events. You can use the following PromQL query to identify the rate at which requests are receiving 429 status codes [1][3]: rate(gotk_event_http_request_duration_seconds_count{code="429"}[30s]) Distinction from Webhook Receivers: It is important to distinguish the notification-controller's internal event API from the webhook receiver component. If you are receiving 429 errors when sending external webhooks, ensure you are targeting the appropriate service (e.g., the webhook-receiver service rather than the notification-controller service) and verify that no external load balancers or ingress controllers in front of your cluster are imposing their own rate limits [6].

Citations:


Avoid rate-limiting the baseline 202 request before checking 429.

event-duplicate-rate-limited reuses the exact 202 body, so both tests share Flux’s duplicate-event rate limit. If bobctl repeats runs or retries within that window, event-kustomization-reconciled can also return 429, and the suite fails before the 429 expectation is reached. Use unique bodies/timestamps for the baseline request or avoid re-running the whole suite inside the same rate-limit window.

🤖 Prompt for 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.

In `@example/flux-notification-controller-functional-tests.yaml` around lines 6 -
12, Update the event-kustomization-reconciled baseline request body so it is
unique from the body used by event-duplicate-rate-limited, including its
timestamp or another event field, while preserving the expected 202 response.
Ensure the baseline cannot consume the duplicate-event rate-limit quota before
the 429 assertion runs.

Comment on lines +26 to +27
- name: artifact-traversal-blocked
http: {method: GET, path: /gitrepository/../../etc/passwd, expectedStatus: 404}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The traversal test may not reach the server as written.

Most HTTP clients normalize .. segments in the request path before transmission. The server then receives /etc/passwd, which returns 404 for an unrelated reason. The assertion passes without proving that the artifact server rejects traversal. Use percent-encoded segments so the dot segments stay on the wire.

♻️ Proposed change
 - name: artifact-traversal-blocked
-  http: {method: GET, path: /gitrepository/../../etc/passwd, expectedStatus: 404}
+  http: {method: GET, path: '/gitrepository/%2e%2e/%2e%2e/etc/passwd', expectedStatus: 404}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: artifact-traversal-blocked
http: {method: GET, path: /gitrepository/../../etc/passwd, expectedStatus: 404}
- name: artifact-traversal-blocked
http: {method: GET, path: '/gitrepository/%2e%2e/%2e%2e/etc/passwd', expectedStatus: 404}
🤖 Prompt for 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.

In `@example/flux-source-controller-functional-tests.yaml` around lines 26 - 27,
Update the artifact-traversal-blocked test request path to percent-encode the
traversal dot segments, ensuring the encoded ../ components remain on the wire
and the expected 404 verifies artifact-server traversal rejection rather than a
normalized /etc/passwd request.

- {path: /sbin/tini, args: [/sbin/tini, ⋯⋯]}
- {path: /usr/local/bin/kustomize-controller, args: [/usr/local/bin/kustomize-controller, ⋯⋯]}
opens:
- {path: /.yaml, flags: [O_CLOEXEC, O_RDONLY]}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

/.yaml looks like an over-collapse residue.

kustomize-controller reads manifests from its temporary working directory, so the captured path should be under /tmp/⋯. A root-level /.yaml entry matches no real file. It appears when a collapse step strips the leading directory segments and leaves the extension attached to the root. This PR adds root-directory collapse thresholds for exactly this class of defect. Confirm the entry against the raw learned profile and restore the correct prefix.

🤖 Prompt for 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.

In `@example/flux/sbobs/cp-flux-kustomize-controller.yaml` at line 13, Update the
path entry in the kustomize-controller profile from the invalid root-level
/.yaml location to the correct /tmp/... temporary-working-directory prefix,
using the raw learned profile to restore the exact captured path and retain the
intended collapse-threshold behavior.

Comment thread kubescape/values.yaml Outdated
Comment on lines +8 to +9
tag: sbob-rc5s-celnet@sha256:dc4e66680df35dfb4d747544358b7fca906434a78f40ad3b573855935788b6f2
pullPolicy: Always

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check how the chart builds the node-agent image reference and whether a digest field exists.
set -euo pipefail

echo "=== image reference construction in chart templates ==="
rg -n -C6 'image:.*repository|\.Values\.nodeAgent\.image|image\.tag|image\.digest' --glob '*.tpl' --glob '*.yaml' || true

echo "=== other pullPolicy settings for comparison ==="
rg -n -C2 'pullPolicy' kubescape/ || true

echo "=== confirm the pinned digest resolves in the registry ==="
curl -sSI "https://ghcr.io/v2/k8sstormcenter/node-agent/manifests/sha256:dc4e66680df35dfb4d747544358b7fca906434a78f40ad3b573855935788b6f2" \
  -H "Accept: application/vnd.oci.image.index.v1+json" | head -20 || echo "anonymous manifest HEAD not permitted"

Repository: k8sstormcenter/bob

Length of output: 212


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== repository files under kubescape ==="
git ls-files | rg '(^|/)kubescape(/|$)' | head -200

echo "=== kubescape files summary ==="
find kubescape -maxdepth 4 -type f 2>/dev/null | sort | sed -n '1,200p'

echo "=== search values, templates, chart metadata ==="
rg -n --hidden --glob '!*.lock' -C4 'pullPolicy|nodeAgent|node-agent|repository:\s*$|tag:\s*$|digest:' kubescape - || true

echo "=== helm chart files and chart.yaml ==="
find kubescape -maxdepth 3 \( -name '*.yaml' -o -name '*.yml' -o -name 'Chart.yaml' -o -name 'values.yaml' \) -print | sort | while read -r f; do
  echo "--- $f ($(wc -l < "$f") lines) ---"
  sed -n '1,220p' "$f"
done

Repository: k8sstormcenter/bob

Length of output: 7606


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== post-render.sh ==="
sed -n '1,160p' kubescape/post-render.sh

echo "=== values.yaml image and nodeAgent sections ==="
sed -n '1,160p' kubescape/values.yaml

echo "=== inspect referenced node-agent manifest HEAD ==="
docker manifest inspect "ghcr.io/k8sstormcenter/node-agent@sha256:dc4e66680df35dfb4d747544358b7fca906434a78f40ad3b573855935788b6f2" \
  2>/tmp/kubescape_node_agent_manifest_err || {
  cat /tmp/kub.escape_node_agent_manifest_err
  echo "docker manifest inspect failed"
}

echo "=== inspect referenced node-agent tag reference ==="
docker manifest inspect "ghcr.io/k8sstormcenter/node-agent:sbob-rc5s-celnet@sha256:dc4e66680df35dfb4d747544358b7fca906434a78f40ad3b573855935788b6f2" \
  2>/tmp/kubescape_node_agent_tag_err || {
  cat /tmp/kubescape_node_agent_tag_err
  echo "docker manifest inspect failed"
}

echo "=== compare digest from tag@digest and digest-only ==="
python3 - <<'PY'
from urllib.request import urlopen
base = "https://ghcr.io/v2/k8sstormcenter/node-agent/manifests/sha256:dc4e66680df35dfb4d747544358b7fca906434a78f40ad3b573855935788b6f2"
for ref in ["sbob-rc5s-celnet@sha256:dc4e66680df35dfb4d747544358b7fca906434a78f40ad3b573855935788b6f2",
            "sha256:dc4e66680df35dfb4d747544358b7fca906434a78f40ad3b573855935788b6f2"]:
    url = "https://ghcr.io/v2/k8sstormcenter/node-agent/manifests/" + ref
    req = urlopen(url)
    print(ref, req.getheader("Docker-Content-Digest"), req.getcode())
PY

Repository: k8sstormcenter/bob

Length of output: 3627


🌐 Web query:

kubescape-operator chart nodeAgent image tag pullPolicy values

💡 Result:

In the Kubescape Operator Helm chart, the configuration for the nodeAgent image is managed under the nodeAgent.image object in the values.yaml file [1]. The specific values you requested are structured as follows: - Repository: nodeAgent.image.repository (default: quay.io/kubescape/node-agent) [1] - Tag: nodeAgent.image.tag (e.g., v0.3.129 in recent chart versions) [1][2] - Pull Policy: nodeAgent.image.pullPolicy (default: IfNotPresent) [1] You can override these values during installation or upgrade using the --set flag or by providing a custom values file. For example: helm upgrade --install kubescape kubescape/kubescape-operator -n kubescape --set nodeAgent.image.tag=v0.3.129 --set nodeAgent.image.pullPolicy=Always Note that for clusters with heterogeneous node pools, if you are using the nodeAgent.multipleDaemonSets feature, you should define your configurations within the nodeAgent.multipleDaemonSets.configurations array in your values.yaml [3][4]. While the top-level image configuration applies to the standard deployment, ensure your overrides align with the specific requirements of your deployment model [1][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

tmp=$(mktemp -d)
cd "$tmp"

echo "=== fetch remote chart values and node-agent image template references ==="
curl -fsS "https://raw.githubusercontent.com/kubescape/helm-charts/main/charts/kubescape-operator/1.40.3/values.yaml" > values_remote.yaml || {
  curl -fsS "https://raw.githubusercontent.com/kubescape/helm-charts/main/charts/kubescape-operator/main/values.yaml" > values_remote.yaml || {
    echo "Could not fetch remote values.yaml"
    exit 0
  }
}

sed -n '1,160p' values_remote.yaml | rg -n -C8 'nodeAgent:|image:|pullPolicy:|repository:|tag:' || true

echo "=== fetch chart templates that render nodeAgent image ==="
git clone --depth 1 --branch 1.40.3 https://github.com/kubescape/helm-charts kubescape-operator-1.40.3 \
  2>/tmp/kubescape_clone_err || {
  cat /tmp/kubescape_clone_err
  git clone --depth 1 --branch main https://github.com/kubescape/helm-charts kubescape-operator-main \
    2>/tmp/kubescape_clone_err || {
    cat /tmp/kubescape_clone_err
    exit 0
  }
  set -x
  chart_dir=kubescape-operator-main/charts/kubescape-operator
  set +x
}
if [ -d kubescape-operator-1.40.3 ]; then
  chart_dir=kubescape-operator-1.40.3/charts/kubescape-operator
else
  chart_dir=kubescape-operator-main/charts/kubescape-operator
fi

rg -n -C8 'nodeAgent|image\.repository|\.Values\.nodeAgent\.image\.repository|image\.tag|\.Values\.nodeAgent\.image\.tag|\.Values\.nodeAgent\.image\.pullPolicy|pullPolicy:' "$chart_dir" || true

echo "=== inspect likely templates ==="
find "$chart_dir" -type f \( -name '*.yaml' -o -name '*.tpl' -o -name '*.go' \) | sort | while read -r f; do
  if rg -q 'nodeAgent|image\.repository|image\.pullPolicy' "$f"; then
    echo "--- $f ---"
    sed -n '1,220p' "$f"
  fi
done

echo "=== OCI tag@digest and digest-only manifest HEADs ==="
for ref in "sbob-rc5s-celnet@sha256:dc4e66680df35dfb4d747544358b7fca906434a78f40ad3b573855935788b6f2" \
           "sha256:dc4e66680df35dfb4d747544358b7fca906434a78f40ad3b573855935788b6f2"; do
  echo "--- $ref ---"
  curl -sSI "https://ghcr.io/v2/k8sstormcenter/node-agent/manifests/$ref" \
    -H "Accept: application/vnd.oci.image.manifest.v1+json,application/vnd.oci.image.index.v1+json" | sed -n '1,12p'
done

Repository: k8sstormcenter/bob

Length of output: 364


🌐 Web query:

github kubescape helm-charts kubescape-operator nodeAgent image repository tag pullPolicy

💡 Result:

In the Kubescape Operator Helm chart, the configuration for the nodeAgent image is managed within the values.yaml file under the nodeAgent section [1]. The default settings for the nodeAgent image are as follows: repository: quay.io/kubescape/node-agent [1] tag: v0.3.129 (as of recent releases, e.g., v1.40.3) [2] pullPolicy: IfNotPresent [1] These values define the container image used by the node-agent DaemonSet [3]. You can override these defaults during installation or upgrade using the --set flag or by providing a custom values.yaml file. For example: helm upgrade --install kubescape kubescape/kubescape-operator -n kubescape --set nodeAgent.image.repository=my-repo/node-agent --set nodeAgent.image.tag=v0.3.129 --set nodeAgent.image.pullPolicy=Always Additionally, if you have a cluster with heterogeneous node pools, the chart supports deploying multiple node-agent DaemonSets with different configurations (including resources and node selectors) by enabling the nodeAgent.multipleDaemonSets feature [4][5][6]. Note that some capabilities of the node-agent may be implicitly disabled depending on other chart settings, such as whether the synchronizer is enabled or if an account is configured [7].

Citations:


🌐 Web query:

github kubescape helm-charts templates node-agent image.nodeAgent.image

💡 Result:

In the Kubescape Operator Helm chart, the node-agent image is configured via the values.yaml file under the nodeAgent.image structure [1]. Specifically, you can define the repository, tag, and pull policy as follows [1][2]: nodeAgent: image: repository: quay.io/kubescape/node-agent tag: v0.3.119 # Example version; update to your desired release pullPolicy: IfNotPresent Within the Helm templates, this configuration is typically referenced in the DaemonSet definitions (such as daemonset.yaml or the shared template definitions in _node-agent.tpl) using the following Go template syntax [2]: image: "{{.Values.nodeAgent.image.repository }}:{{.Values.nodeAgent.image.tag }}" imagePullPolicy: {{.Values.nodeAgent.image.pullPolicy }} As of August 2026, the configuration is modularized, and these values are the primary way to override the default image for the node-agent component [1][2]. You can apply these changes during installation or upgrade using the --set flag, for example: helm upgrade --install kubescape kubescape/kubescape-operator -n kubescape --set nodeAgent.image.tag=v0.3.119 This approach ensures the node-agent component uses the specified container image across your cluster [3][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== try GitHub raw API for node-agent template/value variants ==="
urls=(
  "https://raw.githubusercontent.com/kubescape/helm-charts/main/charts/kubescape-operator/templates/node-agent.yaml"
  "https://raw.githubusercontent.com/kubescape/helm-charts/main/charts/kubescape-operator/templates/daemonset.yaml"
  "https://raw.githubusercontent.com/kubescape/helm-charts/main/charts/kubescape-operator/templates/_node-agent.tpl"
  "https://raw.githubusercontent.com/kubescape/helm-charts/main/charts/kubescape-operator/values.yaml"
)
for url in "${urls[@]}"; do
  echo "--- $url ---"
  (curl -fsSL "$url" | sed -n '1,180p' | rg -n -C6 'image:|nodeAgent|repository|tag|pullPolicy' || true) | sed -n '1,200p'
done

echo "=== direct tag-only registry manifest probe ==="
curl -sSI "https://ghcr.io/v2/k8sstormcenter/node-agent/manifests/sbob-rc5s-celnet" \
  -H "Accept: application/vnd.oci.image.index.v1+json,application/vnd.oci.image.manifest.v1+json" | rg -n 'HTTP|^content-type|^Docker-Content-Digest' || true

echo "=== manifest inspect digest-only with docker if present ==="
if command -v docker >/dev/null 2>&1; then
  docker manifest inspect "ghcr.io/k8sstormcenter/node-agent@sha256:dc4e66680df35dfb4d747544358b7fca906434a78f40ad3b573855935788b6f2" 2>&1 | sed -n '1,80p'
else
  echo "docker not present"
fi

Repository: k8sstormcenter/bob

Length of output: 1829


🌐 Web query:

github kubescape helm-charts charts kubescape-operator kustomize node-agent

💡 Result:

The official position of the Kubescape project is that they only support installing the kubescape-operator Helm chart using Helm or ArgoCD [1][2][3]. While Kustomize 5.0+ includes native support for rendering Helm charts via the --enable-helm flag [4], the Kubescape maintainers explicitly warn that using alternative installation methods, such as Kustomize, Helmfile, or custom scripts, may lead to unexpected behavior and issues [1][2][3]. They state that they cannot guarantee compatibility or provide support for deployments installed using these methods [1][2][3]. The kubescape-operator chart includes the node-agent component [2][5], which is responsible for tasks such as producing SBOMs, creating ApplicationProfile and NetworkNeighborhood objects using eBPF, and monitoring container activity [1][2]. Configuration for the node-agent (such as global.overrideRuntimePath or various exporter settings) is managed via standard Helm values.yaml parameters [1][2][6]. If you choose to use Kustomize to manage the Helm chart despite the lack of official support, you would typically use the helmCharts field within your kustomization.yaml and must run the build command with the --enable-helm flag (e.g., kustomize build . --enable-helm | kubectl apply -f -) [4]. Alternatively, you can use helm template to render the manifests manually for inclusion in a Kustomize workflow [4]. However, be aware that you will be outside of the supported installation paths provided by the Kubescape team [1][3].

Citations:


Reconsider pullPolicy: Always for the digest-pinned node-agent image, and keep the storage image pin aligned.

pullPolicy: Always forces a registry check on each pod start; the digest already guarantees which image runs, so IfNotPresent reduces registry dependency while keeping idempotency. Also, the tag field renders as repository:tag, so the value sbob-rc5s-celnet@sha256:... is valid here. The storage image left on the floating sbob-rc5s tag is another potential drift point unless that is intentional.

🤖 Prompt for 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.

In `@kubescape/values.yaml` around lines 8 - 9, Update the node-agent image
pullPolicy to IfNotPresent while retaining its digest-pinned tag. Align the
storage image tag with the same digest pin, unless the floating sbob-rc5s tag is
explicitly intentional.

Comment thread Makefile
--export > /tmp/flux-install.yaml
kubectl apply -f /tmp/flux-install.yaml
kubectl apply -f example/flux-vulnerable.yaml
kubectl apply -f example/flux/flux-collapse-config.yaml

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Declare the Kubescape CRD prerequisite.

On a cluster without Kubescape, this apply fails because the CollapseConfiguration CRD does not exist. The recipe then stops before its rollout checks and validation run. Install Kubescape first, or move this apply to a target that runs after Kubescape installation.

🤖 Prompt for 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.

In `@Makefile` at line 121, Update the Makefile recipe containing the kubectl
apply for flux-collapse-config.yaml to ensure Kubescape is installed before
applying the CollapseConfiguration resource, or move this apply into the target
that runs after Kubescape installation. Preserve the existing rollout checks and
validation steps so they execute after the prerequisite succeeds.

Comment thread Makefile
Comment on lines 449 to +452
kubescape-vendor:
-$(HELM) repo add kubescape https://kubescape.github.io/helm-charts/
-$(HELM) repo update
$(HELM) upgrade --install kubescape kubescape/kubescape-operator --version $(KUBESCAPE_CHART_VER) -n honey --create-namespace --values kubescape/deprecated/values_vendor.yaml $(KS_RUNC_FLAGS) $(KS_LEARN_FLAGS) $(KS_POST_RENDER_FLAGS)
$(HELM) upgrade --install kubescape kubescape/kubescape-operator --version $(KUBESCAPE_CHART_VER) -n honey --create-namespace --values kubescape/deprecated/values_vendor.yaml $(KS_RUNC_FLAGS) $(KS_RUNC_MNT_FLAGS) $(KS_LEARN_FLAGS) $(KS_POST_RENDER_FLAGS)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Generate the runtime-mount overlay before Helm consumes it.

When KS_RUNC_MNT is set, KS_RUNC_MNT_FLAGS adds -f /tmp/ks-runc-mount-values.yaml. Unlike kubescape, this target does not run ks-runc-mount-values. Helm then fails on a missing file, or consumes a stale overlay from another invocation.

Proposed fix
-kubescape-vendor:
+kubescape-vendor: ks-runc-mount-values
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
kubescape-vendor:
-$(HELM) repo add kubescape https://kubescape.github.io/helm-charts/
-$(HELM) repo update
$(HELM) upgrade --install kubescape kubescape/kubescape-operator --version $(KUBESCAPE_CHART_VER) -n honey --create-namespace --values kubescape/deprecated/values_vendor.yaml $(KS_RUNC_FLAGS) $(KS_LEARN_FLAGS) $(KS_POST_RENDER_FLAGS)
$(HELM) upgrade --install kubescape kubescape/kubescape-operator --version $(KUBESCAPE_CHART_VER) -n honey --create-namespace --values kubescape/deprecated/values_vendor.yaml $(KS_RUNC_FLAGS) $(KS_RUNC_MNT_FLAGS) $(KS_LEARN_FLAGS) $(KS_POST_RENDER_FLAGS)
kubescape-vendor: ks-runc-mount-values
-$(HELM) repo add kubescape https://kubescape.github.io/helm-charts/
-$(HELM) repo update
$(HELM) upgrade --install kubescape kubescape/kubescape-operator --version $(KUBESCAPE_CHART_VER) -n honey --create-namespace --values kubescape/deprecated/values_vendor.yaml $(KS_RUNC_FLAGS) $(KS_RUNC_MNT_FLAGS) $(KS_LEARN_FLAGS) $(KS_POST_RENDER_FLAGS)
🧰 Tools
🪛 checkmake (0.3.2)

[warning] 449-449: Target body for "kubescape-vendor" exceeds allowed length of 5 lines (6).

(maxbodylength)

🤖 Prompt for 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.

In `@Makefile` around lines 449 - 452, Update the kubescape-vendor target to
invoke the existing ks-runc-mount-values generation step before the Helm upgrade
when KS_RUNC_MNT is set, ensuring the /tmp/ks-runc-mount-values.yaml overlay
exists and is freshly generated before KS_RUNC_MNT_FLAGS is consumed.

Comment thread scripts/local-ci.sh
Comment on lines +174 to +175
PRE_PROFILES=$(kubectl get containerprofiles -n "$APP_NS" \
-o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null || true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fail when a required learning prerequisite fails.

These commands use || true even though each result is required to produce a valid learned profile. The script can continue and score stale or incomplete data.

  • scripts/local-ci.sh#L174-L175: fail if the pre-learning profile inventory cannot be read. Do not treat an API error as an empty inventory.
  • scripts/local-ci.sh#L187-L191: fail if any required Flux Deployment cannot restart or become ready.
  • scripts/local-ci.sh#L253-L259: fail if the load driver exits non-zero. Do not tune a profile after an incomplete benchmark.
  • scripts/local-ci.sh#L298-L306: fail if the selected profile cannot be exported to RAW_PROFILE. Recovery cannot restore data that was not saved.

Keep || true only for optional diagnostics.

📍 Affects 1 file
  • scripts/local-ci.sh#L174-L175 (this comment)
  • scripts/local-ci.sh#L187-L191
  • scripts/local-ci.sh#L253-L259
  • scripts/local-ci.sh#L298-L306
🤖 Prompt for 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.

In `@scripts/local-ci.sh` around lines 174 - 175, Update scripts/local-ci.sh so
required prerequisite commands are allowed to fail fast instead of being masked
by `|| true`: the pre-learning inventory read in the `PRE_PROFILES` block must
abort on kubectl/API errors rather than being treated as empty, the Flux
restart/readiness checks must fail if any required deployment does not restart
or become ready, the load-driver execution path must stop on a non-zero exit
before tuning continues, and the `RAW_PROFILE` export path must fail if the
selected profile cannot be saved. Keep `|| true` only on non-essential
diagnostics, and preserve the existing control flow in the surrounding local-ci
steps and profile-handling logic.

Comment thread scripts/local-ci.sh
Comment on lines +217 to +224
CURRENT_RS=$(kubectl get pods -n "$APP_NS" \
-l "app=$APP_SERVICE" -o jsonpath='{.items[0].metadata.ownerReferences[0].name}' 2>/dev/null || true)
if [[ -n "$CURRENT_RS" ]]; then
MATCH="replicaset-${CURRENT_RS}-"
log "Pinning profile match to current ReplicaSet: $MATCH"
else
log "WARNING: could not resolve current ReplicaSet for $APP_SERVICE; falling back to '$MATCH'"
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Resolve the active ReplicaSet instead of the first Pod.

The Pod list is not ordered by Deployment revision. An old ReplicaSet Pod can coexist with a new Pod during a rollout. This can set MATCH to a previous ReplicaSet and select the wrong learned profile.

Query ReplicaSets owned by deploy/$APP_SERVICE, select the active revision, and fail if the result is not unique.

🤖 Prompt for 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.

In `@scripts/local-ci.sh` around lines 217 - 224, Update the current ReplicaSet
lookup in the local-ci script’s matching flow to use ReplicaSets owned by
deploy/$APP_SERVICE instead of reading ownerReferences from the first Pod.
Resolve the active revision from the ReplicaSet set, ensure the result is unique
before assigning MATCH, and fail the script if more than one candidate matches
so an old rollout pod cannot pin the wrong learned profile.

entlein and others added 11 commits August 7, 2026 15:21
… score 0

Flux v2.9.3 gets the same treatment as Argo CD: one attack suite, one functional
suite and one tuned SBoB per controller — source, kustomize, helm, notification,
image-reflector and image-automation.

Four of the seven Flux Deployments ship NO Service, and bobctl resolves its exec
pod through one, so every exec attack would have bailed silently. example/flux-
vulnerable.yaml adds a ClusterIP for kustomize, helm, image-reflector and
image-automation on their existing http-prom port.

Every assertion was verified to fire on a live cluster before being written,
rather than copied from an existing suite. What fires: R0001, R0005, R0010,
R0011, R1004, R1010, R1012. What does not: R0006, R0008, R1000 — those attacks
ship as probes with expectedDetections: [].

That verification overturned an assumption carried from the argocd work. R0010,
R1010 and R1012 all fire here DESPITE cat /etc/shadow returning EACCES and the
hardlink failing with EXDEV: the rules trigger on the attempt, and they fire
with a learned profile present. The argocd suites in #178 dropped those same
assertions on the reasoning that a failed open cannot be seen, which is wrong.
Whatever suppressed them on argocd, it was not the EACCES. That needs revisiting
separately.

Result: 42 asserted detections, 42 found, 0 missed, 0 false positives across all
six controllers.

One process note worth recording. The first learn window was polluted by the
probing that established the list above — 12 /bin/busybox execs ended up in
source-controller's baseline, which killed R0001 because cat and ln had become
normal. Re-learned without touching the containers; the profile then had the
single expected exec.

SBoBs verified: no unanchored opens, no YAML aliases, no cluster-specific
literals, none masking /etc/shadow, the SA token or /proc/1/environ, and every
one round-trips through the live storage:sbob-rc5s unchanged.

All containers in flux-system are named `manager`, so every assertion targets
that name. Alerts therefore cannot be attributed to a component by container
name alone — the tuner's rule+container matching can cross-talk between the six
legs. Not addressed here; recorded because it affects how much a single leg's
score can be trusted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jd9m962b1JAHdAy3rGe7nm
…bility tested

Rework of the flux legs after four valid criticisms. The first version learned
Flux IDLING — zero Flux custom resources were ever created — which is bob#170
repeated on the next app.

INSTALLER. Was raw install.yaml; now `flux install --components-extra=... --export`
piped through kubectl, followed by `flux check`.

GITOPS LOAD. example/flux/drive-gitops-workload.sh creates 13 Flux CRs covering
every controller: GitRepository (branch and tag), HelmRepository (http and OCI),
OCIRepository, Kustomization (git source, OCI source, postBuild.substitute with
healthChecks), HelmRelease (http chart, OCI chart), Provider/Alert/Receiver, and
ImageRepository with semver and numeric ImagePolicies. Per round it forces
reconciles, patches HelmRelease values, suspends and resumes, creates and deletes
a transient Kustomization, and runs flux get/events/stats/trace.

The difference this makes is the whole point:

  source-controller     17 -> 735 opens, egress 1 -> 9 (github.com, ghcr.io,
                        pkg-containers.githubusercontent.com)
  kustomize-controller  12 -> 4301 opens

All six controllers tune to score 0 on the driven profiles.

COLLAPSE. The driven source-controller profile came back with a bare /* — 610 of
735 opens wildcard-rooted — which matches /etc/shadow and blinds R0010/R1010/R1012.
Added /data, /tmp/kustomization- and /tmp/helmrelease- to the CollapseConfiguration;
after that, 0 wildcard-rooted.

That also explains the argocd puzzle in #178: those rules were suppressed by a
profile containing /*, NOT by the open failing with EACCES. My reasoning there
was wrong and #178 needs revisiting.

PORTABILITY, k3s -> kind, measured rather than assumed. A kind cluster with
DIFFERENT CIDRs (pod 10.244.0.0/16, service 10.96.0.0/12 vs k3s 10.42/10.43) got
the k3s-learned SBoBs bound, then the same GitOps load. False positives went
7 -> 8544 -> 6 -> 4 across four rounds of fixes:

  storage silently DROPS /tmp/* when /tmp/⋯ siblings are present (consolidateOpens
  subsumes it), and ⋯ matches exactly one segment, so nested working directories
  went unmatched — 8457 FPs on kustomize-controller alone. Fixed by shipping only
  /tmp/* and no /tmp/⋯ family.
  the projected SA-token timestamp was baked in literally (my generator skipped
  normalise_rotating) — now ⋯.
  R0006 needs rulePolicies.processAllowed, not just the open — set to the 15-char
  kernel comm.
  /data and /data/* were missing on controllers other than source.

Final: 4 FPs. THREE of them are the node-agent path-truncation bug
(/ization.yaml, /edis, /ests/jwt.yam — leading characters eaten), not fixable in
an SBoB. The fourth is R0011 on a GitHub Pages IP, the cost of dropping rotating
IPs in favour of dnsNames.

REVERSE DIRECTION IS INCONCLUSIVE, not passing. Profiles learned natively on
kind and applied to k3s produced 3684 FPs, but the kind-learned
kustomize-controller profile contains no /tmp entries at all: the 2-minute learn
window closed before its render work ran. That measures an unrepresentative
profile, not portability. It needs a longer window on kind to be a real test.

NOT DONE: the killchain GIF, kubescape/rule-coverage.yaml entries, and the CI
matrix leg. Flux is still not CI-gated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jd9m962b1JAHdAy3rGe7nm
…t exist

Narrowing the wildcards turned out to require knowing which forms the matcher
actually supports, and one obvious-looking narrowing is a silent no-op.

/tmp/kustomization-* matches NOTHING. Verified against storage's CompareDynamic:
`*` is only meaningful as a COMPLETE path segment, so "kustomization-*" is read
as a literal directory name. Narrowing /tmp/* to /tmp/kustomization-* therefore
did not tighten the profile, it emptied it — false positives went from 4 to 565.

The narrowest forms that actually match are /tmp/⋯ (exactly one segment) plus
/tmp/⋯/* (one segment then anything). Same for /data/⋯/*.

Second cause of the 565: the projected SA-token DIRECTORY open had no entry at
all. The profile carried .../⋯/token, .../⋯/ca.crt and .../⋯/namespace but not
the directory itself, which every controller opens — 367 of the 565.

False positives on the benign GitOps load: 4 -> 565 -> 17.

Of the remaining 17, seven are the node-agent path-truncation bug
(/k8s.io/apimachinery/..., /monitoring.coreos.com/..., /.kube/config,
/job-rollup-daily.yaml — leading characters eaten) and three are R0011 on GitHub
IPs, the cost of keying external egress on dnsNames instead of rotating
addresses. Neither is fixable in an SBoB.

No SBoB contains a bare /* or any all-wildcard path, and none masks /etc/shadow,
the SA token or /proc/1/environ.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jd9m962b1JAHdAy3rGe7nm
…s with it

Reviewed every wildcard in the six flux SBoBs against the spec at
billofbehavior.com/bob/docs/spec.

Spec rules applied:
  ⋯                exactly one path segment
  * mid-path       zero or more segments
  * trailing       ONE or more segments, never zero (so /tmp/* excludes /tmp)
  ⋯⋯               zero or more exec ARGS; * is not an arg wildcard
  portability      literals for stable cross-cluster paths, ⋯ for version or
                   vendor segments that vary, mid-path * for flexible depth

Result: every SBoB is anchored — no bare /*, no all-wildcard path, no ** (which
the spec says is rejected at admission), and no intra-segment glob (verified
earlier that /tmp/kustomization-* matches nothing at all).

/tmp/⋯ plus /tmp/⋯/* is redundant under the spec: trailing * already means
one-or-more, so /tmp/* subsumes both. Merged. Same for /data.

THE SPEC AND THE RUNTIME DISAGREE, and this blocks the "narrow wildcards with no
false positives" goal. storage's CompareDynamic returns true for
/tmp/* vs /tmp/abc/.git/refs — matching the spec. node-agent's rule evaluation
does not: with /tmp/* in the enforced profile, reading
/tmp/probe-deep/a/b/f.yaml still raises R0002. Verified directly, and neither
/tmp/⋯/⋯/⋯/⋯ nor /tmp/** helps (storage rewrites the first to /tmp/* anyway).

So a deep, variable directory tree cannot currently be expressed without either
false positives or an unanchored entry. The flux profiles keep the anchored form
and accept the residual FPs rather than widen to something that masks
/etc/shadow.

False positives on the benign GitOps load: 13, of which the /tmp ones are this
discrepancy, several are the node-agent path-truncation bug, and one is R0011 on
a GitHub IP.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jd9m962b1JAHdAy3rGe7nm
The learned Flux profiles were collapsing to a bare /*. That path matches
/etc/shadow, so cp.was_path_opened() returned true and R0010, R1010 and R1012
stopped firing entirely — visible only as three "missed" attacks, a long way
from the cause. The same thing had already happened on Argo CD.

Learn uncollapsed, keep the original, then tune:

- CollapseConfiguration open/endpointDynamicThreshold 50 -> 100000, so nothing
  collapses while learning. The per-prefix threshold-1 entries stay: they are
  anchored deep (/tmp/gitrepository-, /tmp/kustomization-) and fold the random
  render dir at a point where the discriminating prefix survives.
- kubescape/collapse-config.yaml was never applied by anything. The live object
  had drifted to 485 entries. `make kubescape` now applies it, and deploy-flux
  applies example/flux/flux-collapse-config.yaml on top.
- local-ci keeps results/learned-profile-raw-<app>.yaml before the tuner runs,
  and dies if the LEARN itself already collapsed — there is nothing to restore
  at that point, the threshold is what has to change.
- restore-overcollapsed.py puts the originals back if the tuner over-collapses.
- check-no-overbroad.py fails the build on any open whose first segment is a
  wildcard, over every shipped SBoB. Wired into validate-suites.sh.

Load driver is upstream fluxcd/flux-benchmark pinned at e09c533, run through
its own Timoni modules and podinfo OCI artifact against an in-cluster registry;
only the registry plumbing differs from upstream, because kind's Docker-network
DNS has no k3s equivalent. It exercises source-, kustomize- and helm-controller
plus notification ingestion; image-reflector and image-automation have no
upstream benchmark module and still rely on drive-gitops-workload.sh.

SBoBs are the full observed surface, not a summary: source-controller 824 opens,
kustomize-controller 540, and 11-16 for the rest. Volatile segments become the
one-segment ellipsis — PIDs, UUIDs, git SHAs (bare and suffixed), sha256
digests, and any segment carrying a 6+ digit run. The first segment is never
rewritten, since widening it is the annihilation this commit exists to prevent.
Head-truncated paths from the node-agent truncation bug (/pe.io/v1beta1/...) are
dropped: they cannot match at runtime.

Functional suites are real client traffic instead of repeated health probes —
the artifact-server GETs kustomize- and helm-controller issue every reconcile,
the event JSON every controller POSTs, and the Prometheus scrape plus pprof
surface for the controllers that serve nothing else.

post-render.sh is off the critical path: helm 3 and 4 disagree on whether
--post-renderer takes a path or a plugin name. The runc-filesystem mount is a
generated values overlay applied with -f instead.
A collapse at the root emits "/*", which matches /etc/shadow and silently
disables R0010/R1010/R1012. openDynamicThreshold alone left that to a global
default; an explicit "/" entry states the intent at the one prefix where
collapsing is never acceptable.
Picks up stripOverBroadOpens, so the tuner itself refuses to carry or emit an
unanchored wildcard. restore-overcollapsed.py stays as the outer-repo backstop
for the opposite failure — the tuner removing more than it should.
…ller

Two variants from the same learn, because they answer different questions and
neither one is right for both.

example/flux/sbobs/ is the full observed surface: source-controller 824 opens,
kustomize-controller 540. Every path the workload actually touched, with only
volatile segments folded to the one-segment ellipsis. This is the ground truth a
regression is diffed against, and what restore-overcollapsed.py substitutes back
when the tuner removes too much.

example/flux/sbobs/wildcarded/ is the shippable policy: 27 and 17. The churn
roots are folded to anchored wildcards — /tmp/⋯ for the per-reconcile render
dirs, /data/* for the artifact store, /usr/share/ca-certificates/mozilla/⋯ and
/etc/ssl/certs for the CA bundle, which is 120 literal entries that change with
every base-image rebuild. The remaining controllers are identical in both
variants: they touch 11-16 paths and there is nothing to fold.

Every path in both variants is anchored by a literal first segment, so none of
them can match /etc/shadow and none blinds R0010/R1010/R1012.

Network in both: the apiserver ClusterIP becomes the service-CIDR pair
(10.43.0.0/16 + 10.96.0.0/12) so it survives a move between k3s and kubeadm,
external peers key on DNS rather than a rotating A record, pod peers on label
selectors, and inbound Host headers on *:port.
…full learn

Every other app ships its policy at example/<app>/sbobs/cp-<app>.yaml, so Flux
having the 824-open raw learn at that path made it the odd one out and pointed
anyone reading it at the wrong artifact.

sbobs/ is now the collapsed, shippable profile and sbobs/uncollapsed/ the full
observed surface it was derived from. No content changed, only the paths.
PR 187 adds R0012 (Unexpected Ingress Network Traffic), which alerts on any
inbound peer matching neither an ipAddress/CIDR ingress entry nor a podSelector
one. An absent stanza therefore means every inbound connection is unexpected.

Verified on the celnet agent: the Flux controllers learn ingress as null even
though egress learns fine (9/4/4/3/1/1 entries), and R0012 duly fired on all six
— overwhelmingly on 10.42.0.1:9440, which is the kubelet liveness/readiness
probe, plus one inter-controller artifact fetch at 10.42.0.168:9090.

Kubelet probes arrive from the node rather than a pod, so no podSelector can
ever match them; they need the pod CIDR of both common distributions as a
literal peer. Inter-controller traffic does have a pod identity, so it is
declared with podSelector + namespaceSelector, which is what 187's
cp.was_selector_in_ingress resolves and what survives pod-IP churn.

Applied to both the collapsed and the uncollapsed variant so they stay
comparable.
Picks up R0012 + FamIngress, selector-based network matching, tuner mutation of
ingress/egress, the volatile-path fix, the target-resolution fixes (-n,
--service, --container) and the Rules() sync against default-rules.yaml.

Now that #61 is merged the pointer targets main rather than a feature branch, so
it cannot go stale under a squash.
@entlein
entlein force-pushed the feat/flux-contrast branch from 692d218 to 8b9f99c Compare August 7, 2026 13:25
@k8sstormcenter k8sstormcenter deleted a comment from coderabbitai Bot Aug 7, 2026
entlein and others added 4 commits August 7, 2026 18:23
Modelled on example/redis/distros/DEMO.md, with the parts that are specific to a
controller workload: the load is upstream fluxcd/flux-benchmark rather than
client traffic, because a profile learned from an idle controller is worthless.

Pins bobctl v0.1.3-rc1 and says why it is a pre-release cut from this PR rather
than from main: main's submodule pointer still predates entlein/bob#61, so a
binary built from main does not contain these fixes and will not reproduce the
numbers.

Documents the two ingress shapes every Flux SBoB needs and why they are not
interchangeable — a kubelet probe arrives from the node, has no pod identity, and
no podSelector can ever match it.

Known gaps are listed rather than omitted: Flux learns ingress: null on the
celnet agent, R0012 does not fire on these controllers for reasons not yet
isolated (#189), and the redis-distro drifted-binary-exec attack asserts a rule
that cannot fire for it.
…istros)

./distro.sh        deploy Flux (make deploy-flux)
./distro.sh sbob   deploy AND bind the six controller SBoBs:
                   apply sbobs/cp-flux-<c>.yaml + label the Deployment pod
                   template kubescape.io/user-defined-profile=flux-<c>, then
                   roll to pick it up (operator only stamps new pods).
…ux-registry SBoBs

- distro.sh is now data-driven: binds every sbobs/cp-flux-*.yaml (server-side
  apply + user-defined-profile label), reading namespace/profile/app from each
  SBoB. Covers the 6 controllers + source-watcher + flux-registry.
- Add starter SBoBs for source-watcher and flux-registry (derived from the
  learned profiles; to be curated).
RCA of flux-system network FPs against the sofia node-agent
(rc-sofia-port0fix). Two hard runtime behaviors, both verified live:

1. was_address_in_{egress,ingress} honors an entry's ipAddresses ONLY
   when the entry is selector-free AND the observed dest port is listed.
   A podSelector/namespaceSelector routes the entry to the peer surface
   and its ipAddresses are dropped from the address surface. Port is
   enforced: 10.43.0.0/16 with ports [80,443] matched :80 but not the
   alertmanager egress :9093 until TCP-9093 was added.
2. The network projection binds at pod-attach and is NOT reprojected on
   CP update (unlike opens). A bound SBoB's network edits take effect
   only after the pod rolls.

Fixes:
- Every CIDR allowlist is a selector-free entry carrying the real dest
  ports. ingress node-probes [10.42.0.0/16] (ports 8080/9090/9440)
  clears R0012; egress cluster-service-ranges [10.43.0.0/16] ports
  [80,443,5000,9093] clears in-cluster R0011.
- github egress: dnsNames stay; added selector-free github-cdn-ranges
  [140.82.112.0/20, 185.199.108.0/22] to cover node-agent's IP->domain
  correlation miss on GitHub's rotated CDN A-records.
- distro.sh binds ALL 8 sbobs: skip missing deployments, rollout-restart
  each so node-agent reprojects, verify the label landed.

Result: 0 R0011/R0012 across all 8 flux components under forced reconciles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A8UV2B7b6dpDJQci31aC6c
entlein added a commit that referenced this pull request Aug 24, 2026
postgres-distros carried pkg at 2a2b337, which predates entlein/bob#61, so none
of the volatile-path normalisation, --container retargeting, network tuning or
R0012 grading was reachable from this branch. The postgres SBoBs are raw learns
full of literal OIDs and shm segments, which is exactly what that work fixes.

Independent of PR #181: that carries the same bump for the flux work, but this
branch should not have to wait on it.
entlein and others added 4 commits August 25, 2026 14:55
Takes only scripts/, Makefile, kubescape/ and the pkg pointer — no postgres
content — so the flux PR stays flux-only while getting the fixes the recording
depends on:

  - local-ci waits on the node-agent rollout BEFORE deploying the app, unbinds
    the workload so it learns instead of enforcing, and clears dead pods
  - the tuner no longer wildcards an exec path's basename, and emits the
    runc-init allowlist; clean-profile.py preserves it
  - generalise-sbob.py: serviceRef protocols, exec envs stripped
  - Makefile: KS_RUNC/KS_RUNC_MNT as --set, off the post-renderer
  - net-v2-rc1 pair with networkEventsStreaming off

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Taking scripts/ wholesale from postgres-distros dropped the flux-* leg and the
APP_LOAD_PREPARE/APP_LOAD_DRIVER machinery the flux benchmark drives learning
with. Flux's local-ci is restored and the three ordering fixes ported into it
instead: rollout status before the app deploys, dead pods cleared before the
deploy target's readiness wait, and unbind + recreate before learning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Taking scripts/ and Makefile from postgres-distros dropped flux-specific
content: deploy-flux in the Makefile, and 24 flux references in fast-tune.sh.
Those are restored; only the genuinely general fixes are kept on top —
KS_RUNC_MNT as --set in the Makefile, and the runc-init allowlist in
clean-profile.py.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The consolidated ContainerProfile — the one worth tuning — sits at status
"ready" (helpers.Learning in storage) until aggregation finishes. What reaches
"completed" first is the per-report time-series shard, named
<consolidated>-<32 hex>, and on flux that took about twenty minutes, well past
the 600s poll. So the flux legs died on "No completed profile found" while six
perfectly good profiles sat in the namespace.

Gating on kubescape.io/completion == complete picks the consolidated profile as
soon as its learning window closes. Shards are excluded by name so a partial
per-report view can never be tuned by accident; status == completed is still
accepted for profiles that report it that way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
entlein and others added 6 commits August 25, 2026 15:44
Relearned against the new agent pair, regenerated with the current schema, and
validated bound rather than only tuned. source-controller measured:

  benign  11/11 pass, 0 false positives
  attack  57 alerts, 11 distinct rules — R0001, R0002, R0004, R0005, R0006,
          R0008, R0010, R0011, R1004, R1010, R1012

Every controller gains an ingress entry with entity: host. Flux probes are
httpGet, so the kubelet connects over the network and carries no pod identity;
nothing selector-based can admit it.

The learn window was the real defect. The benchmark drives reconcile load from
an in-cluster OCI registry, while the functional tests assert on GitRepository
and HelmRepository artifacts that only drive-gitops-workload.sh creates. So the
learned profile contained no upstream egress at all, and binding it fired R0011
and R0005 on every github.com, ghcr.io and stefanprodan.github.io fetch the
benign suite makes — four false positives that were a gap in what we exercised,
not in the SBoB. The flux leg now runs both drivers, and the source-controller
SBoB declares that upstream by DNS name with the GitHub and GitHub-Pages CIDRs
alongside, since the name is not always attributable at connect time.

Tightening that egress did not cost the attacks: the C2 and DNS-anomaly attacks
still fire, because they reach different peers.

generalise-sbob.py drops ingress/egress entries that carry ports but no peer.
Such an entry admits every destination on that port — the network form of a
leading wildcard, and it would make R0011 blind on 443. None of the shipped
profiles had one; the guard keeps it that way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…troller

Measured bound, per controller, benign then attack:

  controller                    benign FPs   attack alerts / rules
  source-controller                 0            58 / 11
  kustomize-controller              0            54 / 11
  notification-controller           0            55 / 11
  image-reflector-controller        0            57 / 11
  helm-controller                   0            42 / 11
  image-automation-controller       0            45 / 11

Same eleven on all six: R0001, R0002, R0004, R0005, R0006, R0008, R0010, R0011,
R1004, R1010, R1012.

The six were NOT generalised before this. source-controller shipped ten literal
paths of the form /tmp/helmchart-<release>-<md5>.tgz and the same name with a
random numeric suffix appended, one pair per HelmRelease. They never recur, so
every helm fetch would have raised R0002. normalise() only ever examined whole
segments and the volatile token here sits inside a leaf name, so it walked past
all ten. collapse_embedded_volatile_leaves handles that case; storage's "*"
matches zero-or-more whole segments rather than a prefix, so /tmp/helmchart-*
cannot be expressed and the leaf collapses to one wildcard segment. No flux
attack asserts R0002 or R0010 on a /tmp path, so nothing is lost.

That collapse first emitted two /tmp/⋯ entries with different flags and the CRD
rejected the object — duplicate entries for key [path]. Dedup keys on the path
alone now and unions the flags.

Every in-cluster peer is declared by name: kube-dns, source-controller,
notification-controller, alertmanager. net-v2-rc1 stores serviceRefName and
serviceRefNamespace but does not resolve them when evaluating R0011, so a
serviceRef-only entry admits nothing and each call to the named Service alerted.
The references stay, since they are the portable form and tighten by themselves
once the agent honours them, with the two common Service CIDRs alongside so the
entries work today. Both are scoped to the declared port. entity: host needed
the same treatment for R0012 earlier.

Each suite was confirmed to attack its own component: five suites run in
sequence produced alerts on five distinct controllers, and source-controller,
deliberately left out, stayed silent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… CIDR crutch

The earlier run was on net-v2-rc1, which predates the fork's serviceRef work.
Adopting main's pins (net-v2s-f6fa47d6 for storage and node-agent,
networkEventsStreaming enabled) and re-testing shows serviceRef resolving
correctly: the six SBoBs declare every in-cluster peer by name ALONE, with no
ipAddresses at all, and the benign suite produces zero false positives.

The Service CIDRs added as a fallback are therefore removed from all nineteen
serviceRef entries. That was the whole point of the reference — a ClusterIP is
cluster-specific and a profile carrying one is not portable.

Measured on the new pair, all six controllers bound:

  benign  11/11 pass, 0 false positives across flux-system
  attack  6/6 controllers alerting, 41-57 alerts each, 10-11 rules each

node-agent#97 is filed with the rc1 evidence and is being closed with this
result.

Two flux controllers reported no detections at all on the first attack round of
this retest. That was not the agent: ResolveServicePod was resolving the Service
to a Succeeded pod left by the previous ReplicaSet, so the attacks execed into a
corpse. Fixed in pkg with a regression test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The demo still described admitting kubelet probes with the pod CIDR, which also
admits every pod on that node; entity: host replaced it in the SBoBs and the doc
now matches. Peers are named rather than addressed, so the profiles port.

Results are re-measured on net-v2s-f6fa47d6: six controllers bound, 0 benign
false positives each, 41-57 attack alerts and 10-11 rules each.

R0012 has never fired on these controllers — zero alerts across every run, while
R0011 fires on the same pods in the same window. That makes the entity: host
stanza unverified on Flux specifically, and the doc says so rather than implying
the ingress shape was proven here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
react2shell's stages summed to exactly 30m and dapr-shop's to 26m, so anything
driving these waited that long for a single pass. K6_SPEED divides every stage;
the default of 6 gives a ~5m and ~4m cycle.

The SHAPE is what these encode — peaks and quiet periods, so the learned
baseline is not a flat rate — and scaling every stage by the same factor keeps
it. Only the wall-clock shrinks. K6_SPEED=1 restores the original for a long
soak.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
On k3s/flannel the kubelet probe arrives from the cni0 gateway (10.42.x.1) and
kube-dns egress hits its Service ClusterIP (10.43.0.10), neither of which the
node-agent resolves to the recorded pod-selectors -> R0011/R0012 FP storm (~320).
Add a NetworkNeighbor with ipAddresses CIDR (v0.0.2 list-form) for the
cluster-internal ranges — k3s (10.42/10.43) AND kind (10.244/10.96) — scoped to
the observed ports. Purely additive: existing selectors + entity:host untouched,
so the kindnet case stays valid. Internal-only, so external C2 egress + external
DNS still fire (verified). Result on our stack: 377 -> 41 FPs (R0011/R0012 -> 0).
Residual 41 = R0006 SA-token, a net-v2-rc1 ⋯-normalisation gap fixed by the
net-v2s node-agent bump (PR#214).
capabilities: [CAP_DAC_OVERRIDE, CAP_DAC_READ_SEARCH, CAP_NET_ADMIN]
rulePolicies:
R0002:
processAllowed: ['runc:[1:INIT]', 'runc:[1:CHILD]', 'runc:[2:INIT]', 'runc:[3:INIT]']

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

still checking how that d look for crun

internal: true
methods: [GET]
- direction: outbound
endpoint: :80/helmchart/helm-benchmark/helm-benchmark-podinfo-hr-4/podinfo-6.5.3.tgz

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

decide if the benchmarks should be allowlisted/wildcarded - would a user ever run them in prod to verify their install?

ipAddresses: [140.82.112.0/20, 185.199.108.0/22]
ports:
- {name: TCP-443, port: 443, protocol: TCP}
- identifier: kube-dns

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

rerun with all dns providers and/or add generic identifier on udp/53

Host: ['10.42.0.233:9440']
internal: true
methods: [GET]
- direction: outbound

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

hmm, well that clearly fell through the generalization...

- flags: [O_RDONLY]
path: /sys/kernel/mm/transparent_hugepage/hpage_pmd_size
- flags: [O_WRONLY, O_CREAT, O_CLOEXEC, O_RDONLY]
path: /var/lib/registry/docker/registry/v2/blobs/sha256/1f/1fde96af441fa5f15363d3bba9a16b6e15d960d0e897934cb26308d1eebb5766/data

@entlein entlein Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

why did they not get wildcarded -> AI , did you miss this entire file? needs to be processed

path: /usr/local/bin/source-watcher
opens:
- flags: [O_RDONLY, O_CLOEXEC]
path: /1/serverresources.json

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

must be rerecorded after NA merged the IG fix for aboslute paths

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant