Fix/argocd 170 - #176
Fix/argocd 170#176
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe 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. ChangesArgo CD Kubescape workflow
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (1 warning, 2 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (22)
.github/workflows/ci-bobctl-autotune.yamlMakefileexample/argocd-repo-server-attacks.yamlexample/argocd-server-attacks.yamlexample/argocd/argocd.manifest.yamlexample/argocd/argocd.yamlexample/argocd/attack-pod.yamlexample/argocd/attacks.yamlexample/argocd/drive-gitops-workload.shexample/argocd/functional-tests.yamlexample/argocd/sbobs/cp-app.yamlexample/argocd/sbobs/cp-argocd-application-controller.yamlexample/argocd/sbobs/cp-argocd-applicationset-controller.yamlexample/argocd/sbobs/cp-argocd-notifications-controller.yamlexample/argocd/sbobs/cp-argocd-repo-server.yamlexample/argocd/sbobs/cp-argocd-server.yamlexample/argocd/sbobs/cp-observer.yamlkubescape/default-rules.yamlkubescape/representativeness.yamlscripts/check-representativeness.pyscripts/sbob-from-learned.pyscripts/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"] |
There was a problem hiding this comment.
🎯 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}" |
There was a problem hiding this comment.
🎯 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.
| 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.
| log "seeding Applications + ApplicationSet" | ||
| apply_apps | ||
| apply_appset |
There was a problem hiding this comment.
🩺 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 ofapply_appsandapply_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 soendpointsrecords 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.
| 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"] |
There was a problem hiding this comment.
🩺 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.
| 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.
| 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", ""))] |
There was a problem hiding this comment.
🗄️ 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.
| 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.
| 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] |
There was a problem hiding this comment.
🗄️ 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.
| 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.
| # 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 |
There was a problem hiding this comment.
🎯 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.
| Accept-Encoding: | ||
| - gzip | ||
| Host: | ||
| - 127.0.0.1:6443 |
There was a problem hiding this comment.
check to replace with placeholder
| Connection: | ||
| - close | ||
| Host: | ||
| - 10.42.0.250:8082 |
There was a problem hiding this comment.
check to replace with placeholder
| - dns: '' | ||
| dnsNames: null | ||
| identifier: dd18fa52244005a49761004e6173ec450c0779087cedb1eaf1ae49142a6550a2 | ||
| ipAddress: 10.43.0.1 |
There was a problem hiding this comment.
use PluralForm and cidr
| - flags: | ||
| - O_RDONLY | ||
| - O_CLOEXEC | ||
| path: /87307a |
| - dns: '' | ||
| dnsNames: null | ||
| identifier: dd18fa52244005a49761004e6173ec450c0779087cedb1eaf1ae49142a6550a2 | ||
| ipAddress: 10.43.0.1 |
| - O_CLOEXEC | ||
| path: /usr/share/zoneinfo/Etc/UTC | ||
| - path: /tmp/* | ||
| flags: &id001 |
There was a problem hiding this comment.
what happened here ?
| Connection: | ||
| - close | ||
| Host: | ||
| - 10.42.0.244:8084 |
There was a problem hiding this comment.
use plurals and use cidr
… 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
There was a problem hiding this comment.
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
--anchorre-adds the paths.
droppedis built frombadbefore the--anchorbranch runs. Whenargs.anchoris set, those paths are re-anchored intokept, 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
📒 Files selected for processing (13)
example/argocd/drive-gitops-workload.shexample/argocd/sbobs/cp-argocd-application-controller.yamlexample/argocd/sbobs/cp-argocd-applicationset-controller.yamlexample/argocd/sbobs/cp-argocd-dex-server.yamlexample/argocd/sbobs/cp-argocd-notifications-controller.yamlexample/argocd/sbobs/cp-argocd-redis.yamlexample/argocd/sbobs/cp-argocd-repo-server.yamlexample/argocd/sbobs/cp-argocd-server.yamlkubescape/representativeness.yamlkubescape/values.yamlscripts/check-representativeness.pyscripts/portable-sbob.pyscripts/sbob-from-learned.py
| 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 |
There was a problem hiding this comment.
🎯 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 || trueRepository: 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:
- 1: https://github.com/argoproj/argo-cd/blob/master/server/application/application.proto
- 2: https://apis.io/apis/argo/argo-applications-api/
- 3: https://raw.githubusercontent.com/api-evangelist/argo/refs/heads/main/openapi/argo-applications-api-openapi.yml
- 4: https://argoproj-argo-cd-10.mintlify.app/api/grpc/application-service
- 5: https://argo-cd.readthedocs.io/en/stable/developer-guide/api-docs/
- 6: https://argo-cd.readthedocs.io/en/latest/developer-guide/api-docs/
- 7: https://argo-cd.readthedocs.io/en/release-3.4/developer-guide/api-docs/
- 8: Sync Behavior: Web UI vs. API argoproj/argo-cd#18903
- 9: https://github.com/argoproj/argo-cd/blob/master/controller/sync.go
- 10: https://cd.apps.argoproj.io/swagger-ui
🌐 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:
- 1: https://kubernetes.io/docs/reference/kubectl/generated/kubectl_create/
- 2: add kubectl create --raw -f kubernetes/kubernetes#54245
- 3: https://iximiuz.com/en/posts/kubernetes-api-call-simple-http-client/
🏁 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"
fiRepository: 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:
- 1: https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster-services/
- 2: https://stackoverflow.com/questions/59766405/how-is-the-verb-proxy-determined-in-the-audit-logs
- 3: https://levelup.gitconnected.com/kubectl-deep-dive-talking-to-the-raw-api-11af383f9889
- 4: https://oneuptime.com/blog/post/2026-02-09-kubectl-get-raw-api-endpoints/view
- 5: https://acritelli.medium.com/interacting-directly-with-the-kubernetes-api-using-kubectl-254821b8aaf2
- 6: https://raesene.github.io/blog/2025/01/18/Exploring-the-Kubernetes-API-Server-Proxy/
- 7: https://github.com/kubernetes/kubernetes/blob/e9b95827/pkg/registry/core/service/proxy.go
- 8: https://github.com/kubernetes/kubectl/blob/master/pkg/cmd/get/get.go
- 9: Bug: Limit Apiserver Proxy Redirects kubernetes/kubernetes#95129
- 10: https://kubernetes.io/docs/reference/kubectl/generated/kubectl_proxy/
🌐 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:
- 1: https://kubernetes.io/docs/reference/kubectl/generated/kubectl_get/
- 2: https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands
- 3: https://v1-34.docs.kubernetes.io/docs/reference/generated/kubectl/kubectl-commands
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.
| - flags: [O_CLOEXEC, O_RDONLY] | ||
| path: /proc/⋯/net/core/somaxconn |
There was a problem hiding this comment.
🔒 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/somaxconnThe 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.
| - 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: [] |
There was a problem hiding this comment.
🗄️ 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.
| 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}") |
There was a problem hiding this comment.
🎯 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.
| 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.
| try: | ||
| ipaddress.ip_address(host) | ||
| except ValueError: | ||
| new.append(h); continue |
There was a problem hiding this comment.
📐 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.
| 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
| for f in args.files: | ||
| d = yaml.safe_load(Path(f).read_text()) | ||
| s = d.get("spec") or {} |
There was a problem hiding this comment.
🩺 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.
| 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.
| 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) |
There was a problem hiding this comment.
🩺 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.
| 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
| 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] |
There was a problem hiding this comment.
🔒 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.
…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
…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
There was a problem hiding this comment.
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 winApply
kubescape/collapse-config.yamlduring installation.
kubescape/collapse-config.yamldefines theCollapseConfigurationthresholds, but the Makefile install target only applieskubescape/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 winRemove the spurious
:6443/metricsinbound 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 winRestrict the
/procwildcard to the PID segment (still unresolved).Same issue as the sibling profiles:
/proc/⋯/net/core/somaxconnshould 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 winRestrict the
/procwildcard to the PID segment (still unresolved).Same issue as
cp-argocd-application-controller.yaml:/proc/⋯/net/core/somaxconnshould be/proc/sys/net/core/somaxconn, sincesysis 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 winRemove the spurious
:6443/metricsinbound entry (still unresolved).This is the same byte-identical
:6443/metricsentry 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 winRestrict the
/procwildcard to the PID segment (still unresolved).Same issue as the other profiles:
/proc/⋯/net/core/somaxconnshould 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 winThe peer selector is keyed to StatefulSet pod ordinal 0 (still unresolved).
apps.kubernetes.io/pod-index: '0'andstatefulset.kubernetes.io/pod-name: argocd-application-controller-0identify one pod, not the workload. A cluster running more than oneargocd-application-controllerreplica sends traffic fromargocd-application-controller-1, which matches neither this rule nor any other, and the connection appears unexpected. Keep onlyapp.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 winRemove the spurious
:6443/metricsinbound 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/healthzand 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 winRestrict the
/procwildcard to the PID segment (still unresolved).The real read is
/proc/sys/net/core/somaxconn.sysis 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 winRestore the full kubeconfig path (still unresolved).
/.kube/configis a truncated path. Sibling profiles record the full path,/home/argocd/.kube/config(seecp-argocd-application-controller.yamlandcp-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 winRemove the spurious
:6443/metricsinbound 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 valueSix egress rules describe two destinations (still unresolved).
charts.helm.shappears four times (lines 179, 186, 193, 200) andgithub.comtwice (lines 215, 222). The rules differ only byidentifier; each learned connection produced its own entry instead of being merged by destination. Collapse them into one rule perdnsNamesvalue 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 liftThe 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.yamlcorrespondingly records no inbound traffic. Together this confirms the learn window never exercised the SSO/argocd loginflow. 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 winThe peer selector is keyed to StatefulSet pod ordinal 0 (still unresolved).
apps.kubernetes.io/pod-index: '0'andstatefulset.kubernetes.io/pod-name: argocd-application-controller-0are per-pod identity labels. A cluster running more than oneargocd-application-controllerreplica sends traffic fromargocd-application-controller-1, which matches neither this rule nor any other, and the connection appears unexpected. Keep onlyapp.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 liftNo login flow was captured; the paired server-side egress rule was also removed (still unresolved).
endpointsis empty, there is noingresskey in this file, andcp-argocd-server.yamlno 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 exercisedPOST /api/v1/session. A deployment using SSO orargocd loginalerts 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
📒 Files selected for processing (11)
Makefileexample/argocd/sbobs/cp-argocd-application-controller.yamlexample/argocd/sbobs/cp-argocd-applicationset-controller.yamlexample/argocd/sbobs/cp-argocd-dex-server.yamlexample/argocd/sbobs/cp-argocd-notifications-controller.yamlexample/argocd/sbobs/cp-argocd-redis.yamlexample/argocd/sbobs/cp-argocd-repo-server.yamlexample/argocd/sbobs/cp-argocd-server.yamlkubescape/collapse-config.yamlkubescape/representativeness.yamlkubescape/values.yaml
| 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 |
There was a problem hiding this comment.
🗄️ 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:
- 1: https://github.com/argoproj/argocd-example-apps
- 2: https://github.com/argoproj/argocd-example-apps/tree/36363233d00d67a7ab6b180053dc3322af9bc5c6
🏁 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]}")
PYRepository: 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/' || trueRepository: 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.
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
kubescape/default-rules.yaml
…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
…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
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
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
No description provided.