Skip to content

Fix/argocd 170 - #176

Merged
entlein merged 15 commits into
mainfrom
fix/argocd-170
Aug 2, 2026
Merged

entlein merged 15 commits into
mainfrom
fix/argocd-170

Conversation

@entlein

@entlein entlein commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds Kubescape configuration, profile conversion and validation tools, an Argo CD workload driver, updated ContainerProfiles and representativeness rules, and cluster-free suite validation in CI.

Changes

Argo CD Kubescape workflow

Layer / File(s) Summary
Kubescape configuration and representativeness
Makefile, kubescape/*
Adds optional learning-period and post-renderer controls. Updates Kubescape images, streaming, collapse settings, detection rules, and Argo CD behavior criteria.
Learned-profile conversion and validation
scripts/sbob-from-learned.py, scripts/portable-sbob.py, scripts/check-representativeness.py
Adds profile sanitization, portability normalization, live or committed profile loading, and checks for executions, opens, ports, IPs, and DNS.
Argo CD workload and ContainerProfiles
example/argocd/drive-gitops-workload.sh, example/argocd/sbobs/*
Adds timed Application and ApplicationSet activity and records Argo CD component execution, filesystem, capability, endpoint, ingress, and egress data.
Suite validation and scenario maintenance
scripts/validate-suites.sh, .github/workflows/ci-bobctl-autotune.yaml, example/argocd*
Adds cluster-free suite validation and updates Argo CD attack prerequisites and probe output.

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

Sequence Diagram(s)

sequenceDiagram
  participant WorkloadDriver
  participant ArgoCDServer
  participant ArgoCDControllers
  participant KubernetesAPI
  WorkloadDriver->>ArgoCDServer: create, refresh, sync, and delete Applications
  ArgoCDServer->>ArgoCDControllers: trigger rendering and reconciliation
  ArgoCDControllers->>KubernetesAPI: access workloads and cluster services
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (1 warning, 2 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title identifies an Argo CD fix but does not describe the primary changes clearly enough. Replace the issue-style title with a concise summary of the main Argo CD profile and validation changes.
Description check ❓ Inconclusive No pull request description was provided, so it does not explain how the changes relate to the intended fix. Add a brief description of the Argo CD changes, validation updates, and intended behavior.
✅ Passed checks (2 passed)
Check name Status Explanation
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
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/argocd-170

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: 19

🤖 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/argocd-server-attacks.yaml`:
- Line 69: Update the command in the pivot marker to pass TOKEN into the Perl
process and issue an authenticated Kubernetes API HTTP request using that token.
Make the Perl logic exit successfully only when the request receives a
successful response; otherwise exit nonzero so k8s_pivot_done is emitted only
after a verified authenticated API call.

In `@example/argocd/drive-gitops-workload.sh`:
- Around line 70-72: The driver must stop hiding workload failures and falsely
reporting success. In example/argocd/drive-gitops-workload.sh lines 70-72, check
apply_apps and apply_appset status, verify the Applications exist, and exit
non-zero when setup fails; also surface proxy and metrics failures at lines
100-109 by counting failed calls and failing when appropriate. In
example/argocd/sbobs/cp-argocd-server.yaml line 108, confirm proxy calls return
data after the driver succeeds and regenerate the profile so endpoints records
inbound API traffic.
- Line 25: Validate the DURATION argument immediately after its default
assignment, ensuring it is a positive integer before the script reaches the
arithmetic expression around END. Reject invalid or non-positive values with a
clear error and terminate, while preserving the existing default and normal
execution for valid durations.

In `@example/argocd/sbobs/cp-argocd-application-controller.yaml`:
- Around line 931-934: Remove CAP_NET_ADMIN from the capability allowlists in
example/argocd/sbobs/cp-argocd-application-controller.yaml:931-934,
example/argocd/sbobs/cp-argocd-applicationset-controller.yaml:104-107,
example/argocd/sbobs/cp-argocd-notifications-controller.yaml:116-119,
example/argocd/sbobs/cp-argocd-repo-server.yaml:319-322, and
example/argocd/sbobs/cp-argocd-server.yaml:104-107. Preserve only capabilities
required and actually granted by each corresponding workload securityContext,
retaining repo-server capabilities needed by git, gpg, and rendering.
- Around line 946-955: Remove the spurious :6443/metrics inbound entry from
example/argocd/sbobs/cp-argocd-application-controller.yaml:946-955 while
retaining :8082/healthz; remove it from
example/argocd/sbobs/cp-argocd-applicationset-controller.yaml:108-118 and
example/argocd/sbobs/cp-argocd-notifications-controller.yaml:120-130; remove it
from example/argocd/sbobs/cp-argocd-repo-server.yaml:323-333 while retaining
:8084/healthz. Update the observation attribution pipeline so one observation is
not copied across profiles and is attributed only to the container actually
serving that endpoint.
- Around line 20-120: Regenerate the opens list in
cp-argocd-application-controller.yaml using full CA bundle paths instead of
root-relative fragments or partial basenames. Collapse the certificate reads
into a single /usr/share/ca-certificates/mozilla/⋯ entry, matching the structure
used by cp-argocd-repo-server.yaml.
- Around line 169-807: Collapse cluster-specific Kubernetes discovery paths,
temporary serverresources suffixes, and concrete HTTP cache keys in the
generated SBoB to normalized ellipsis paths such as the discovery and HTTP cache
roots. Update the normalization logic in scripts/sbob-from-learned.py so future
profile generation applies these collapses consistently, then regenerate the
affected cp-argocd-application-controller profile.

In `@example/argocd/sbobs/cp-argocd-applicationset-controller.yaml`:
- Around line 150-166: Update the workload driver associated with the
applicationset-controller baseline to seed a git generator alongside the
existing list generator, then regenerate the profile so it includes the required
DNS and SCM HTTPS egress and resolver file entries. If the driver must remain
list-only, document that limitation instead.

In `@example/argocd/sbobs/cp-argocd-repo-server.yaml`:
- Around line 371-385: Update the ingress rule’s podSelector to retain only
app.kubernetes.io/name: argocd-application-controller, removing the
apps.kubernetes.io/pod-index and statefulset.kubernetes.io/pod-name identity
labels so all StatefulSet replicas match.
- Around line 11-12: Make the image pinning consistent across the Argo CD
ContainerProfile set: either add matching imageID/imageTag fields to the other
four profiles or remove both fields from the repo-server profile. Preserve the
existing container.name matching for argocd-repo-server and ensure all five
profiles follow the same pinning strategy.
- Around line 402-413: Update the GitHub egress configuration represented by the
removed entries to use one rule matching github.com through dnsNames only.
Remove the resolved ipAddress entries and do not add or retain pinned GitHub
addresses; preserve the existing port, protocol, namespace, pod, identifier, and
external rule settings.

In `@Makefile`:
- Line 352: Update KS_LEARN_FLAGS so KS_LEARN_PERIOD sets both
nodeAgent.config.learningPeriod and nodeAgent.config.maxLearningPeriod to the
requested period, preserving the existing behavior when KS_LEARN_PERIOD is
unset.

In `@scripts/check-representativeness.py`:
- Around line 80-82: Remove the unused name parameter from the check function
signature, then update its call site around the main processing flow to pass
only rules and facts while preserving the existing return behavior.
- Line 74: Update the port-name collection in the facts construction to exclude
unnamed entries before storing them in facts["ports"]. Ensure only non-None
names reach check’s sorting and joining logic, while preserving the existing
collection of named ports.
- Around line 141-146: Update load_sbobs to validate each parsed document before
accessing metadata.name: skip empty, comment-only, non-mapping, or
metadata-missing/name-missing SBoB files and emit a readable message identifying
the malformed file, while continuing to process the remaining cp-*.yaml files.

In `@scripts/sbob-from-learned.py`:
- Around line 142-163: Update the output construction in the converter to
preserve the source spec’s imageID and imageTag fields, alongside the existing
conditional passthrough fields, so regenerated repo-server SBoBs retain them.
Use the existing spec values and only add each field when present; keep the
current handling of all other fields unchanged.
- Around line 116-120: Update the opens processing near normalise_rotating to
merge entries sharing the same normalised path before broad-path filtering.
Union each duplicate entry’s flags, matching the existing execs deduplication
approach, then derive dropped and kept from the merged opens so each path
appears once with the complete flag set.
- Around line 105-114: Update main’s input JSON loading around the spec lookup
to handle missing "spec" explicitly, raising a clear error that includes
args.src instead of allowing an uncaught KeyError. Preserve the existing
behavior when the spec key is present.

In `@scripts/validate-suites.sh`:
- Around line 55-59: Update the validation result logic in
scripts/validate-suites.sh so missing “loading (attack suite|functional tests)”
output is not automatically treated as a pass. Preserve the known “connecting to
cluster” success case, and classify other output as failure unless it matches an
explicitly recognized post-validation cluster error.
🪄 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: 89f231eb-fab0-45d7-a97d-0c0f9fda7b57

📥 Commits

Reviewing files that changed from the base of the PR and between 34acc2d and 29c9fd3.

📒 Files selected for processing (22)
  • .github/workflows/ci-bobctl-autotune.yaml
  • Makefile
  • example/argocd-repo-server-attacks.yaml
  • example/argocd-server-attacks.yaml
  • example/argocd/argocd.manifest.yaml
  • example/argocd/argocd.yaml
  • example/argocd/attack-pod.yaml
  • example/argocd/attacks.yaml
  • example/argocd/drive-gitops-workload.sh
  • example/argocd/functional-tests.yaml
  • example/argocd/sbobs/cp-app.yaml
  • example/argocd/sbobs/cp-argocd-application-controller.yaml
  • example/argocd/sbobs/cp-argocd-applicationset-controller.yaml
  • example/argocd/sbobs/cp-argocd-notifications-controller.yaml
  • example/argocd/sbobs/cp-argocd-repo-server.yaml
  • example/argocd/sbobs/cp-argocd-server.yaml
  • example/argocd/sbobs/cp-observer.yaml
  • kubescape/default-rules.yaml
  • kubescape/representativeness.yaml
  • scripts/check-representativeness.py
  • scripts/sbob-from-learned.py
  • scripts/validate-suites.sh
💤 Files with no reviewable changes (7)
  • example/argocd/sbobs/cp-app.yaml
  • example/argocd/sbobs/cp-observer.yaml
  • example/argocd/argocd.manifest.yaml
  • example/argocd-repo-server-attacks.yaml
  • example/argocd/attacks.yaml
  • example/argocd/functional-tests.yaml
  • example/argocd/argocd.yaml

type: cmdinject
exec:
command: ["sh", "-c", "TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token 2>/dev/null); perl -e 'use IO::Socket::SSL; my $s = IO::Socket::SSL->new(PeerAddr => $ENV{KUBERNETES_SERVICE_HOST}, PeerPort => 443, SSL_verify_mode => 0, Timeout => 3); print $s ? qq{api_reached} : qq{api_unreached}' 2>&1 | head -c 40; echo; echo k8s_pivot_done"]
command: ["sh", "-c", "TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token 2>/dev/null); perl -e 'use IO::Socket::SSL; my $s = IO::Socket::SSL->new(PeerAddr => $ENV{KUBERNETES_SERVICE_HOST}, PeerPort => 443, SSL_verify_mode => 0, Timeout => 3); print $s ? qq{api_reached} : qq{api_unreached}' >/dev/null 2>&1 && echo k8s_pivot_done"]

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

Make the pivot marker depend on an authenticated API request.

Line 69 does not pass TOKEN to Perl. The Perl code also does not send an HTTP request. It only attempts a TLS connection.

Perl exits with status 0 after both api_reached and api_unreached. Therefore, k8s_pivot_done is emitted even when the connection fails. Pass the token to Perl, send an authenticated API request, and exit nonzero unless the response proves that the request succeeded.

🤖 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/argocd-server-attacks.yaml` at line 69, Update the command in the
pivot marker to pass TOKEN into the Perl process and issue an authenticated
Kubernetes API HTTP request using that token. Make the Perl logic exit
successfully only when the request receives a successful response; otherwise
exit nonzero so k8s_pivot_done is emitted only after a verified authenticated
API call.

#
# ./drive-gitops-workload.sh 900 # seconds
set -uo pipefail
DURATION="${1:-900}"

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

Validate DURATION as a positive integer.

DURATION accepts any string. Line 74 uses it in an arithmetic expression. A non-numeric argument produces an arithmetic error or a wrong END value, so the loop exits immediately and the learn window stays empty.

🛡️ Proposed validation
 DURATION="${1:-900}"
+case "$DURATION" in
+  ''|*[!0-9]*) echo "usage: $0 [duration_seconds]" >&2; exit 2 ;;
+esac
+[ "$DURATION" -gt 0 ] || { echo "duration must be > 0" >&2; exit 2; }
📝 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
DURATION="${1:-900}"
DURATION="${1:-900}"
case "$DURATION" in
''|*[!0-9]*) echo "usage: $0 [duration_seconds]" >&2; exit 2 ;;
esac
[ "$DURATION" -gt 0 ] || { echo "duration must be > 0" >&2; exit 2; }
🤖 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/argocd/drive-gitops-workload.sh` at line 25, Validate the DURATION
argument immediately after its default assignment, ensuring it is a positive
integer before the script reaches the arithmetic expression around END. Reject
invalid or non-positive values with a clear error and terminate, while
preserving the existing default and normal execution for valid durations.

Comment thread example/argocd/drive-gitops-workload.sh Outdated
Comment on lines +70 to +72
log "seeding Applications + ApplicationSet"
apply_apps
apply_appset

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 | 🟠 Major | ⚡ Quick win

The driver discards every kubectl error, so an empty learn window looks successful. example/argocd/drive-gitops-workload.sh redirects stdout and stderr to /dev/null on every kubectl call and checks no exit status. The script then prints "workload complete" whether or not any workload ran. cp-argocd-server.yaml shows the consequence: endpoints: [], although the driver issues nine API GETs per round through the argocd-server proxy. The profile records no inbound HTTP at all, which means those calls never reached the server and nobody noticed. An unrepresentative profile is the defect bob#170 tracks, so silent suppression defeats the purpose of this script.

  • example/argocd/drive-gitops-workload.sh#L70-L72: check the exit status of apply_apps and apply_appset, verify the Applications exist, and exit non-zero on failure. Also surface failures from the proxy and metrics calls at lines 100-109, for example by counting them and failing the run when the count is high.
  • example/argocd/sbobs/cp-argocd-server.yaml#L108-L108: after the driver reports real errors, confirm the proxy calls return data and regenerate this profile so endpoints records the inbound API traffic.
📍 Affects 2 files
  • example/argocd/drive-gitops-workload.sh#L70-L72 (this comment)
  • example/argocd/sbobs/cp-argocd-server.yaml#L108-L108
🤖 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/argocd/drive-gitops-workload.sh` around lines 70 - 72, The driver
must stop hiding workload failures and falsely reporting success. In
example/argocd/drive-gitops-workload.sh lines 70-72, check apply_apps and
apply_appset status, verify the Applications exist, and exit non-zero when setup
fails; also surface proxy and metrics failures at lines 100-109 by counting
failed calls and failing when appropriate. In
example/argocd/sbobs/cp-argocd-server.yaml line 108, confirm proxy calls return
data after the driver succeeds and regenerate the profile so endpoints records
inbound API traffic.

Comment thread example/argocd/sbobs/cp-argocd-application-controller.yaml Outdated
Comment thread example/argocd/sbobs/cp-argocd-application-controller.yaml Outdated
Comment thread scripts/check-representativeness.py
Comment on lines +105 to +114
def main():
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--in", dest="src", required=True, help="learned profile as JSON")
ap.add_argument("--out", required=True)
ap.add_argument("--name", required=True, help="SBoB name = the bind-label value")
ap.add_argument("--namespace", required=True)
ap.add_argument("--keep-syscalls", action="store_true")
args = ap.parse_args()

spec = json.loads(Path(args.src).read_text())["spec"]

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

Report a clear error when the input JSON has no spec.

Line 114 indexes ["spec"] directly. A learned profile exported without spec, or an error payload from kubectl, raises an uncaught KeyError with no context. Fail with a message that names the input file.

🛡️ Proposed guard
-    spec = json.loads(Path(args.src).read_text())["spec"]
+    doc = json.loads(Path(args.src).read_text())
+    spec = doc.get("spec")
+    if not isinstance(spec, dict):
+        print(f"error: {args.src} has no .spec object", file=sys.stderr)
+        return 2
📝 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
def main():
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--in", dest="src", required=True, help="learned profile as JSON")
ap.add_argument("--out", required=True)
ap.add_argument("--name", required=True, help="SBoB name = the bind-label value")
ap.add_argument("--namespace", required=True)
ap.add_argument("--keep-syscalls", action="store_true")
args = ap.parse_args()
spec = json.loads(Path(args.src).read_text())["spec"]
def main():
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--in", dest="src", required=True, help="learned profile as JSON")
ap.add_argument("--out", required=True)
ap.add_argument("--name", required=True, help="SBoB name = the bind-label value")
ap.add_argument("--namespace", required=True)
ap.add_argument("--keep-syscalls", action="store_true")
args = ap.parse_args()
doc = json.loads(Path(args.src).read_text())
spec = doc.get("spec")
if not isinstance(spec, dict):
print(f"error: {args.src} has no .spec object", file=sys.stderr)
return 2
🤖 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/sbob-from-learned.py` around lines 105 - 114, Update main’s input
JSON loading around the spec lookup to handle missing "spec" explicitly, raising
a clear error that includes args.src instead of allowing an uncaught KeyError.
Preserve the existing behavior when the spec key is present.

Comment on lines +116 to +120
opens = spec.get("opens") or []
for o in opens:
o["path"] = normalise_rotating(o.get("path", ""))
dropped = [o["path"] for o in opens if is_over_broad(o.get("path", ""))]
kept = [o for o in opens if not is_over_broad(o.get("path", ""))]

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 | 🔵 Trivial | ⚡ Quick win

Merge duplicate opens after path normalisation.

normalise_rotating collapses every ..<timestamp> segment to . Several learned entries therefore collapse to the same path, each with a different flags subset. The script keeps all of them, so the shipped SBoB can contain repeated path values with partial flag sets. execs already gets deduplicated for the same reason. Do the same for opens, and union the flags.

♻️ Proposed dedup for opens
     opens = spec.get("opens") or []
     for o in opens:
         o["path"] = normalise_rotating(o.get("path", ""))
     dropped = [o["path"] for o in opens if is_over_broad(o.get("path", ""))]
-    kept = [o for o in opens if not is_over_broad(o.get("path", ""))]
+    merged = {}
+    for o in opens:
+        if is_over_broad(o.get("path", "")):
+            continue
+        entry = merged.setdefault(o["path"], {"path": o["path"], "flags": []})
+        for f in o.get("flags") or []:
+            if f not in entry["flags"]:
+                entry["flags"].append(f)
+    kept = list(merged.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
opens = spec.get("opens") or []
for o in opens:
o["path"] = normalise_rotating(o.get("path", ""))
dropped = [o["path"] for o in opens if is_over_broad(o.get("path", ""))]
kept = [o for o in opens if not is_over_broad(o.get("path", ""))]
opens = spec.get("opens") or []
for o in opens:
o["path"] = normalise_rotating(o.get("path", ""))
dropped = [o["path"] for o in opens if is_over_broad(o.get("path", ""))]
merged = {}
for o in opens:
if is_over_broad(o.get("path", "")):
continue
entry = merged.setdefault(o["path"], {"path": o["path"], "flags": []})
for f in o.get("flags") or []:
if f not in entry["flags"]:
entry["flags"].append(f)
kept = list(merged.values())
🤖 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/sbob-from-learned.py` around lines 116 - 120, Update the opens
processing near normalise_rotating to merge entries sharing the same normalised
path before broad-path filtering. Union each duplicate entry’s flags, matching
the existing execs deduplication approach, then derive dropped and kept from the
merged opens so each path appears once with the complete flag set.

Comment on lines +142 to +163
out = {
"apiVersion": "spdx.softwarecomposition.kubescape.io/v1beta1",
"kind": "ContainerProfile",
"metadata": {
"name": args.name,
"namespace": args.namespace,
"annotations": {"kubescape.io/managed-by": "User"},
},
"spec": {
"architectures": spec.get("architectures") or ["amd64"],
"execs": execs,
"opens": kept,
"capabilities": spec.get("capabilities") or [],
"endpoints": spec.get("endpoints") or [],
"rulePolicies": rule_policies,
},
}
if args.keep_syscalls and spec.get("syscalls"):
out["spec"]["syscalls"] = spec["syscalls"]
for k in ("matchLabels", "ingress", "egress"):
if spec.get(k) is not None:
out["spec"][k] = spec[k]

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

The converter drops imageID and imageTag, which the committed repo-server SBoB contains.

out["spec"] is built from a fixed field list, and only matchLabels, ingress and egress are copied conditionally. example/argocd/sbobs/cp-argocd-repo-server.yaml lines 11-12 carry imageID and imageTag. If that SBoB is regenerated with this script, both fields disappear, so the committed set becomes inconsistent. Decide which form is correct: copy the two fields, or remove them from the repo-server SBoB.

♻️ Proposed field passthrough
-    for k in ("matchLabels", "ingress", "egress"):
+    for k in ("matchLabels", "ingress", "egress", "imageID", "imageTag"):
         if spec.get(k) is not None:
             out["spec"][k] = spec[k]
📝 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
out = {
"apiVersion": "spdx.softwarecomposition.kubescape.io/v1beta1",
"kind": "ContainerProfile",
"metadata": {
"name": args.name,
"namespace": args.namespace,
"annotations": {"kubescape.io/managed-by": "User"},
},
"spec": {
"architectures": spec.get("architectures") or ["amd64"],
"execs": execs,
"opens": kept,
"capabilities": spec.get("capabilities") or [],
"endpoints": spec.get("endpoints") or [],
"rulePolicies": rule_policies,
},
}
if args.keep_syscalls and spec.get("syscalls"):
out["spec"]["syscalls"] = spec["syscalls"]
for k in ("matchLabels", "ingress", "egress"):
if spec.get(k) is not None:
out["spec"][k] = spec[k]
out = {
"apiVersion": "spdx.softwarecomposition.kubescape.io/v1beta1",
"kind": "ContainerProfile",
"metadata": {
"name": args.name,
"namespace": args.namespace,
"annotations": {"kubescape.io/managed-by": "User"},
},
"spec": {
"architectures": spec.get("architectures") or ["amd64"],
"execs": execs,
"opens": kept,
"capabilities": spec.get("capabilities") or [],
"endpoints": spec.get("endpoints") or [],
"rulePolicies": rule_policies,
},
}
if args.keep_syscalls and spec.get("syscalls"):
out["spec"]["syscalls"] = spec["syscalls"]
for k in ("matchLabels", "ingress", "egress", "imageID", "imageTag"):
if spec.get(k) is not None:
out["spec"][k] = spec[k]
🤖 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/sbob-from-learned.py` around lines 142 - 163, Update the output
construction in the converter to preserve the source spec’s imageID and imageTag
fields, alongside the existing conditional passthrough fields, so regenerated
repo-server SBoBs retain them. Use the existing spec values and only add each
field when present; keep the current handling of all other fields unchanged.

Comment on lines +55 to +59
# Anything else that reached the cluster step is also fine — the point is that
# loading and validating succeeded, not what happened afterwards.
if ! grep -qE 'loading (attack suite|functional tests)' <<<"$out"; then
printf ' PASS %s\n' "$file"; pass=$((pass + 1)); return
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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fail when bobctl does not reach the known cluster step.

Line 57 marks any output without the loading ... text as a pass. An early bobctl failure can therefore pass without validating a suite.

Keep the known connecting to cluster success case. Treat all other output as a failure unless it matches another explicit post-validation cluster error.

🤖 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/validate-suites.sh` around lines 55 - 59, Update the validation
result logic in scripts/validate-suites.sh so missing “loading (attack
suite|functional tests)” output is not automatically treated as a pass. Preserve
the known “connecting to cluster” success case, and classify other output as
failure unless it matches an explicitly recognized post-validation cluster
error.

@k8sstormcenter k8sstormcenter deleted a comment from coderabbitai Bot Aug 1, 2026
Accept-Encoding:
- gzip
Host:
- 127.0.0.1:6443

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.

check to replace with placeholder

Connection:
- close
Host:
- 10.42.0.250:8082

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.

check to replace with placeholder

Comment thread example/argocd/sbobs/cp-argocd-dex-server.yaml Outdated
Comment thread example/argocd/sbobs/cp-argocd-dex-server.yaml Outdated
- dns: ''
dnsNames: null
identifier: dd18fa52244005a49761004e6173ec450c0779087cedb1eaf1ae49142a6550a2
ipAddress: 10.43.0.1

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.

use PluralForm and cidr

- flags:
- O_RDONLY
- O_CLOEXEC
path: /87307a

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.

fix

- dns: ''
dnsNames: null
identifier: dd18fa52244005a49761004e6173ec450c0779087cedb1eaf1ae49142a6550a2
ipAddress: 10.43.0.1

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.

pluralform and cidr

Comment thread example/argocd/sbobs/cp-argocd-redis.yaml Outdated
- O_CLOEXEC
path: /usr/share/zoneinfo/Etc/UTC
- path: /tmp/*
flags: &id001

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.

what happened here ?

Connection:
- close
Host:
- 10.42.0.244:8084

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.

use plurals and use cidr

entlein added a commit that referenced this pull request Aug 1, 2026
… form

Addresses the review comments on #176.

NO YAML ALIASES. The pushed cp-argocd-repo-server.yaml contained &id001/*id001
because anchor_wildcards() handed all three anchored entries the SAME flags list
object and PyYAML back-references anything it sees twice by identity. Kubescape
does not resolve aliases, and a reviewer cannot see what a rule allows through a
pointer. Fixed at both levels: each entry gets its own list, and both generators
now dump through a NoAliasDumper that refuses to emit anchors at all.

PLURAL FORM + CIDR. IPAddress and DNS are DEPRECATED singulars; the v0.0.2
fields are the lists IPAddresses and DNSNames, and an IPAddresses entry may be a
literal, a CIDR, or "*" (pkg/registry/file/networkmatch, spec 5.7/5.8). Every
egress moves onto them:

  10.43.0.1  -> [10.43.0.0/16, 10.96.0.0/12]   apiserver, k3s AND kubeadm
  10.42.0.1  -> [10.42.0.0/16, 10.244.0.0/16]  pod CIDR, k3s AND flannel
  140.82.121.3/4, 185.199.108-111.153 -> dropped, keyed on dnsNames
                                         github.com / charts.helm.sh instead

The external addresses rotate, so pinning them guarantees the SBoB stops
matching. The learn captured the DNS names, so nothing is lost by keying on
them — and the representativeness gate is now STRONGER for it: repo-server must
show egress to both github.com and charts.helm.sh by name.

PLACEHOLDERS for volatile Host headers: 10.42.0.250:8082 and 127.0.0.1:6443
become *:8082 and *:6443. A pod IP differs on every restart.

TRUNCATED PATHS repaired. /4_46_21.2502219693/{ca.crt,token,namespace} is the
projected SA-token directory with its leading characters eaten by the node-agent
bug; the correct form is known because every uncorrupted profile carries it, so
it is restored rather than shipped as garbage.

COMPACT FORMAT. One entry per line via default_flow_style=None. An SBoB is read
as a policy document and block style buried the paths among their flags:
application-controller 978 -> 412 lines, repo-server 430 -> 228, dex 129 -> 64.

The gate had to learn all of this too, and it silently broke first: reading only
the singular ipAddress it failed 6 of 7 correct profiles. It now reads both
forms, satisfies an expectation by literal OR containing CIDR, and counts a
public DNS name as leaving the cluster.

Verified on the RUNNING storage:sbob-rc4 — applied and read back, the plural
ipAddresses with CIDRs persist and every profile's opens are preserved exactly.

Also pins storage to sbob-rc5s from the helm-chart PR. node-agent is HELD at
rc4 with a comment saying why: rc5s predates the fix on
k8sstormcenter/node-agent#59 for the truncation above, and its build is
currently red, so there is no fixed tag to move to yet.

Refs #170 #176

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

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

Caution

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

⚠️ Outside diff range comments (1)
scripts/sbob-from-learned.py (1)

245-247: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

"dropped" message is inaccurate when --anchor re-adds the paths.

dropped is built from bad before the --anchor branch runs. When args.anchor is set, those paths are re-anchored into kept, not actually dropped, but the stderr message still reports them as dropped. Report anchored counts separately so the diagnostic matches what the output file actually contains.

🤖 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/sbob-from-learned.py` around lines 245 - 247, The dropped diagnostic
inaccurately includes paths restored by the --anchor flow. Update the reporting
around the dropped collection and anchor handling to separate genuinely dropped
paths from paths re-added to kept, then report anchored counts separately and
ensure the dropped message only lists paths absent from the output.
🤖 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/argocd/drive-gitops-workload.sh`:
- Around line 203-205: Update the sync loop in the script to use a POST-based
Argo CD write invocation or the argocd app sync command for helm-dependency,
helm-hooks, bob-app-of-apps, and sock-shop. Do not use the read-only api()
helper for these manual syncs; preserve api() for GET/read-only endpoints.

In `@example/argocd/sbobs/cp-argocd-application-controller.yaml`:
- Around line 321-322: Update the proc path entry near the somaxconn rule in
each affected Argo CD manifest so the wildcard represents the PID segment while
retaining the literal sys segment, producing the pattern
/proc/⋯/sys/net/core/somaxconn. Apply the same correction to the corresponding
entries in the applicationset-controller, notifications-controller, redis, and
server manifests.

In `@example/argocd/sbobs/cp-argocd-dex-server.yaml`:
- Line 46: Update drive-gitops-workload.sh to perform a POST /api/v1/session
during the workload, then regenerate the Dex and Argo CD server network profiles
so authentication traffic and the TCP/5556 egress rule are recorded;
alternatively, explicitly document in the baseline file header that
authentication is excluded.

In `@example/argocd/sbobs/cp-argocd-redis.yaml`:
- Around line 54-74: Remove the ingress rules identified by the profile-specific
kubescape.io/user-defined-profile label for argocd-server and
argocd-repo-server, including the corresponding duplicate rule in the other
referenced section. Retain one stable TCP/6379 internal rule per peer using only
the app.kubernetes.io/name selector.

In `@example/argocd/sbobs/cp-argocd-repo-server.yaml`:
- Around line 172-213: Consolidate the duplicate external egress rules in the
manifest so only one rule remains for github.com and one for charts.helm.sh.
Preserve the shared TCP-443 port, external type, and null selectors, and remove
the redundant identifier-specific entries.
- Around line 157-163: Remove the per-pod identity labels
apps.kubernetes.io/pod-index and statefulset.kubernetes.io/pod-name from both
application-controller peer selectors, retaining app.kubernetes.io/name:
argocd-application-controller. Apply this to
example/argocd/sbobs/cp-argocd-repo-server.yaml#L157-L163 for TCP/8081 and
example/argocd/sbobs/cp-argocd-redis.yaml#L75-L81 for TCP/6379.

In `@example/argocd/sbobs/cp-argocd-server.yaml`:
- Around line 14-56: The profile generator’s conversion step strips leading path
segments, producing invalid truncated open paths across four profiles. Fix the
path handling in the conversion step, then regenerate all profiles: update
example/argocd/sbobs/cp-argocd-server.yaml lines 14-56 to collapse discovery
paths under the real root; restore or collapse checkout paths in
example/argocd/sbobs/cp-argocd-application-controller.yaml lines 314-348;
collapse the HTTP cache path in
example/argocd/sbobs/cp-argocd-notifications-controller.yaml lines 15-16; and
restore the full kubeconfig path in
example/argocd/sbobs/cp-argocd-dex-server.yaml lines 13-14.

In `@scripts/check-representativeness.py`:
- Around line 92-97: Update the "dns" fact construction alongside "ips" to
include both the deprecated singular egress "dns" field and the plural
"dnsNames" list, normalizing trailing dots for both sources so
egress_include_dns and egress_public_ip checks recognize either representation.

In `@scripts/portable-sbob.py`:
- Around line 51-56: Update is_cluster_ip to return true only when the parsed IP
belongs to one of the declared CLUSTER_CIDRS or POD_CIDRS ranges, rather than
relying on a.is_private; preserve false for invalid addresses and private
addresses outside those configured ranges.
- Line 1: Update fix_neighbor in scripts/portable-sbob.py to migrate a popped
singular dns value into dnsNames when dnsNames is empty, matching the existing
ipAddress-to-ipAddresses behavior. Also update profile_facts in
scripts/check-representativeness.py to include the singular dns value in
facts["dns"], matching the facts["ips"] handling of ipAddress and ipAddresses.
- Line 96: In the loop containing the combined `new.append(h); continue`
statement, split the append operation and the continue statement onto separate
lines to satisfy Ruff E702 without changing control flow.
- Around line 75-79: Update the CIDR selection in the is_cluster_ip handling to
determine pod membership against all ranges declared by POD_CIDRS, including
both k3s and kubeadm networks, rather than checking only the literal
10.42.0.0/16 range; use POD_CIDRS for matching pod IPs and retain CLUSTER_CIDRS
for the remaining cluster addresses.
- Around line 144-146: Update the yaml.dump call in the args.dry_run guard to
open the output path with a context manager, and pass the managed file handle to
yaml.dump. Preserve the existing dump options and loop behavior while ensuring
each output file is closed deterministically.
- Around line 112-114: Update the YAML loading logic in the args.files loop so a
None result from yaml.safe_load is treated as an empty mapping before accessing
d.get("spec"). Preserve the existing spec fallback and allow empty or
comment-only files to continue without raising AttributeError.
- Around line 62-73: Update the neighbor normalization around the dns and names
handling to migrate the deprecated singular dns value into the existing names
collection before the names check. Preserve existing dnsNames entries, ignore
empty values, deduplicate and normalize trailing dots, and ensure the resulting
n["dnsNames"] follows the same portable identity path instead of discarding dns.

In `@scripts/sbob-from-learned.py`:
- Around line 119-136: Update anchor_wildcards so flags are scoped per
caller-supplied anchor rather than applying the union from all dropped_paths to
every generated wildcard; accept and use an anchor-to-flags mapping while
preserving independent flag-list objects for PyYAML serialization. If exact
per-anchor attribution is unavailable, document that limitation explicitly at
the function or relevant representativeness gate instead of silently widening
permissions.

---

Outside diff comments:
In `@scripts/sbob-from-learned.py`:
- Around line 245-247: The dropped diagnostic inaccurately includes paths
restored by the --anchor flow. Update the reporting around the dropped
collection and anchor handling to separate genuinely dropped paths from paths
re-added to kept, then report anchored counts separately and ensure the dropped
message only lists paths absent from the output.
🪄 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: 5fd65303-827e-4f56-9e8b-0b28afb137ed

📥 Commits

Reviewing files that changed from the base of the PR and between 29c9fd3 and e79bbb3.

📒 Files selected for processing (13)
  • example/argocd/drive-gitops-workload.sh
  • example/argocd/sbobs/cp-argocd-application-controller.yaml
  • example/argocd/sbobs/cp-argocd-applicationset-controller.yaml
  • example/argocd/sbobs/cp-argocd-dex-server.yaml
  • example/argocd/sbobs/cp-argocd-notifications-controller.yaml
  • example/argocd/sbobs/cp-argocd-redis.yaml
  • example/argocd/sbobs/cp-argocd-repo-server.yaml
  • example/argocd/sbobs/cp-argocd-server.yaml
  • kubescape/representativeness.yaml
  • kubescape/values.yaml
  • scripts/check-representativeness.py
  • scripts/portable-sbob.py
  • scripts/sbob-from-learned.py

Comment on lines +203 to +205
for a in helm-dependency helm-hooks bob-app-of-apps sock-shop; do
api "api/v1/applications/$a/sync" >/dev/null 2>&1
done

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
git ls-files | rg '(^|/)drive-gitops-workload\.sh$|argocd'

echo "== file excerpt =="
if [ -f example/argocd/drive-gitops-workload.sh ]; then
  wc -l example/argocd/drive-gitops-workload.sh
  sed -n '1,260p' example/argocd/drive-gitops-workload.sh | nl -ba
fi

echo "== api helper usages =="
rg -n 'api\(|kubectl get --raw|kubectl create --raw|/sync' example/argocd/drive-gitops-workload.sh 2>/dev/null || true

Repository: k8sstormcenter/bob

Length of output: 1473


🌐 Web query:

Argo CD API POST /api/v1/applications/{name}/sync GET behavior

💡 Result:

The Argo CD API endpoint POST /api/v1/applications/{name}/sync is used to trigger a synchronization of a specified application, reconciling its live state in Kubernetes with the desired state defined in its Git source [1][2][3]. Key technical details regarding this endpoint include: Request Structure: It accepts an ApplicationSyncRequest object in the request body [1][4]. This request allows for significant customization of the sync operation, including specifying a target revision, enabling dry-run mode, choosing whether to prune resources, setting sync strategies (e.g., apply or hook-based), and selecting specific resources to sync [1][4][3]. Functionality: - Reconciliation: The endpoint initiates the process of applying the Git-declared state to the cluster [1][3]. - Parameters: Users can pass detailed configuration options, such as sync options (e.g., ServerSideApply), retry strategies, and namespace/project context [1][3]. - Response: Upon success, it typically returns the updated Application object [1][4][2]. Authentication: Like all Argo CD API operations, requests to this endpoint must be authenticated using a bearer token obtained via the session endpoint (e.g., /api/v1/session) and passed in the Authorization header [5][6][2]. Behavioral Considerations: - Error Handling: Requests may return a 404 error if the application is not found, or a 403 error if the user lacks authorization for the specified project [5][6][7]. - Sync Hooks: While the API is designed to trigger standard sync behavior, users have occasionally reported discrepancies compared to the Web UI in how specific resource hooks (e.g., Jobs) are executed [8]. This is generally managed by the underlying controller settings that process the sync request [9]. Documentation Access: Official documentation can be accessed by navigating to the /swagger-ui path on your running Argo CD instance [10][7].

Citations:


🌐 Web query:

kubectl create --raw POST Kubernetes API operation example

💡 Result:

The kubectl create --raw command allows you to perform an HTTP POST request directly to a specified Kubernetes API endpoint using the authentication and transport configuration defined in your kubeconfig file [1][2]. When you use the --raw flag with kubectl create, the command takes the raw URI provided as an argument and POSTs the content from the file specified by the -f flag (or stdin) directly to that endpoint [1][3][2]. Example usage: kubectl create --raw /api/v1/namespaces/default/pods -f pod.yaml In this example: 1. /api/v1/namespaces/default/pods is the API resource endpoint to which you are sending the POST request [3]. 2. -f pod.yaml provides the resource manifest data that will be sent as the body of the request [3][2]. This feature is particularly useful for interacting with Kubernetes subresources or API endpoints that may not be fully supported by higher-level kubectl commands, all while leveraging your existing cluster authentication [3][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== tool availability =="
for cmd in sed awk grep find wc kubectl; do
  if command -v "$cmd" >/dev/null 2>&1; then
    echo "$cmd available"
  else
    echo "$cmd missing"
  fi
done

echo "== script lines 1-245 =="
sed -n '1,245p' example/argocd/drive-gitops-workload.sh > /tmp/workload_excerpt.txt
awk '{printf "%4d\t%s\n", NR, $0}' /tmp/workload_excerpt.txt

echo "== grep within script for relevant terms =="
grep -nE 'api\(|post\(|kubectl (get|create|exec)|sync|get --raw|create --raw|APPLICATION_SYNC|sync_path|ALL_APPS|header comment|Line 23' example/argocd/drive-gitops-workload.sh || true

echo "== read-only shell probe of command behavior if kubectl available =="
if command -v kubectl >/dev/null 2>&1; then
  echo "kubectl version:"
  kubectl version --client --short 2>/tmp/kubectl_client_error || kubectl version --client -o json 2>/tmp/kubectl_client_error || true
  echo "check kubectl get --raw has -X GET by parsing args in --help text if present:"
  kubectl get --help 2>/tmp/kubectl_get_help.txt | grep -Ei 'get raw|raw|http' || true
else
  echo "kubectl unavailable; cannot run behavioral probe"
fi

Repository: k8sstormcenter/bob

Length of output: 14329


🌐 Web query:

kubectl get --raw /api/v1/namespaces default services type clusterIP proxy POST

💡 Result:

The command you are asking about, kubectl get --raw /api/v1/namespaces/default/services//proxy, interacts with the Kubernetes API server's built-in proxy sub-resource for Services [1][2]. Here is a breakdown of why this command structure is used and its limitations: 1. The --raw flag: The kubectl get --raw command sends a direct, unformatted HTTP request to the Kubernetes API server using your current kubeconfig credentials [3][4]. It bypasses standard kubectl output formatting, returning the raw response body from the API [3][5]. 2. The proxy sub-resource: In Kubernetes, proxy is not an HTTP verb but a sub-resource available on services, pods, and nodes [2]. It allows you to forward traffic from the API server to a specific service endpoint within the cluster [1][6]. 3. HTTP Methods (POST vs. GET): While you specified POST in your query, the proxy sub-resource officially supports multiple HTTP methods, including GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS [7]. However, when using kubectl get --raw, the command defaults to an HTTP GET request [3][4]. If you intend to perform an operation other than GET (such as a POST), kubectl get --raw may not be the appropriate tool because it is explicitly designed for raw GET requests [8][4]. 4. Important Usage Notes: - Trailing Slashes: The API server's proxy endpoint is sensitive to paths. It often requires a trailing slash to function correctly [9]. - Authentication: When using kubectl get --raw, authentication is handled automatically by kubectl using your kubeconfig [3][4]. This is distinct from using kubectl proxy, which starts a local HTTP server on your machine that performs the proxying locally [10][3]. - Authorization: Accessing this endpoint requires authorization permissions for the proxy verb on the service resource [2][6]. If you need to perform a POST request to a proxied service, you should typically use a tool like curl alongside the token or credentials from your kubeconfig, or utilize kubectl proxy to open a local port and then make the POST request to localhost [10][3].

Citations:


🌐 Web query:

site:kubernetes.io kubectl get --raw POST

💡 Result:

The --raw flag in kubectl is a parameter used to send a raw URI request directly to the Kubernetes API server [1][2][3]. While the flag is commonly associated with retrieving data (such as querying specific API paths), documentation explicitly defines --raw as the "Raw URI to POST to the server" [2][3]. This means that when you use the --raw flag, the HTTP method used to send the request to the server is POST [2][3]. This functionality is useful for interacting directly with API endpoints that require a POST request, such as those that trigger specific actions, start processes, or perform operations not covered by standard kubectl subcommands. To use it, you provide the URI path as the argument to the flag: kubectl get --raw /api/v1/namespaces/default/pods/my-pod/exec In this context, although the command starts with kubectl get, the --raw flag overrides the default behavior of performing an HTTP GET, instead sending a POST request to the specified path using the transport configuration from your kubeconfig file [1][2].

Citations:


Manual sync calls are GETs, not the Argo CD write path.

api() uses kubectl get --raw, which sends a GET to argocd-server/proxy/...; Argo CD starts a sync with POST /api/v1/applications/{name}/sync. The loop at lines 203-205 does not exercise manual sync, so MANUAL apps such as helm-dependency, helm-hooks, bob-app-of-apps, and sock-shop never start through this script. Use a POST-based write invocation or argocd app sync, and keep api() for read-only 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/argocd/drive-gitops-workload.sh` around lines 203 - 205, Update the
sync loop in the script to use a POST-based Argo CD write invocation or the
argocd app sync command for helm-dependency, helm-hooks, bob-app-of-apps, and
sock-shop. Do not use the read-only api() helper for these manual syncs;
preserve api() for GET/read-only endpoints.

Comment on lines +321 to +322
- flags: [O_CLOEXEC, O_RDONLY]
path: /proc/⋯/net/core/somaxconn

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 | ⚡ Quick win

The wildcard collapses the wrong /proc segment.

The real read is /proc/sys/net/core/somaxconn. The generator replaced sys with , so the entry allows /proc/<anything>/net/core/somaxconn. Restrict the literal segment to sys and reserve for the PID segment.

🔧 Proposed fix
   - flags: [O_CLOEXEC, O_RDONLY]
-    path: /proc/⋯/net/core/somaxconn
+    path: /proc/sys/net/core/somaxconn

The same entry exists in cp-argocd-applicationset-controller.yaml Line 40, cp-argocd-notifications-controller.yaml Line 40, cp-argocd-redis.yaml Line 30, and cp-argocd-server.yaml Line 30.

📝 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
- flags: [O_CLOEXEC, O_RDONLY]
path: /proc//net/core/somaxconn
- flags: [O_CLOEXEC, O_RDONLY]
path: /proc/sys/net/core/somaxconn
🤖 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/argocd/sbobs/cp-argocd-application-controller.yaml` around lines 321
- 322, Update the proc path entry near the somaxconn rule in each affected Argo
CD manifest so the wildcard represents the PID segment while retaining the
literal sys segment, producing the pattern /proc/⋯/sys/net/core/somaxconn. Apply
the same correction to the corresponding entries in the
applicationset-controller, notifications-controller, redis, and server
manifests.

- flags: [O_CLOEXEC, O_RDONLY]
path: /usr/share/mime/globs2
capabilities: [CAP_DAC_OVERRIDE, CAP_DAC_READ_SEARCH]
endpoints: []

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

The Dex baseline records no inbound traffic and no Argo CD server egress pairs with it.

endpoints is empty, the profile has no ingress section, and cp-argocd-server.yaml Lines 75-106 no longer contain a TCP/5556 egress rule to Dex. Together these show that no login flow ran during the learn window. example/argocd/drive-gitops-workload.sh never calls POST /api/v1/session. An install that uses SSO or argocd login will therefore alert on the first authentication.

Add a session login to the driver and regenerate, or state in the file header that this baseline excludes authentication.

Also applies to: 55-64

🤖 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/argocd/sbobs/cp-argocd-dex-server.yaml` at line 46, Update
drive-gitops-workload.sh to perform a POST /api/v1/session during the workload,
then regenerate the Dex and Argo CD server network profiles so authentication
traffic and the TCP/5556 egress rule are recorded; alternatively, explicitly
document in the baseline file header that authentication is excluded.

Comment thread example/argocd/sbobs/cp-argocd-redis.yaml Outdated
Comment thread example/argocd/sbobs/cp-argocd-repo-server.yaml
Comment thread scripts/portable-sbob.py
Comment on lines +75 to +79
if ip:
if is_cluster_ip(ip):
cidrs = POD_CIDRS if ipaddress.ip_address(ip) in ipaddress.ip_network("10.42.0.0/16") else CLUSTER_CIDRS
n["ipAddresses"] = list(cidrs)
changed.append(f"{ip} -> {cidrs}")

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

Pod-CIDR check only tests one of the two declared POD_CIDRS ranges.

POD_CIDRS declares both 10.42.0.0/16 (k3s) and 10.244.0.0/16 (kubeadm), but this branch tests membership only against the literal "10.42.0.0/16". A kubeadm pod IP (10.244.x.x) is private, so is_cluster_ip returns True, but it fails the 10.42.0.0/16 test and falls into CLUSTER_CIDRS instead of POD_CIDRS. This contradicts the file's own stated goal of supporting "k3s and kubeadm alike".

🛡️ Proposed fix
-            cidrs = POD_CIDRS if ipaddress.ip_address(ip) in ipaddress.ip_network("10.42.0.0/16") else CLUSTER_CIDRS
+            addr = ipaddress.ip_address(ip)
+            in_pod_range = any(addr in ipaddress.ip_network(c) for c in POD_CIDRS)
+            cidrs = POD_CIDRS if in_pod_range else CLUSTER_CIDRS
📝 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
if ip:
if is_cluster_ip(ip):
cidrs = POD_CIDRS if ipaddress.ip_address(ip) in ipaddress.ip_network("10.42.0.0/16") else CLUSTER_CIDRS
n["ipAddresses"] = list(cidrs)
changed.append(f"{ip} -> {cidrs}")
if ip:
if is_cluster_ip(ip):
addr = ipaddress.ip_address(ip)
in_pod_range = any(addr in ipaddress.ip_network(c) for c in POD_CIDRS)
cidrs = POD_CIDRS if in_pod_range else CLUSTER_CIDRS
n["ipAddresses"] = list(cidrs)
changed.append(f"{ip} -> {cidrs}")
🤖 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/portable-sbob.py` around lines 75 - 79, Update the CIDR selection in
the is_cluster_ip handling to determine pod membership against all ranges
declared by POD_CIDRS, including both k3s and kubeadm networks, rather than
checking only the literal 10.42.0.0/16 range; use POD_CIDRS for matching pod IPs
and retain CLUSTER_CIDRS for the remaining cluster addresses.

Comment thread scripts/portable-sbob.py
try:
ipaddress.ip_address(host)
except ValueError:
new.append(h); continue

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 | ⚡ Quick win

Split the combined statement.

new.append(h); continue combines two statements on one line, flagged by Ruff (E702). Split for lint compliance.

♻️ Proposed fix
-            new.append(h); continue
+            new.append(h)
+            continue
📝 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
new.append(h); continue
new.append(h)
continue
🧰 Tools
🪛 Ruff (0.16.0)

[error] 96-96: Multiple statements on one line (semicolon)

(E702)

🤖 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/portable-sbob.py` at line 96, In the loop containing the combined
`new.append(h); continue` statement, split the append operation and the continue
statement onto separate lines to satisfy Ruff E702 without changing control
flow.

Source: Linters/SAST tools

Comment thread scripts/portable-sbob.py
Comment on lines +112 to +114
for f in args.files:
d = yaml.safe_load(Path(f).read_text())
s = d.get("spec") or {}

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

Guard against None from an empty or comment-only YAML file.

yaml.safe_load returns None for an empty or comment-only file. d.get("spec") at line 114 then raises AttributeError, aborting the whole batch over one bad file among args.files.

🛡️ Proposed fix
-        d = yaml.safe_load(Path(f).read_text())
+        d = yaml.safe_load(Path(f).read_text()) or {}
📝 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
for f in args.files:
d = yaml.safe_load(Path(f).read_text())
s = d.get("spec") or {}
for f in args.files:
d = yaml.safe_load(Path(f).read_text()) or {}
s = d.get("spec") or {}
🤖 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/portable-sbob.py` around lines 112 - 114, Update the YAML loading
logic in the args.files loop so a None result from yaml.safe_load is treated as
an empty mapping before accessing d.get("spec"). Preserve the existing spec
fallback and allow empty or comment-only files to continue without raising
AttributeError.

Comment thread scripts/portable-sbob.py
Comment on lines +144 to +146
if not args.dry_run:
yaml.dump(d, open(f, "w"), sort_keys=False, width=4096, allow_unicode=True,
default_flow_style=None, Dumper=NoAliasDumper)

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 | 🔵 Trivial | ⚡ Quick win

Use a context manager for the output file.

open(f, "w") is never explicitly closed; Ruff flags this (SIM115). Use a with block so the handle is closed deterministically, especially since this runs in a loop over args.files.

♻️ Proposed fix
-            yaml.dump(d, open(f, "w"), sort_keys=False, width=4096, allow_unicode=True,
-                   default_flow_style=None, Dumper=NoAliasDumper)
+            with open(f, "w") as fp:
+                yaml.dump(d, fp, sort_keys=False, width=4096, allow_unicode=True,
+                          default_flow_style=None, Dumper=NoAliasDumper)
📝 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
if not args.dry_run:
yaml.dump(d, open(f, "w"), sort_keys=False, width=4096, allow_unicode=True,
default_flow_style=None, Dumper=NoAliasDumper)
if not args.dry_run:
with open(f, "w") as fp:
yaml.dump(d, fp, sort_keys=False, width=4096, allow_unicode=True,
default_flow_style=None, Dumper=NoAliasDumper)
🧰 Tools
🪛 ast-grep (0.45.0)

[warning] 144-144: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(f, "w")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)

🪛 Ruff (0.16.0)

[warning] 145-145: Use a context manager for opening files

(SIM115)

🤖 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/portable-sbob.py` around lines 144 - 146, Update the yaml.dump call
in the args.dry_run guard to open the output path with a context manager, and
pass the managed file handle to yaml.dump. Preserve the existing dump options
and loop behavior while ensuring each output file is closed deterministically.

Source: Linters/SAST tools

Comment on lines +119 to +136
def anchor_wildcards(dropped_paths, anchors):
"""Turn unanchored paths into anchored patterns on the workload's real roots.

Dropping them is safe but expensive: every legitimate read underneath then
fires R0002. The prefix cannot be recovered per-path — the collapse already
destroyed it — so the caller supplies the roots the workload is known to
write to (for argocd-repo-server these are the emptyDir volumeMounts in the
Deployment: /tmp, /helm-working-dir, /app/config).

An anchored `/tmp/*` keeps the same breadth under /tmp as the broken
`/*/Chart.yaml` had, but its first segment is literal, so it cannot swallow
the rest of the profile on save and it still does not match /etc/shadow.
"""
flags = sorted({f for o in dropped_paths for f in (o.get("flags") or [])})
# list(flags) per entry, NOT the shared object: PyYAML emits &id001/*id001
# anchors for any value it sees twice by identity, which turns a policy file
# into something nobody can read at review time.
return [{"path": a.rstrip("/") + "/*", "flags": list(flags)} for a in anchors]

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 | ⚡ Quick win

anchor_wildcards grants the union of all dropped flags to every anchor.

flags is computed once as the union across every entry in dropped_paths, then applied identically to every anchor in anchors. If one dropped path only had read flags and another had write flags, every anchor — including ones where only reads were observed — receives both. This widens the profile's permissions beyond what was actually learned, which is the exact failure mode is_over_broad and the representativeness gate exist to prevent.

Since the original per-path prefix is unrecoverable after the wildcard collapse, this cannot be perfectly precise, but granting an identical, maximal flag set to every caller-supplied root is avoidable. At minimum, document this as a known limitation the way other gates in this codebase do (e.g., the "HONEST LIMITATION" notes in kubescape/representativeness.yaml), or scope flags per-anchor via a caller-supplied mapping instead of one global union.

🛡️ Proposed fix: accept a mapping instead of a single flag set for every anchor
-def anchor_wildcards(dropped_paths, anchors):
+def anchor_wildcards(dropped_paths, anchor_flags):
+    """anchor_flags: {anchor_prefix: [flags]}, computed by the caller from the
+    subset of dropped_paths plausibly relevant to that root."""
-    flags = sorted({f for o in dropped_paths for f in (o.get("flags") or [])})
-    return [{"path": a.rstrip("/") + "/*", "flags": list(flags)} for a in anchors]
+    return [{"path": a.rstrip("/") + "/*", "flags": sorted(set(fl))}
+            for a, fl in anchor_flags.items()]
🤖 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/sbob-from-learned.py` around lines 119 - 136, Update anchor_wildcards
so flags are scoped per caller-supplied anchor rather than applying the union
from all dropped_paths to every generated wildcard; accept and use an
anchor-to-flags mapping while preserving independent flag-list objects for
PyYAML serialization. If exact per-anchor attribution is unavailable, document
that limitation explicitly at the function or relevant representativeness gate
instead of silently widening permissions.

entlein added a commit that referenced this pull request Aug 1, 2026
…fixed

Re-learned all seven components on the fixed image with a 15m window and the
wide GitOps driver, then verified against both the PR's acceptance criterion and
the separate corruption cases found here.

The PR criterion PASSES: zero headless /<digits>/... proc paths.

The fix is BROADER than the note described, which is worth recording because the
note only claimed /proc re-rooting:

  jsonnet/kustomize name fragments   /sonnet-guestbook, /ustomize-guestbook -> 0
  bare /git/HEAD (lost checkout)     11 paths -> 0
  top-level .crt fragments           38 -> 0
  /usr/share/ca-certificates paths   0 intact -> 137 intact

ONE CASE SURVIVES, and it is now isolated: argocd-dex-server still truncates the
projected SA-token directory to /6_22_16.2999670045/{ca.crt,namespace,token}.
Every other component emits the correct /run/secrets/... form, and dex emits
BOTH, which suggests a specific code path rather than a general failure.
portable-sbob.py repairs it, but it should be reported upstream rather than
papered over here.

Also probably truncated, unconfirmed: argocd-redis logs /dump.rdb where the
image's WORKDIR is /data, so the real path is likely /data/dump.rdb.

A NEW consequence of the wider workload, which is the interesting part: the
application-controller profile collapsed to /* on its own. 419 of 423 learned
opens were wildcard-rooted, because the kube discovery cache writes a fresh
atomic-write temp file per refresh and 33 driver rounds produced 344 of them
under one directory — enough churn to cascade the trie collapse to the root.
More representative load therefore makes collapse MORE likely, not less. It is
anchored onto /home/argocd, /tmp and the serviceaccount dir.

opens_min is dropped for application-controller for the same reason it was for
repo-server, and the reason is now demonstrated twice: the correctly anchored
profile has 6 opens while a short unrepresentative run leaves dozens of
literals. Any count-based check rewards the worse profile. The behavioural
assertions (which peers, which binaries) are what separate them.

Verified: all seven pass the gate, no YAML aliases, no cluster-specific
literals, and applying to the live cluster on storage:sbob-rc5s preserves every
profile's opens exactly. Demo mode restored, driver workload torn down.

Refs #170 #176

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jd9m962b1JAHdAy3rGe7nm
entlein added a commit that referenced this pull request Aug 1, 2026
…on it

The argocd-application-controller profile was being destroyed before anyone
could look at it: 427 of 430 learned opens collapsed to /*, so the shipped SBoB
was three broad anchored roots standing in for everything the workload actually
touches. Driving MORE load made it worse (419/423 -> 427/430), because the kube
discovery cache writes one atomic-write temp file per refresh and a longer,
more representative window puts more children under one node until the collapse
propagates to the root.

kubescape/collapse-config.yaml sets threshold 1 on the three churn directories,
so node-agent collapses THERE and never cascades:

  /home/argocd/.kube/cache    discovery cache, 344 temp files in one run
  /tmp/_argocd-repo           repo-server git checkouts, one dir per UUID
  /helm-working-dir           helm cache (HELM_CACHE_HOME)

All five compiled-in defaults are repeated verbatim. CollapseConfigs REPLACES
the defaults wholesale rather than merging, so omitting them would have
silently changed collapse behaviour for every other app in the cluster.

The middle prefix is deliberately /tmp/_argocd-repo and not /tmp. This CR is
cluster-scoped; threshold 1 on bare /tmp would collapse every workload's /tmp
to /tmp/*, weakening the redis, postgres and webapp SBoBs to fix an Argo CD
problem. The narrower prefix is evidence-backed — learned exec args showed
`kustomize build /tmp/_argocd-repo/194e7baf-.../kustomize-guestbook`.

Result, re-learned with the driver and the full functional suite:

  application-controller  430 opens / 427 cascaded  ->  40 opens / 0 cascaded

and the 40 are real literals — /etc/passwd, /etc/resolv.conf, /proc/⋯/stat,
/usr/local/bin/argocd, libc, the SA token, the git refs and tags — plus a
single /home/argocd/.kube/cache/* that node-agent emitted itself. No post-hoc
anchoring needed for it any more. Predicted by the analyzer beforehand
(368 -> 9, 8/8 literals preserved) and confirmed live.

repo-server still cascades and this is the honest limit of the approach: it has
/*, /*/* and /⋯ and ZERO paths carrying the /tmp/_argocd-repo prefix, because
node-agent truncates checkout paths before storage sees them. A prefix rule
cannot match a path whose prefix is already gone, so the fragments land at the
root and pile up. Truncation defeats CollapseConfig. repo-server therefore
still needs --anchor.

Correcting an earlier claim in this branch: sbob-rc5s-fpo did NOT fix all
truncation. It fixed the /proc class and the cert fragments, and the SA-token
directory now arrives in full. But checkout paths still arrive as /git/HEAD,
/git/refs/tags/... where the truth is /tmp/_argocd-repo/<uuid>/.git/... . That
was reported as fixed only because application-controller was 419/423 collapsed
at the time, so its /git entries were hidden inside the wildcard rather than
absent. Uncollapsing it revealed them.

Verified: no YAML aliases, no cluster-specific literals, rotating SA directory
normalised to ⋯, gate passes all seven, and applying to the live cluster on
storage:sbob-rc5s preserves every profile's opens exactly. Demo mode restored,
workload torn down, node-agent restart count still 0.

Refs #170 #176

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

@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 (13)
Makefile (1)

374-381: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Apply kubescape/collapse-config.yaml during installation.

kubescape/collapse-config.yaml defines the CollapseConfiguration thresholds, but the Makefile install target only applies kubescape/default-rules.yaml. Store configuration may keep the upstream defaults, so apply this manifest after the storage API/CRDs are available.

🤖 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 374 - 381, Update the kubescape installation flow in
the kubescape target to apply kubescape/collapse-config.yaml after helm upgrade
completes, ensuring the storage API and CRDs are available before applying the
CollapseConfiguration manifest. Preserve the existing chart installation and
flags.

Source: MCP tools

example/argocd/sbobs/cp-argocd-notifications-controller.yaml (2)

65-71: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Remove the spurious :6443/metrics inbound entry (still unresolved).

Same copy-attribution defect flagged across four profiles: no Argo CD component serves port 6443. This component's real metrics port, 9001, is already recorded in ingress.

🤖 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/argocd/sbobs/cp-argocd-notifications-controller.yaml` around lines 65
- 71, Remove the inbound `:6443/metrics` entry from the Argo CD notifications
controller profile, leaving the existing `ingress` configuration for the
component’s actual metrics port 9001 unchanged.

39-40: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Restrict the /proc wildcard to the PID segment (still unresolved).

Same issue as the sibling profiles: /proc/⋯/net/core/somaxconn should be /proc/sys/net/core/somaxconn.

🤖 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/argocd/sbobs/cp-argocd-notifications-controller.yaml` around lines 39
- 40, Update the proc path in the relevant profile entry so the wildcard is
removed and it points exactly to /proc/sys/net/core/somaxconn, while preserving
the existing flags and access configuration.
example/argocd/sbobs/cp-argocd-applicationset-controller.yaml (2)

39-40: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Restrict the /proc wildcard to the PID segment (still unresolved).

Same issue as cp-argocd-application-controller.yaml: /proc/⋯/net/core/somaxconn should be /proc/sys/net/core/somaxconn, since sys is a literal path segment, not a per-process value.

🤖 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/argocd/sbobs/cp-argocd-applicationset-controller.yaml` around lines
39 - 40, Update the proc path associated with the somaxconn entry to use the
literal /proc/sys/net/core/somaxconn path, replacing the wildcard segment while
preserving the existing flags and access configuration.

65-71: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Remove the spurious :6443/metrics inbound entry (still unresolved).

This is the same byte-identical :6443/metrics entry flagged as a copy-attribution defect across four profiles. This component's ingress only records TCP/8080, matching its real metrics port. Remove the 6443 entry.

🤖 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/argocd/sbobs/cp-argocd-applicationset-controller.yaml` around lines
65 - 71, Remove the inbound `:6443/metrics` entry from the Argo CD
applicationset controller configuration, including its headers, internal flag,
and GET method; retain the existing TCP/8080 metrics ingress unchanged.
example/argocd/sbobs/cp-argocd-redis.yaml (2)

27-28: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Restrict the /proc wildcard to the PID segment (still unresolved).

Same issue as the other profiles: /proc/⋯/net/core/somaxconn should be /proc/sys/net/core/somaxconn.

🤖 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/argocd/sbobs/cp-argocd-redis.yaml` around lines 27 - 28, Update the
proc path in the relevant profile configuration to use the fixed
`/proc/sys/net/core/somaxconn` path instead of a wildcard PID segment, while
preserving the existing flags.

68-74: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

The peer selector is keyed to StatefulSet pod ordinal 0 (still unresolved).

apps.kubernetes.io/pod-index: '0' and statefulset.kubernetes.io/pod-name: argocd-application-controller-0 identify one pod, not the workload. A cluster running more than one argocd-application-controller replica sends traffic from argocd-application-controller-1, which matches neither this rule nor any other, and the connection appears unexpected. Keep only app.kubernetes.io/name: argocd-application-controller.

🔧 Proposed fix
   - identifier: d93b79fbec160a0515c1632b70a626152653f142c3a311111243c31b0b0d1ce7
     namespaceSelector: null
     podSelector:
-      matchLabels: {app.kubernetes.io/name: argocd-application-controller, apps.kubernetes.io/pod-index: '0', statefulset.kubernetes.io/pod-name: argocd-application-controller-0}
+      matchLabels: {app.kubernetes.io/name: argocd-application-controller}
     ports:
     - {name: TCP-6379, port: 6379, protocol: TCP}
     type: internal
🤖 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/argocd/sbobs/cp-argocd-redis.yaml` around lines 68 - 74, Update the
peer selector for the argocd-application-controller rule by removing the
pod-specific labels apps.kubernetes.io/pod-index and
statefulset.kubernetes.io/pod-name, while retaining only app.kubernetes.io/name:
argocd-application-controller so all replicas match.
example/argocd/sbobs/cp-argocd-application-controller.yaml (2)

104-110: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Remove the spurious :6443/metrics inbound entry (still unresolved).

Port 6443 is the kube-apiserver port; no Argo CD component serves it. This entry was already flagged as a copy-attribution defect affecting four profiles, and it is still present here with no corresponding ingress rule (only :8082/healthz and TCP-8082 ingress exist). Remove it and fix the observation-attribution pipeline so it does not recur.

🤖 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/argocd/sbobs/cp-argocd-application-controller.yaml` around lines 104
- 110, Remove the inbound :6443/metrics entry from the Argo CD
application-controller configuration, and update the observation-attribution
pipeline that generates these profiles so this kube-apiserver port is not
reintroduced. Preserve the valid :8082/healthz and TCP-8082 ingress rules.

71-72: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Restrict the /proc wildcard to the PID segment (still unresolved).

The real read is /proc/sys/net/core/somaxconn. sys is a literal segment, not a per-process value, so wildcarding it as allows /proc/<anything>/net/core/somaxconn. This was already flagged and is not fixed here.

🔧 Proposed fix
   - flags: [O_CLOEXEC, O_RDONLY]
-    path: /proc/⋯/net/core/somaxconn
+    path: /proc/sys/net/core/somaxconn
🤖 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/argocd/sbobs/cp-argocd-application-controller.yaml` around lines 71 -
72, Update the proc path rule in the Argo CD application controller
configuration so the wildcard matches only the PID segment, while retaining the
literal sys segment and the existing /net/core/somaxconn suffix. Ensure it
permits /proc/<pid>/net/core/somaxconn without allowing arbitrary intermediate
path segments.
example/argocd/sbobs/cp-argocd-dex-server.yaml (1)

13-14: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Restore the full kubeconfig path (still unresolved).

/.kube/config is a truncated path. Sibling profiles record the full path, /home/argocd/.kube/config (see cp-argocd-application-controller.yaml and cp-argocd-server.yaml). This entry matches no real open and the corresponding read raises an unexpected-open alert. This is the same prefix-stripping generator defect flagged previously for this exact site; the other three affected sites in this revision (cp-argocd-server.yaml, cp-argocd-notifications-controller.yaml, cp-argocd-application-controller.yaml) were fixed, but this one was not.

🔧 Proposed fix
   - flags: [O_CLOEXEC, O_RDONLY]
-    path: /.kube/config
+    path: /home/argocd/.kube/config
🤖 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/argocd/sbobs/cp-argocd-dex-server.yaml` around lines 13 - 14, Restore
the path in the kubeconfig entry under cp-argocd-dex-server to
/home/argocd/.kube/config, matching the sibling Argo CD profiles and preserving
the existing flags.
example/argocd/sbobs/cp-argocd-repo-server.yaml (2)

136-142: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Remove the spurious :6443/metrics inbound entry (still unresolved).

Same copy-attribution defect flagged across four profiles. Keep :8084/healthz, which matches the recorded TCP-8084 ingress rule; port 6443 is the kube-apiserver port and no Argo CD component serves it.

🤖 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/argocd/sbobs/cp-argocd-repo-server.yaml` around lines 136 - 142,
Remove the inbound endpoint entry for :6443/metrics from the Argo CD repository
server configuration, including its associated headers, internal flag, and GET
method. Preserve the :8084/healthz inbound entry and its existing settings.

179-228: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Six egress rules describe two destinations (still unresolved).

charts.helm.sh appears four times (lines 179, 186, 193, 200) and github.com twice (lines 215, 222). The rules differ only by identifier; each learned connection produced its own entry instead of being merged by destination. Collapse them into one rule per dnsNames value to keep the profile readable and stable across regeneration.

🤖 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/argocd/sbobs/cp-argocd-repo-server.yaml` around lines 179 - 228,
Collapse the duplicate external egress rules in the profile so charts.helm.sh
has one TCP-443 rule and github.com has one TCP-443 rule. Remove the redundant
identifier-specific entries while preserving the existing kube-dns internal rule
and destination, port, protocol, and type settings.
example/argocd/sbobs/cp-argocd-server.yaml (1)

75-106: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

The Dex egress rule was removed without a replacement (still unresolved).

This egress list previously included a rule to Argo CD Dex over TCP/5556; it is gone in this revision, replaced by rules to the Kubernetes API server, Redis, kube-dns, and repo-server only. cp-argocd-dex-server.yaml correspondingly records no inbound traffic. Together this confirms the learn window never exercised the SSO/argocd login flow. A deployment using SSO alerts on its first authentication attempt against Dex.

Add a session login step to the workload driver and regenerate both profiles, or document that this baseline excludes authentication.

🤖 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/argocd/sbobs/cp-argocd-server.yaml` around lines 75 - 106, The
profile lacks the Argo CD Dex TCP/5556 egress required for SSO authentication.
Add a session login step to the workload driver that exercises the Dex-backed
argocd login flow, then regenerate both argocd-server and dex-server profiles so
the corresponding egress and ingress rules are captured; alternatively,
explicitly document that this baseline excludes authentication.
♻️ Duplicate comments (2)
example/argocd/sbobs/cp-argocd-repo-server.yaml (1)

157-163: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

The peer selector is keyed to StatefulSet pod ordinal 0 (still unresolved).

apps.kubernetes.io/pod-index: '0' and statefulset.kubernetes.io/pod-name: argocd-application-controller-0 are per-pod identity labels. A cluster running more than one argocd-application-controller replica sends traffic from argocd-application-controller-1, which matches neither this rule nor any other, and the connection appears unexpected. Keep only app.kubernetes.io/name: argocd-application-controller.

🔧 Proposed fix
   - identifier: d93b79fbec160a0515c1632b70a626152653f142c3a311111243c31b0b0d1ce7
     namespaceSelector: null
     podSelector:
-      matchLabels: {app.kubernetes.io/name: argocd-application-controller, apps.kubernetes.io/pod-index: '0', statefulset.kubernetes.io/pod-name: argocd-application-controller-0}
+      matchLabels: {app.kubernetes.io/name: argocd-application-controller}
     ports:
     - {name: TCP-8081, port: 8081, protocol: TCP}
     type: internal
🤖 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/argocd/sbobs/cp-argocd-repo-server.yaml` around lines 157 - 163,
Update the peer selector for the argocd-application-controller rule by removing
the ordinal-specific labels apps.kubernetes.io/pod-index and
statefulset.kubernetes.io/pod-name, while retaining only app.kubernetes.io/name:
argocd-application-controller.
example/argocd/sbobs/cp-argocd-dex-server.yaml (1)

49-68: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

No login flow was captured; the paired server-side egress rule was also removed (still unresolved).

endpoints is empty, there is no ingress key in this file, and cp-argocd-server.yaml no longer contains a TCP/5556 egress rule to Dex (it was present before this revision and is now removed per the reported line-range changes). Together these confirm the learn window never exercised POST /api/v1/session. A deployment using SSO or argocd login alerts on its first authentication attempt.

Add a session login step to the workload driver and regenerate both profiles, or document that this baseline excludes authentication.

🤖 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/argocd/sbobs/cp-argocd-dex-server.yaml` around lines 49 - 68, Update
the workload driver to exercise the Dex session login flow, including POST
/api/v1/session, then regenerate both cp-argocd-dex-server and cp-argocd-server
profiles so the required ingress and paired TCP/5556 egress rules are captured.
If authentication is intentionally excluded, document that baseline decision
instead of modifying the profiles.
🤖 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/argocd/sbobs/cp-argocd-application-controller.yaml`:
- Around line 40-62: Replace the enumerated branch and tag entries in the
profile with dynamic wildcard path segments under /git/refs/remotes/origin/ and
/git/refs/tags/. Preserve the existing O_CLOEXEC and O_RDONLY flags and keep the
patterns scoped to those Git ref directories so unrelated /git paths remain
denied.

---

Outside diff comments:
In `@example/argocd/sbobs/cp-argocd-application-controller.yaml`:
- Around line 104-110: Remove the inbound :6443/metrics entry from the Argo CD
application-controller configuration, and update the observation-attribution
pipeline that generates these profiles so this kube-apiserver port is not
reintroduced. Preserve the valid :8082/healthz and TCP-8082 ingress rules.
- Around line 71-72: Update the proc path rule in the Argo CD application
controller configuration so the wildcard matches only the PID segment, while
retaining the literal sys segment and the existing /net/core/somaxconn suffix.
Ensure it permits /proc/<pid>/net/core/somaxconn without allowing arbitrary
intermediate path segments.

In `@example/argocd/sbobs/cp-argocd-applicationset-controller.yaml`:
- Around line 39-40: Update the proc path associated with the somaxconn entry to
use the literal /proc/sys/net/core/somaxconn path, replacing the wildcard
segment while preserving the existing flags and access configuration.
- Around line 65-71: Remove the inbound `:6443/metrics` entry from the Argo CD
applicationset controller configuration, including its headers, internal flag,
and GET method; retain the existing TCP/8080 metrics ingress unchanged.

In `@example/argocd/sbobs/cp-argocd-dex-server.yaml`:
- Around line 13-14: Restore the path in the kubeconfig entry under
cp-argocd-dex-server to /home/argocd/.kube/config, matching the sibling Argo CD
profiles and preserving the existing flags.

In `@example/argocd/sbobs/cp-argocd-notifications-controller.yaml`:
- Around line 65-71: Remove the inbound `:6443/metrics` entry from the Argo CD
notifications controller profile, leaving the existing `ingress` configuration
for the component’s actual metrics port 9001 unchanged.
- Around line 39-40: Update the proc path in the relevant profile entry so the
wildcard is removed and it points exactly to /proc/sys/net/core/somaxconn, while
preserving the existing flags and access configuration.

In `@example/argocd/sbobs/cp-argocd-redis.yaml`:
- Around line 27-28: Update the proc path in the relevant profile configuration
to use the fixed `/proc/sys/net/core/somaxconn` path instead of a wildcard PID
segment, while preserving the existing flags.
- Around line 68-74: Update the peer selector for the
argocd-application-controller rule by removing the pod-specific labels
apps.kubernetes.io/pod-index and statefulset.kubernetes.io/pod-name, while
retaining only app.kubernetes.io/name: argocd-application-controller so all
replicas match.

In `@example/argocd/sbobs/cp-argocd-repo-server.yaml`:
- Around line 136-142: Remove the inbound endpoint entry for :6443/metrics from
the Argo CD repository server configuration, including its associated headers,
internal flag, and GET method. Preserve the :8084/healthz inbound entry and its
existing settings.
- Around line 179-228: Collapse the duplicate external egress rules in the
profile so charts.helm.sh has one TCP-443 rule and github.com has one TCP-443
rule. Remove the redundant identifier-specific entries while preserving the
existing kube-dns internal rule and destination, port, protocol, and type
settings.

In `@example/argocd/sbobs/cp-argocd-server.yaml`:
- Around line 75-106: The profile lacks the Argo CD Dex TCP/5556 egress required
for SSO authentication. Add a session login step to the workload driver that
exercises the Dex-backed argocd login flow, then regenerate both argocd-server
and dex-server profiles so the corresponding egress and ingress rules are
captured; alternatively, explicitly document that this baseline excludes
authentication.

In `@Makefile`:
- Around line 374-381: Update the kubescape installation flow in the kubescape
target to apply kubescape/collapse-config.yaml after helm upgrade completes,
ensuring the storage API and CRDs are available before applying the
CollapseConfiguration manifest. Preserve the existing chart installation and
flags.

---

Duplicate comments:
In `@example/argocd/sbobs/cp-argocd-dex-server.yaml`:
- Around line 49-68: Update the workload driver to exercise the Dex session
login flow, including POST /api/v1/session, then regenerate both
cp-argocd-dex-server and cp-argocd-server profiles so the required ingress and
paired TCP/5556 egress rules are captured. If authentication is intentionally
excluded, document that baseline decision instead of modifying the profiles.

In `@example/argocd/sbobs/cp-argocd-repo-server.yaml`:
- Around line 157-163: Update the peer selector for the
argocd-application-controller rule by removing the ordinal-specific labels
apps.kubernetes.io/pod-index and statefulset.kubernetes.io/pod-name, while
retaining only app.kubernetes.io/name: argocd-application-controller.
🪄 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: 78a5275e-a36a-4496-85f0-96be7ed3abd0

📥 Commits

Reviewing files that changed from the base of the PR and between e79bbb3 and 6692a50.

📒 Files selected for processing (11)
  • Makefile
  • example/argocd/sbobs/cp-argocd-application-controller.yaml
  • example/argocd/sbobs/cp-argocd-applicationset-controller.yaml
  • example/argocd/sbobs/cp-argocd-dex-server.yaml
  • example/argocd/sbobs/cp-argocd-notifications-controller.yaml
  • example/argocd/sbobs/cp-argocd-redis.yaml
  • example/argocd/sbobs/cp-argocd-repo-server.yaml
  • example/argocd/sbobs/cp-argocd-server.yaml
  • kubescape/collapse-config.yaml
  • kubescape/representativeness.yaml
  • kubescape/values.yaml

Comment on lines +40 to +62
path: /git/refs/remotes/origin/crenshaw-dev-patch-1
- flags: [O_CLOEXEC, O_RDONLY]
path: /git/refs/remotes/origin/master
- flags: [O_CLOEXEC, O_RDONLY]
path: /git/refs/remotes/origin/wanghong230-patch-1
- flags: [O_CLOEXEC, O_RDONLY]
path: /git/refs/tags
- flags: [O_CLOEXEC, O_RDONLY]
path: /git/refs/tags/bg-deploy-v0.1
- flags: [O_CLOEXEC, O_RDONLY]
path: /git/refs/tags/bg-deploy-v0.2
- flags: [O_CLOEXEC, O_RDONLY]
path: /git/refs/tags/bg-guestbook-v0.1
- flags: [O_CLOEXEC, O_RDONLY]
path: /git/refs/tags/bg-guestbook-v0.2
- flags: [O_CLOEXEC, O_RDONLY]
path: /git/refs/tags/guestbook-v0.1
- flags: [O_CLOEXEC, O_RDONLY]
path: /git/refs/tags/guestbook-v0.2
- flags: [O_CLOEXEC, O_RDONLY]
path: /git/refs/tags/pre-post-sync-v0.1
- flags: [O_CLOEXEC, O_RDONLY]
path: /git/refs/tags/pre-post-sync-v0.2

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 | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

argoproj argocd-example-apps repository branches list

💡 Result:

The argoproj/argocd-example-apps repository uses master as its default branch [1]. It is primarily a single-branch repository containing various example applications within its file structure to demonstrate Argo CD functionality [1][2]. You can view the list of files and directories by visiting the repository on GitHub [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file existence/section =="
if [ -f example/argocd/sbobs/cp-argocd-application-controller.yaml ]; then
  wc -l example/argocd/sbobs/cp-argocd-application-controller.yaml
  sed -n '1,130p' example/argocd/sbobs/cp-argocd-application-controller.yaml | cat -n
else
  echo "file not found"
fi

echo
echo "== usages of drive-gitops-workload and git ref profile generation =="
rg -n "drive-gitops-workload|cp-argocd-application-controller|\\.git/refs|remotes/origin|/git/refs/tags|argocd-example-apps|example-apps" .

echo
echo "== nearby wildcard/open profile examples =="
python3 - <<'PY'
from pathlib import Path
p = Path('example/argocd/sbobs/cp-argocd-application-controller.yaml')
if p.exists():
    lines = p.read_text().splitlines()
    for i,l in enumerate(lines,1):
        if '⋯' in l:
            start=max(1,i-3); end=min(len(lines),i+3)
            print(f"-- lines {start}-{end} --")
            for n in range(start,end+1):
                print(f"{n}: {lines[n-1]}")
PY

Repository: k8sstormcenter/bob

Length of output: 10565


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== generate-opens / walk git refs / path wildcard support =="
for f in $(git ls-files | rg 'generate-opens|sbobs|argocd|git|opens|profile'); do
  case "$f" in
    *.yaml|*.yml|*.sh|*.py)
      echo "-- $f --"
      rg -n "opens|walk|git |refs|⋯|walk|glob|path" "$f" || true
      ;;
  esac
done

echo
echo "== candidate scripts =="
git ls-files | rg '(^|/)(generate|create|profile|sbob|opens).*\.(sh|py)$|(^|/)scripts/|^scripts/' || true

Repository: k8sstormcenter/bob

Length of output: 41849


Collapse Git ref path entries into dynamic segments.

drive-gitops-workload.sh uses https://github.com/argoproj/argocd-example-apps.git, so the branch and tag names in the profile are read from an external repository. If that repository adds, renames, or removes refs after this profile is generated, reads of those new Git ref paths fail the policy and create unexpected-open alerts. Replace the expanded ref names with path wildcards, for example /git/refs/remotes/origin/⋯ and /git/refs/tags/⋯, while keeping enough specificity to deny unrelated /git/... paths.

🤖 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/argocd/sbobs/cp-argocd-application-controller.yaml` around lines 40 -
62, Replace the enumerated branch and tag entries in the profile with dynamic
wildcard path segments under /git/refs/remotes/origin/ and /git/refs/tags/.
Preserve the existing O_CLOEXEC and O_RDONLY flags and keep the patterns scoped
to those Git ref directories so unrelated /git paths remain denied.

@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

🤖 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 `@kubescape/default-rules.yaml`:
- Line 17: Update the kubescape-orig setup to keep its ruleset and node-agent
image compatible: either change the rules applied by
kubescape/default-rules.yaml to use ap.* and nn.* helpers, or explicitly
configure the CP-enabled node-agent image in the kubescape-orig installation.
Ensure make kubescape-orig no longer applies a cp.*-only ruleset with the stock
node-agent.
🪄 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: 543c00c9-e546-4807-8505-9f6fb55bfa48

📥 Commits

Reviewing files that changed from the base of the PR and between 6620e3b and b7f2cf3.

📒 Files selected for processing (1)
  • kubescape/default-rules.yaml

Comment thread kubescape/default-rules.yaml
entlein and others added 11 commits August 2, 2026 13:38
…t-in

Reverts 3bb7081, which dropped the Argo CD changes from #173 to keep that PR
scoped to the restructure. This brings back the five re-learned SBoBs, the
GitOps driver that makes the learn window representative, sbob-from-learned.py,
and the R0006 supportPolicy fix.

Two things are deliberately NOT restored as they were.

kubescape/values.yaml keeps the chart default learningPeriod of 2m. The
reverted commit set it to 15m globally, which would break every CI leg: the
workflow waits 600s for a completed ContainerProfile, so a 15m window means no
profile ever completes and the whole matrix times out. Argo CD genuinely needs
longer than 2m — bob#170 was caused by a 2m window on an idle control plane
learning 5 execs instead of 10 and 51 opens instead of 517 — so the longer
window is now an opt-in Makefile variable, validated as a Go duration and
absent from the helm invocation unless set:

  make kubescape KS_LEARN_PERIOD=15m

The pkg submodule pointer stays at main's 2a2b337. The reverted commit pointed
it at ed64bf2, which lives on an inner topic branch and is not reachable from
inner main — exactly the off-main gitlink CodeRabbit flagged on #173. The
AUTOTUNING.md note on that commit (supportPolicy gating rulePolicies) still
needs landing inner-side on its own.

Refs #170

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

example/argocd/attacks.yaml and example/argocd/functional-tests.yaml could not
be run by bobctl at all: attacks lacked the required per-attack `type`, tests
lacked the `http`/`exec`/`redis` block, and both targeted argocd-server:8080
where the upstream Service exposes 443. They date from before Argo CD was split
into per-component suites, nothing references them, and all seven
example/argocd-<component>-*.yaml successors validate and carry correct ports
(argocd-server:443, repo-server:8081, dex:5556, redis:6379, ...). Deleted
rather than repaired — repairing would leave two competing entry points.

The reason they rotted is the part worth fixing: a suite is only ever exercised
if its app is in the CI tune matrix, and Argo CD is not in it. So nothing
schema-checked these files and an outside user found them before CI did.

scripts/validate-suites.sh now loads every AttackSuite and FunctionalTestSuite
under example/ through bobctl and runs in the build job. It needs no cluster —
bobctl validates a suite before touching kubernetes, so KUBECONFIG points at a
nonexistent path and the error is classified: a "validating ... suite" error is
a schema failure, while reaching "connecting to cluster" means the suite parsed
clean. __NAME__ placeholders are filled with dummy values first, so the one
suite carrying a credential (argocd-server, __ARGOCD_TOKEN__) is validated too
instead of being the only one skipped.

Verified by deleting a required `type` from argocd-server-attacks.yaml — the
exact #171 breakage — and confirming a non-zero exit, then restoring.

Closes #171

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jd9m962b1JAHdAy3rGe7nm
The reusable lesson from #170 was that nothing asked whether the baseline was
representative. A profile learned from an idle Argo CD is internally consistent,
tunes to score 0, and passes contrast at 0 Blind, while encoding "this app does
nothing" as normal — so the shipped SBoB flagged the GitOps toolchain itself as
attack. Every existing gate measures something orthogonal: tune score measures
detections against the functional tests you supply, contrast compares against an
app-TYPE envelope, the rule-coverage card counts rules the attacks lit.

kubescape/representativeness.yaml declares, per component, the evidence that the
workload actually did its job, and check-representativeness.py enforces it
against committed SBoBs or live ContainerProfiles.

The assertions are behavioural, not size-based, because size does not
discriminate: 21 opens is correct for argocd-server and catastrophic for
repo-server. What separates working from idle is WHICH peers were dialled and
WHICH binaries ran — repo-server forking git/helm/kustomize and leaving the
cluster for github.com, application-controller dialling repo-server:8081 because
reconciling means asking for the desired state.

Validated both directions. Against the #170 SBoBs still on main it reproduces
the RCA exactly: repo-server with no git/helm/kustomize, no public egress and 51
opens; application-controller never reaching 8081. Against the re-learned SBoBs
all five pass.

Two things deliberately not smoothed over:

applicationset-controller is marked weak: it polls the apiserver whether or not
any ApplicationSet exists, so TCP-443 does not discriminate. Saying so beats a
green tick that means nothing.

An unanchored open is reported as its own failure, not as a low count. The live
repo-server profile has collapsed to a bare /* — node-agent's dynamic-path
collapsing degenerating under git-checkout churn — which masks /etc/shadow and
makes R0010 Blind. That profile has only 1 open BECAUSE it is over-broad, and
"only 1 open" would send you to re-learn when re-learning just reproduces it.
The two modes get opposite remedies and the script says which applies.

load_live() does one Get per profile: the storage API strips spec contents from
List responses, so a List reports every profile as empty and the gate would fail
good profiles.

Refs #170

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

RCA of why the repo-server opens collapse to /*.

PROVEN, against the real storage analyzer
(storage/pkg/registry/file/dynamicpathdetector). Three paths, one of them
/*/Chart.yaml, come back as a single /*; the same three without it come back
intact. Order-independent — AnalyzeOpens builds the trie in a first pass before
emitting. This is NOT the 50-entry threshold; it reproduces at n=3.

The mechanism is two lines. processSegment short-circuits on a wildcard child at
the node it is walking:

    if wildcardChild, exists := node.Children[WildcardIdentifier]; exists {
        return wildcardChild
    }

so a * in ROOT position captures every subsequent path. processSegments then
breaks out ("wildcard absorbs the rest"), emitting just /*. Every path maps to
the same key and the map dedups to one entry.

PROVEN, in this repo: cp-argocd-repo-server.yaml ships 255 opens, 74 with a
leading wildcard and ZERO all-wildcard. Applied to the cluster, the stored
profile has exactly 1 open: /*. The 74 are git internals and checkout contents
(/*/refs/remotes/origin/master, /*/pack/pack-*.idx, /*/Chart.yaml) whose real
prefixes are /tmp, /helm-working-dir and /app/config per the deployment's
volumeMounts.

Why every guard missed it: is_over_broad here, isOverBroadOpen in
pkg/pkg/contrast/baseline.go, and the copy in check-representativeness.py all
require EVERY segment to be a wildcard. /*/Chart.yaml has a literal segment, so
all three called it anchored. But "anchored" for matching and "anchored" for the
storage trie are different properties, and only the second one decides whether
the profile survives being saved. That asymmetry is the bug.

Both python guards now reject a wildcard in first position as well as
all-wildcard. Verified they agree on /*, /*/Chart.yaml, /⋯/x, /**/y (all
rejected) and /tmp/*, /proc/⋯/*, /etc/passwd (all kept). The Go isOverBroadOpen
has the same gap and needs the same fix inner-side — it gates the tuner's
minimiser, so the tuner can currently emit a profile that destroys itself.

NOT PROVEN, stated as open: a separate path-mangling bug is visible in the same
data. cp-argocd-application-controller.yaml carries 38 top-level fragments of CA
certificate filenames (/.crt, /048_Secure_Server_CA.crt,
/ation_Authority_-_G2.crt) and zero intact /usr/share/ca-certificates paths,
while repo-server has 137 intact ones from the same image. main's idle-learned
version has 21 opens and no fragments, so these appeared during the #170
re-learn. Each fragment is a distinct top-level segment, and the root threshold
is 50 — application-controller sits at 48. Whether that mangling is what pushed
repo-server's root over the line cannot be shown: the collapse destroyed the
evidence. Filed as a follow-up rather than asserted.

A --round-trip check against the live storage API was written and then dropped:
the aggregated API server answers dry-run patches with ServiceUnavailable, so it
would have skipped every file while looking like coverage.

Refs #170

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jd9m962b1JAHdAy3rGe7nm
Rather than ship paths the storage API will rewrite, the SBoB is now already in
the form storage produces, so what is committed is exactly what gets enforced.

The 74 leading-wildcard entries are replaced by two anchored ones, chosen from
repo-server's declared writable roots (the deployment's volumeMounts), not
guessed:

  /tmp/*                    git checkouts, their .git internals and lock files
                            (39 manifest reads + 26 git internals + 8 locks)
  /app/config/gpg/keys/*    the gpg keyring, files named <fingerprint>.key

`*` was verified against storage's own CompareDynamic to span any depth:
/tmp/* matches /tmp/abc/.git/refs/remotes/origin/master. It stays anchored —
/tmp/* does NOT match /etc/shadow — so R0010 remains live. That is the whole
difference from the /*/… form: same breadth under /tmp, but a literal first
segment, so it cannot swallow the rest of the profile on save.

The 137 /usr/share/ca-certificates/mozilla/*.crt entries are pre-collapsed to
one /usr/share/ca-certificates/mozilla/⋯. That directory is over any sane
threshold and storage would collapse it regardless; doing it here means the file
and the enforced policy agree.

Verified against the real analyzer: 47 opens in, 47 out, nothing invented, and
/etc/shadow, /etc/passwd and the SA token all remain outside the baseline. The
previous version was 255 in, 1 out.

opens_min is dropped for repo-server, and it is worth being blunt about why: the
correct profile has 47 opens and the broken idle one had 51. The good profile is
SMALLER. Any count-based check would have passed the broken one and failed the
correct one. The execs and public-egress assertions are what actually separate
them — confirmed by re-running the gate against both.

Refs #170

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jd9m962b1JAHdAy3rGe7nm
cp-app.yaml and cp-argocd-repo-server.yaml both declared
metadata.name: argocd-repo-server in namespace argocd. Applying the directory
meant whichever landed second silently won.

Merged rather than deleted, because cp-app.yaml was not redundant. It carried
three execs the newer file lacked (/usr/local/bin/argocd-repo-server,
/usr/lib/git-core/git-remote-https, /usr/local/bin/gpg-wrapper.sh), seventeen
opens (/etc/ssl/certs/ca-certificates.crt, /etc/hosts, /etc/resolv.conf,
/dev/urandom, the /helm-working-dir and /app/config directory opens), and the
pinned image digest that cp-argocd-repo-server.yaml had no imageID at all for.

Merging exposed that the exec args were unusable on both sides. The learned
entries pinned values that can never recur — a commit SHA in `git checkout
--force 8088f4c…`, a random temp path in `gpg --gen-key
/tmp/gpg-key-recipe1260621259`, a pod name and PID in `git index-pack
--keep=fetch-pack 31 on argocd-repo-server-677d44c68c-8s4jx`, a checkout UUID in
`kustomize build /tmp/_argocd-repo/194e7baf-…`. cp-app.yaml's entries were the
opposite failure: bare ["/usr/bin/git"], which CompareExecArgs shows does NOT
match `git fetch`. So neither form would have matched a real invocation.

Both are replaced by one entry per binary with args [argv0, "⋯⋯"]. Verified
against storage's CompareExecArgs: ⋯⋯ matches both `git fetch origin --tags` and
a bare `git`, while `*` does NOT work for args (it is an opens-only wildcard) and
a bare arg list matches nothing with arguments. 28 duplicate entries collapse to
13 distinct binaries.

Incidental confirmation: that UUID path is /tmp/_argocd-repo/<uuid>/…, which
independently confirms the /tmp anchor chosen for the opens in the previous
commit was the right prefix.

Re-verified after merging: 64 opens in, 64 out, nothing invented, /etc/shadow
and the SA token still outside the baseline, and the representativeness gate
passes all five components.

Refs #170

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jd9m962b1JAHdAy3rGe7nm
example/argocd/ held a second, parallel Argo CD setup that duplicated the live
one and in one case silently disarmed it. Removed:

  argocd.yaml           declares the same three objects as
                        example/argocd-vulnerable.yaml (Namespace/adv,
                        AppProject/default, ConfigMap/argocd-cm) but WITHOUT the
                        configManagementPlugins exec-plugin block. Applying it
                        over the real one removes the CVE-carrying attack
                        surface the suites target. Strictly a subset; the
                        Makefile has always used argocd-vulnerable.yaml.

  argocd.manifest.yaml  a ChainManifest referenced by nothing. It pointed at
                        four files, two of which (app-b.yaml, app-c.yaml) never
                        existed, so it could not have run. CI discovers chain
                        manifests by the exact name chain.manifest.yaml, so this
                        one was never validated either.

  sbobs/cp-observer.yaml  the negative control for that manifest. No observer
                        workload exists in argocd and no argocd manifest creates
                        one; the other chains carry their own.

attack-pod.yaml is KEPT and is not outdated. It is a validated end-to-end
malicious-render RCE — an attacker git-daemon in ns adv serving a kustomize
exec-generator that runs inside argocd-repo-server during render, producing
R0001 (mal.sh), R0010 (/etc/shadow) and R0002. Its prerequisites are live:
kustomize.buildOptions is set by argocd-vulnerable.yaml and namespace adv
exists. Its header pointed at the deleted argocd.yaml for that prerequisite;
now points at argocd-vulnerable.yaml.

What remains is one path, matching the redis layout and the documented
convention: suites and the deploy manifest as example/argocd-*.yaml, everything
else under example/argocd/ (sbobs/, attack-pod.yaml, drive-gitops-workload.sh,
README, gif).

Verified after removal: no dangling references, all 25 suites still validate,
and the representativeness gate passes all five components.

Two coverage GAPS remain, deliberately not papered over: dex-server has attacks
but no functional tests and no SBoB, and redis has both suites but no SBoB.

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

The previous learn drove three Applications through one render path each. That
proved the #170 point but was still a narrow baseline: it never ran jsonnet,
never resolved a Helm dependency, never fired a sync hook, never rolled back. A
profile learned from it calls all of those anomalous.

Argo CD's native render tools are Helm, Jsonnet and Kustomize plus plain
directory, with waves and PreSync/Sync/PostSync hooks layered on top. The driver
now exercises all of it — every path verified to exist upstream before use:

  render   directory, helm, kustomize, jsonnet, jsonnet-with-TLA,
           helm-dependency, directory recursion (sock-shop)
  sync     PreSync/PostSync hooks, sync waves, helm hooks, manual sync via API
  history  helm parameter flip -> new revision -> rollback + revision metadata
  fanout   ApplicationSet list AND git-directory generators, app-of-apps
  misc     alternating hard/normal refresh, resource action (rollout restart)

helm-dependency is the highest-value addition and it paid off exactly as
intended: repo-server egress went from github.com alone to github.com PLUS
charts.helm.sh on four IPs, with DNS names captured. Nothing else in the
workload makes it dial a non-github host.

sock-shop, helm-hooks and blue-green are deliberately left on manual sync.
repo-server still clones and renders them every refresh, which is what the
baseline needs, but syncing sock-shop would put ~10 services on the laptop for
no extra profile coverage.

Results, 15m window (the live maxSniffingTimePerContainer — no node-agent
restart was needed, it was already configured):

  repo-server              10 execs   48 opens   8 egress (github + charts.helm.sh)
  application-controller    2 execs  168 opens   4 egress
  server                    2 execs   21 opens   4 egress
  notifications-controller  2 execs   23 opens   3 egress
  applicationset-controller 2 execs   24 opens   3 egress
  dex-server                1 exec    18 opens   1 egress   NEW
  redis                     2 execs   15 opens   0 egress   NEW

dex-server and redis close the two coverage gaps that were open since the
component split.

repo-server reproduced the leading-wildcard collapse — 68 unanchored opens plus
2 fully-unanchored, from an entirely fresh learn. That confirms the RCA is
deterministic rather than a one-off, so the remedy is now in the generator
instead of hand-applied: --anchor re-anchors unanchored opens onto the
workload's real writable roots (/tmp, /helm-working-dir, /app/config — the
emptyDir volumeMounts in the Deployment) rather than dropping them, and
--collapse-dir pre-collapses a directory to the form storage would produce
anyway. Exec args are collapsed to [argv0, ⋯⋯] because learned args pin commit
SHAs, checkout UUIDs, temp filenames and pod names that can never recur.

Verified end to end, and this closes the caveat left on the previous commits.
All seven are fixpoints against the analyzer (n in, n out, no invented entries,
/etc/shadow and the SA token still outside every baseline), the gate passes all
seven, and — the part that was previously untested — applying them to the live
cluster and reading them back shows every profile preserved byte-for-byte on the
RUNNING storage:sbob-rc4 image, not just against local source. The previous
repo-server SBoB stored 1 open where it committed 255.

Refs #170

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

Addresses the review comments on #176.

NO YAML ALIASES. The pushed cp-argocd-repo-server.yaml contained &id001/*id001
because anchor_wildcards() handed all three anchored entries the SAME flags list
object and PyYAML back-references anything it sees twice by identity. Kubescape
does not resolve aliases, and a reviewer cannot see what a rule allows through a
pointer. Fixed at both levels: each entry gets its own list, and both generators
now dump through a NoAliasDumper that refuses to emit anchors at all.

PLURAL FORM + CIDR. IPAddress and DNS are DEPRECATED singulars; the v0.0.2
fields are the lists IPAddresses and DNSNames, and an IPAddresses entry may be a
literal, a CIDR, or "*" (pkg/registry/file/networkmatch, spec 5.7/5.8). Every
egress moves onto them:

  10.43.0.1  -> [10.43.0.0/16, 10.96.0.0/12]   apiserver, k3s AND kubeadm
  10.42.0.1  -> [10.42.0.0/16, 10.244.0.0/16]  pod CIDR, k3s AND flannel
  140.82.121.3/4, 185.199.108-111.153 -> dropped, keyed on dnsNames
                                         github.com / charts.helm.sh instead

The external addresses rotate, so pinning them guarantees the SBoB stops
matching. The learn captured the DNS names, so nothing is lost by keying on
them — and the representativeness gate is now STRONGER for it: repo-server must
show egress to both github.com and charts.helm.sh by name.

PLACEHOLDERS for volatile Host headers: 10.42.0.250:8082 and 127.0.0.1:6443
become *:8082 and *:6443. A pod IP differs on every restart.

TRUNCATED PATHS repaired. /4_46_21.2502219693/{ca.crt,token,namespace} is the
projected SA-token directory with its leading characters eaten by the node-agent
bug; the correct form is known because every uncorrupted profile carries it, so
it is restored rather than shipped as garbage.

COMPACT FORMAT. One entry per line via default_flow_style=None. An SBoB is read
as a policy document and block style buried the paths among their flags:
application-controller 978 -> 412 lines, repo-server 430 -> 228, dex 129 -> 64.

The gate had to learn all of this too, and it silently broke first: reading only
the singular ipAddress it failed 6 of 7 correct profiles. It now reads both
forms, satisfies an expectation by literal OR containing CIDR, and counts a
public DNS name as leaving the cluster.

Verified on the RUNNING storage:sbob-rc4 — applied and read back, the plural
ipAddresses with CIDRs persist and every profile's opens are preserved exactly.

Also pins storage to sbob-rc5s from the helm-chart PR. node-agent is HELD at
rc4 with a comment saying why: rc5s predates the fix on
k8sstormcenter/node-agent#59 for the truncation above, and its build is
currently red, so there is no fixed tag to move to yet.

Refs #170 #176

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jd9m962b1JAHdAy3rGe7nm
node-agent sbob-rc5s-fpo carries the /proc full-path fix from
k8sstormcenter/node-agent#59: NormalizePath re-roots headless /proc/<pid>/<file>
opens via an explicit allowlist (task|fd|setgroups|gid_map|uid_map|status|stat|
cgroup|mountinfo|maps|environ|comm|cmdline|ns) rather than only task|fd, so
"/17/setgroups" resolves to "/proc/17/setgroups".

Built from fix/fullpathopens-sbob (migrate/sbob HEAD eab532e4 + the PR's
pkg/utils/path.go), so it compiles against the migrated storage and must be
paired with storage:sbob-rc5s. The original fix/fullpathopens branch is 32
commits behind and fails the AP/NN mock compile — not a valid build source.

Digest: sha256:4e512e03724e34c8788f35151d5cd4e0fa2a5a4dced5a92df4c14402e329ca30

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

Re-learned all seven components on the fixed image with a 15m window and the
wide GitOps driver, then verified against both the PR's acceptance criterion and
the separate corruption cases found here.

The PR criterion PASSES: zero headless /<digits>/... proc paths.

The fix is BROADER than the note described, which is worth recording because the
note only claimed /proc re-rooting:

  jsonnet/kustomize name fragments   /sonnet-guestbook, /ustomize-guestbook -> 0
  bare /git/HEAD (lost checkout)     11 paths -> 0
  top-level .crt fragments           38 -> 0
  /usr/share/ca-certificates paths   0 intact -> 137 intact

ONE CASE SURVIVES, and it is now isolated: argocd-dex-server still truncates the
projected SA-token directory to /6_22_16.2999670045/{ca.crt,namespace,token}.
Every other component emits the correct /run/secrets/... form, and dex emits
BOTH, which suggests a specific code path rather than a general failure.
portable-sbob.py repairs it, but it should be reported upstream rather than
papered over here.

Also probably truncated, unconfirmed: argocd-redis logs /dump.rdb where the
image's WORKDIR is /data, so the real path is likely /data/dump.rdb.

A NEW consequence of the wider workload, which is the interesting part: the
application-controller profile collapsed to /* on its own. 419 of 423 learned
opens were wildcard-rooted, because the kube discovery cache writes a fresh
atomic-write temp file per refresh and 33 driver rounds produced 344 of them
under one directory — enough churn to cascade the trie collapse to the root.
More representative load therefore makes collapse MORE likely, not less. It is
anchored onto /home/argocd, /tmp and the serviceaccount dir.

opens_min is dropped for application-controller for the same reason it was for
repo-server, and the reason is now demonstrated twice: the correctly anchored
profile has 6 opens while a short unrepresentative run leaves dozens of
literals. Any count-based check rewards the worse profile. The behavioural
assertions (which peers, which binaries) are what separate them.

Verified: all seven pass the gate, no YAML aliases, no cluster-specific
literals, and applying to the live cluster on storage:sbob-rc5s preserves every
profile's opens exactly. Demo mode restored, driver workload torn down.

Refs #170 #176

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jd9m962b1JAHdAy3rGe7nm
entlein and others added 3 commits August 2, 2026 13:39
…on it

The argocd-application-controller profile was being destroyed before anyone
could look at it: 427 of 430 learned opens collapsed to /*, so the shipped SBoB
was three broad anchored roots standing in for everything the workload actually
touches. Driving MORE load made it worse (419/423 -> 427/430), because the kube
discovery cache writes one atomic-write temp file per refresh and a longer,
more representative window puts more children under one node until the collapse
propagates to the root.

kubescape/collapse-config.yaml sets threshold 1 on the three churn directories,
so node-agent collapses THERE and never cascades:

  /home/argocd/.kube/cache    discovery cache, 344 temp files in one run
  /tmp/_argocd-repo           repo-server git checkouts, one dir per UUID
  /helm-working-dir           helm cache (HELM_CACHE_HOME)

All five compiled-in defaults are repeated verbatim. CollapseConfigs REPLACES
the defaults wholesale rather than merging, so omitting them would have
silently changed collapse behaviour for every other app in the cluster.

The middle prefix is deliberately /tmp/_argocd-repo and not /tmp. This CR is
cluster-scoped; threshold 1 on bare /tmp would collapse every workload's /tmp
to /tmp/*, weakening the redis, postgres and webapp SBoBs to fix an Argo CD
problem. The narrower prefix is evidence-backed — learned exec args showed
`kustomize build /tmp/_argocd-repo/194e7baf-.../kustomize-guestbook`.

Result, re-learned with the driver and the full functional suite:

  application-controller  430 opens / 427 cascaded  ->  40 opens / 0 cascaded

and the 40 are real literals — /etc/passwd, /etc/resolv.conf, /proc/⋯/stat,
/usr/local/bin/argocd, libc, the SA token, the git refs and tags — plus a
single /home/argocd/.kube/cache/* that node-agent emitted itself. No post-hoc
anchoring needed for it any more. Predicted by the analyzer beforehand
(368 -> 9, 8/8 literals preserved) and confirmed live.

repo-server still cascades and this is the honest limit of the approach: it has
/*, /*/* and /⋯ and ZERO paths carrying the /tmp/_argocd-repo prefix, because
node-agent truncates checkout paths before storage sees them. A prefix rule
cannot match a path whose prefix is already gone, so the fragments land at the
root and pile up. Truncation defeats CollapseConfig. repo-server therefore
still needs --anchor.

Correcting an earlier claim in this branch: sbob-rc5s-fpo did NOT fix all
truncation. It fixed the /proc class and the cert fragments, and the SA-token
directory now arrives in full. But checkout paths still arrive as /git/HEAD,
/git/refs/tags/... where the truth is /tmp/_argocd-repo/<uuid>/.git/... . That
was reported as fixed only because application-controller was 419/423 collapsed
at the time, so its /git entries were hidden inside the wildcard rather than
absent. Uncollapsing it revealed them.

Verified: no YAML aliases, no cluster-specific literals, rotating SA directory
normalised to ⋯, gate passes all seven, and applying to the live cluster on
storage:sbob-rc5s preserves every profile's opens exactly. Demo mode restored,
workload torn down, node-agent restart count still 0.

Refs #170 #176

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

`make kubescape` is what clients run, so it must install the chart as
published. It was silently rewriting the rendered manifest through
kubescape/post-render.sh on every invocation.

KS_POST_RENDER is now an opt-in variable, off by default, and it gates the
--post-renderer flag on all three helm targets (kubescape, alertmanager,
kubescape-vendor). The script itself is unchanged.

verify-streaming had to change with it or the target would simply fail instead:
it hard-errored when networkStreamingEnabled was not true, which is exactly what
a stock install renders. The chart ANDs that flag with cloud-submit, so on an
on-prem stack with no backend it is false by design. That is an upstream chart
limitation worth telling people about, not a broken install, so it is now a
NOTE explaining the consequence (the profile's inline network shape is inert, so
R0005 and R0011 cannot fire) and pointing at KS_POST_RENDER=1. It no longer
fails the build.

CI and scripts/local-ci.sh now pass KS_POST_RENDER=1 explicitly. Without that
this change would have quietly broken them: the contrast tests assert R0005 and
R0011, and with streaming off those assertions pass as "no alert" rather than
failing, so the matrix would have gone green while measuring nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jd9m962b1JAHdAy3rGe7nm
The claim that networkStreamingEnabled is required for R0005 (DNS) and R0011
(egress) was wrong. It was asserted in post-render.sh, in values.yaml, in a
verify-streaming target that FAILED the build without it, and in the commit
message that added the post-render opt-in. All of it is removed.

What the flag actually does, in node-agent:

  cmd/main.go:368   the ONLY functional use — whether NetworkStream is a real
                    client or a mock. NetworkStream is an EXPORTER that POSTs
                    to /v1/networkstreams.
  tracer_factory.go the network and dns tracers are registered
                    UNCONDITIONALLY; nothing consults the flag.
  default-rules     R0005/R0011 are CEL expressions over
                    nn.is_domain_in_egress / nn.was_address_in_egress against
                    the profile, fed by those tracers.
  containerprofilemanager  zero references to networkstream, so the profile's
                    egress/ingress is populated independently.

Verified live rather than argued. With streaming OFF, a DNS lookup and a
public-address connection from a pod whose profile has egress:null both reached
their rules — R0005 and R0011 appear in node-agent's evaluation log. Flipping
streaming ON and repeating produced an identical result. No difference.

Forcing it on was actively harmful: with no backend configured node-agent
logged "NetworkStream - failed to send network events ... unsupported protocol
scheme" roughly once a minute for the whole run.

In the chart, capabilities.networkEventsStreaming gates no template — diffing
enable vs disable changes only the capabilities string and a warning. It is
ANDed with cloud-submit, so networkStreamingEnabled renders false either way on
a backend-less install. Set to disable, which also drops the spurious
"network events will NOT be streamed" warning.

Removed: the streaming rewrite in post-render.sh, the verify-streaming and
enable-streaming targets, and the KS_POST_RENDER=1 opt-in that CI and
local-ci.sh had just been given for this. post-render.sh keeps its other half —
mounting the filesystem for a non-stock runc — which is real and cannot be done
with --set, and is now implied by KS_RUNC_MNT rather than needing its own flag.

SEPARATE AND UNRESOLVED, found while verifying: no profile-dependent rule can
currently evaluate on this cluster. All 12 (R0001, R0002, R0004, R0005, R0006,
R0007, R0008, R0010, R0011, R1001, R1004, R1009) fail with "undeclared
reference to 'ap'/'nn' (in container '')" — an empty container binding, so the
CEL libraries never load. It affects user-defined-profile pods and learn-mode
pods alike, and is identical with streaming on or off, so it is not caused by
this change. Detection is dead on node-agent sbob-rc5s-fpo + this
default-rules.yaml until that is understood.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jd9m962b1JAHdAy3rGe7nm
Reasoning belongs in commit messages, not in files nobody reads. Removes only
comments added on this branch; pre-existing ones are untouched.

Behaviour unchanged: make kubescape stays plain, KS_RUNC_MNT still implies the
post-renderer, KS_LEARN_PERIOD still maps to maxLearningPeriod, 25 suites
validate, 7 SBoBs pass the gate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jd9m962b1JAHdAy3rGe7nm
@entlein
entlein merged commit b7d17c0 into main Aug 2, 2026
9 of 10 checks passed
entlein added a commit that referenced this pull request Aug 2, 2026
Replaces hand-anchored learned profiles with bobctl tune output. All seven now
score 0 (0 missed, 0 false positives).

The misses were assertion bugs, not detection gaps. /etc/shadow is unreadable in
all seven containers — every one runs non-root — so cat/ln produce R0001 but the
open never succeeds and R0010/R1010/R1012 cannot fire. Those assertions are
removed; R0001 is kept where it genuinely fires. fileless-memfd-exec on
argocd-server needs perl, which is not in the image, so its R1005 assertion goes
too.

Worth recording: the four components that already scored 0 were getting FALSE
CREDIT. The tuner matches a detection on rule+container within the window, and
those containers had incidental R0010 alerts from other sources. repo-server and
application-controller had none and failed honestly. Verified by triggering
cat/ln manually in repo-server: 3 new alerts, all R0001, no R0010/R1010/R1012.

Two defects in the tuner output had to be corrected:

Egress was stripped to bare ports — the greedy minimiser drops ipAddress because
it does not affect the score, which destroys R0011's specificity. The learned
egress is restored, then made portable (CIDRs for cluster IPs, dnsNames for
external peers).

Leading-wildcard opens survived the minimiser's guard. Fixed at source in the
submodule: contrast.isOverBroadOpen now rejects a wildcard in first position,
not only an all-wildcard path.

repo-server and notifications-controller are committed in the form storage
actually stores (137 CA certs collapse; the SA-token dir merges), so committed
and enforced agree exactly.

Verified: 7/7 score 0, 7/7 pass the representativeness gate, 25/25 suites
validate, no YAML aliases, no cluster-specific literals, and every profile
round-trips through the live storage:sbob-rc5s unchanged.

Refs #170 #176

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jd9m962b1JAHdAy3rGe7nm
entlein added a commit that referenced this pull request Aug 2, 2026
Replaces hand-anchored learned profiles with bobctl tune output. All seven now
score 0 (0 missed, 0 false positives).

The misses were assertion bugs, not detection gaps. /etc/shadow is unreadable in
all seven containers — every one runs non-root — so cat/ln produce R0001 but the
open never succeeds and R0010/R1010/R1012 cannot fire. Those assertions are
removed; R0001 is kept where it genuinely fires. fileless-memfd-exec on
argocd-server needs perl, which is not in the image, so its R1005 assertion goes
too.

Worth recording: the four components that already scored 0 were getting FALSE
CREDIT. The tuner matches a detection on rule+container within the window, and
those containers had incidental R0010 alerts from other sources. repo-server and
application-controller had none and failed honestly. Verified by triggering
cat/ln manually in repo-server: 3 new alerts, all R0001, no R0010/R1010/R1012.

Two defects in the tuner output had to be corrected:

Egress was stripped to bare ports — the greedy minimiser drops ipAddress because
it does not affect the score, which destroys R0011's specificity. The learned
egress is restored, then made portable (CIDRs for cluster IPs, dnsNames for
external peers).

Leading-wildcard opens survived the minimiser's guard. Fixed at source in the
submodule: contrast.isOverBroadOpen now rejects a wildcard in first position,
not only an all-wildcard path.

repo-server and notifications-controller are committed in the form storage
actually stores (137 CA certs collapse; the SA-token dir merges), so committed
and enforced agree exactly.

Verified: 7/7 score 0, 7/7 pass the representativeness gate, 25/25 suites
validate, no YAML aliases, no cluster-specific literals, and every profile
round-trips through the live storage:sbob-rc5s unchanged.

Refs #170 #176

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jd9m962b1JAHdAy3rGe7nm
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