Skip to content

feat(argocd): per-container contrast legs for all 7 subcomponents - #167

Merged
entlein merged 7 commits into
mainfrom
feat/argocd-contrast
Jul 30, 2026
Merged

feat(argocd): per-container contrast legs for all 7 subcomponents#167
entlein merged 7 commits into
mainfrom
feat/argocd-contrast

Conversation

@entlein

@entlein entlein commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Argo CD is one contrast leg per container (skill §8), so this adds an attack suite and functional tests for each of the seven: server, repo-server, application-controller, applicationset-controller, notifications-controller, dex, redis.

Depends on entlein/bob#58 (three tuner fixes without which none of this runs).

Coverage

Union across the legs is 13 rules — the same set redis asserts:
R0001 R0005 R0006 R0007 R0008 R0010 R0011 R1000 R1004 R1005 R1008 R1010 R1012

argocd-server alone lands 12 live and tunes to score=0, 16/16 expected detections matched, 0 benign FPs, contrast gate 25 Separable / 4 Ambiguous / 0 Blind with --type web-app --expect reads-host-files,reads-secrets.

Rules placed where they actually discriminate

This is the point of splitting by container rather than writing one suite:

  • R0006/R0007 live on argocd-redis — the only subcomponent with the SA token mounted that never reads it. On every controller the token read is the baseline, so the rules are structurally suppressed; repo-server has no token mounted at all.
  • R0009 is a documented probe everywherebpf(2) returns EPERM in an unprivileged container, so no program ever loads.
  • R1001 is never emitted by this node-agent build (verified zero cluster-wide). The drifted exec trips R1000 instead, which is what is asserted.

Each of those carries expectedDetections: [] plus the reason, per "honest boundary, never fake a detection".

Infrastructure

  • kubescape/values.yaml — point node-agent's fanotify hook at the runc this host actually uses (k3s runs with --data-dir /mnt/dev-data/k3s) and mount that partition. Without it node-agent sees no container started after its own boot, so nothing is ever learned — it just looks healthy. This is what the old rollout restart ds/node-agent was hiding.
  • Makefile / local-ci.sh — node-agent is never restarted, only waited on. Network streaming is forced at render time by a Helm post-renderer instead of patch-then-bounce.
  • fast-tune.sh — fix --kubescape-namespace--ks-namespace (this was broken for every app, not just argocd), add the seven argocd legs, and mint the Argo session token through the K8s API proxy.

Status

argocd-server is green. argocd-repo-server currently scores 4 on a ledger attribution bug tracked in entlein/bob#58 — the alerts fire and are present in alertmanager, they are just not credited. The remaining five legs are generated and validated (TestAttackSuite_AssertionRealism passes) but not yet individually tuned.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Jd9m962b1JAHdAy3rGe7nm

entlein and others added 2 commits July 29, 2026 11:43
…nal tests)

First Argo CD subcomponent leg, redis-aligned per the add-new-app-for-bobctl-
tuning skill. VALIDATED live: R0001 fires on exec attacks (cat/id/perl/ln)
against the hand-authored SBoB; the sole benign file-access (projected SA
token) is covered so R0002/R0006 don't FP on the legitimate k8s-client read.

Key finding: Argo's Go control-plane components NEVER fork-exec, so a *learned*
profile has execs=[] and gives no R0001 contrast. The SBoB must DECLARE the
exec baseline (tini -> argocd-server -> argocd) — every other exec is then
anomalous. This means control-plane legs are demo-verification (apply SBoB ->
attack -> verify), not learn->tune like redis.

Files:
- example/argocd-vulnerable.yaml            full-stack vulnerable overlay (all 7 subcomponents)
- example/argocd-server-attacks.yaml        exec kill-chain (R0001) + HTTP-logic CVE probes
- example/argocd-server-functional-tests.yaml  Argo's REAL swagger API read-path (token-substituted)
- example/argocd/sbobs/cp-argocd-server.yaml   demo SBoB with declared exec baseline
- example/argocd-repo-server-attacks.yaml    render path-traversal/symlink/exec-RCE leg (attacks)
- Makefile deploy-argocd                     full upstream install + overlay + wait

Deploy order (critical): node-agent FIRST, then the app — node-agent hooks a
container at its start. pkg bump wires the suites into the static sweeps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JWQXE6qkAbWkuv8rcUSTVq
Argo CD is one contrast leg PER container (skill §8), so this adds an
attack suite and functional tests for each of the seven: server,
repo-server, application-controller, applicationset-controller,
notifications-controller, dex and redis.

Coverage. The union across the legs is 13 rules — the same set redis
asserts: R0001 R0005 R0006 R0007 R0008 R0010 R0011 R1000 R1004 R1005
R1008 R1010 R1012. argocd-server alone lands 12 live and tunes to
score=0 with 16/16 expected detections matched, 0 benign FPs, and a
contrast gate of 25 Separable / 4 Ambiguous / 0 Blind.

Rules are placed on the container where they actually discriminate,
which is the whole point of splitting by container:
  - R0006/R0007 live on argocd-redis. It is the only subcomponent with
    the SA token mounted that never reads it; on every controller the
    token read IS the baseline, so the rules are structurally suppressed,
    and repo-server has no token mounted at all.
  - R0009 is a documented probe everywhere: bpf(2) returns EPERM in an
    unprivileged container, so no program ever loads.
  - R1001 is never emitted by this node-agent build (verified zero
    cluster-wide); the drifted exec trips R1000, which is asserted.

Infrastructure:
  - kubescape/values.yaml: point node-agent's fanotify hook at the runc
    this host actually uses (k3s --data-dir /mnt/dev-data/k3s) and mount
    that partition, without which node-agent sees NO container started
    after its own boot and nothing is ever learned.
  - Makefile/local-ci.sh: node-agent is never restarted; wait for it.
    Streaming is forced at render time by a Helm post-renderer instead of
    a patch-then-bounce.
  - fast-tune.sh: fix --kubescape-namespace -> --ks-namespace (this was
    broken for every app, not just argocd) and add the seven argocd legs.

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

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 25 minutes

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

How can I continue?

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

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

How do review limits work?

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

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

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 04789bd8-70f0-4c20-8db9-3727869ec4cb

📥 Commits

Reviewing files that changed from the base of the PR and between 75d4e35 and 5d4e534.

⛔ Files ignored due to path filters (6)
  • example/argocd/argocd-killchain.gif is excluded by !**/*.gif
  • example/redis-client/redis-killchain.gif is excluded by !**/*.gif
  • example/rule-coverage/mariadb-killchain.gif is excluded by !**/*.gif
  • example/rule-coverage/postgres-killchain.gif is excluded by !**/*.gif
  • example/rule-coverage/postgres-vuln-killchain.gif is excluded by !**/*.gif
  • example/rule-coverage/webapp-killchain.gif is excluded by !**/*.gif
📒 Files selected for processing (4)
  • Makefile
  • README.md
  • kubescape/rule-coverage.yaml
  • scripts/render-rule-coverage-gif.py
📝 Walkthrough

Walkthrough

Adds a pinned Argo CD deployment with a vulnerable overlay, attack and functional suites for Argo CD components, an argocd-server ContainerProfile, Kubescape network-streaming post-rendering, tuning updates, and metrics tooling.

Changes

Argo CD and Kubescape integration

Layer / File(s) Summary
Argo CD deployment and vulnerable overlay
Makefile, example/argocd-vulnerable.yaml
Installs pinned Argo CD manifests, applies the vulnerable overlay, and waits for component rollouts.
Controller, Dex, notifications, and Redis attack coverage
example/argocd-application-controller-*, example/argocd-applicationset-controller-*, example/argocd-dex-server-attacks.yaml, example/argocd-notifications-controller-*, example/argocd-redis-*
Adds attack and functional suites covering component metrics, Redis commands, execution anomalies, filesystem access, DNS, egress, and boundary cases.
Server and repo-server attack and functional coverage
example/argocd-server-*, example/argocd-repo-server-*
Adds Argo CD API and metrics tests plus attack coverage for CVE probes, render-toolchain processes, execution, filesystem, network, and Kubernetes API behavior.
Kubescape runtime profile and network policy
example/argocd/sbobs/cp-argocd-server.yaml
Defines the server executable allowlist, readable paths, rule policies, capabilities, and internal traffic constraints.
Streaming post-rendering and tuning orchestration
kubescape/force-network-streaming.sh, Makefile, scripts/fast-tune.sh, scripts/local-ci.sh, pkg
Enables network streaming during Helm rendering, waits for node-agent readiness, updates Argo CD suite selection and token provisioning, and updates the submodule reference.
Leg metrics and visualization tooling
scripts/merge-leg-metrics.py, scripts/render-metrics-gif.py, README.md
Adds per-leg metrics merging, common-canvas GIF rendering, and documentation for multi-container contrast legs.

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

Sequence Diagram(s)

sequenceDiagram
  participant Makefile
  participant Helm
  participant PostRenderer
  participant NodeAgent
  Makefile->>Helm: Render Kubescape chart
  Helm->>PostRenderer: Process rendered manifests
  PostRenderer->>Helm: Set networkStreamingEnabled true
  Helm->>NodeAgent: Apply updated DaemonSet
  Makefile->>NodeAgent: Wait for readiness and verify streaming
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: per-container contrast legs for all seven Argo CD subcomponents.
Description check ✅ Passed The description matches the changeset and explains the seven Argo CD legs plus related infrastructure updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/argocd-contrast

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

❤️ Share

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

@entlein entlein self-assigned this Jul 29, 2026
@entlein entlein linked an issue Jul 29, 2026 that may be closed by this pull request

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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 `@Makefile`:
- Around line 273-283: Deduplicate the repeated post-renderer and validation
sequence across the kubescape, alertmanager, and kubescape-vendor targets by
introducing a shared .PHONY target such as post-kubescape-checks that runs
wait-node-agent and verify-streaming. Make all three targets depend on or invoke
this shared check while preserving the existing KS_POST_RENDERER usage and
validation order.
- Around line 101-109: Remove the error-swallowing `-` prefixes from every
`kubectl rollout status` command in the Argo CD subcomponent wait block so
`deploy-argocd` fails when any rollout fails. Preserve execution of all status
checks so each subcomponent is still reported before the target exits nonzero.

In `@scripts/fast-tune.sh`:
- Around line 59-66: Update the PROFILE_MATCH assignment in the argocd-server
case to match the hand-authored profile name argocd-server rather than the
hash-based learned-profile regex. Keep the surrounding case configuration
unchanged.
🪄 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: b9679b03-d1dc-46f0-9852-cabc698b3f5f

📥 Commits

Reviewing files that changed from the base of the PR and between cb7b590 and f5db7fc.

📒 Files selected for processing (21)
  • Makefile
  • example/argocd-application-controller-attacks.yaml
  • example/argocd-application-controller-functional-tests.yaml
  • example/argocd-applicationset-controller-attacks.yaml
  • example/argocd-applicationset-controller-functional-tests.yaml
  • example/argocd-dex-server-attacks.yaml
  • example/argocd-notifications-controller-attacks.yaml
  • example/argocd-notifications-controller-functional-tests.yaml
  • example/argocd-redis-attacks.yaml
  • example/argocd-redis-functional-tests.yaml
  • example/argocd-repo-server-attacks.yaml
  • example/argocd-repo-server-functional-tests.yaml
  • example/argocd-server-attacks.yaml
  • example/argocd-server-functional-tests.yaml
  • example/argocd-vulnerable.yaml
  • example/argocd/sbobs/cp-argocd-server.yaml
  • kubescape/force-network-streaming.sh
  • kubescape/values.yaml
  • pkg
  • scripts/fast-tune.sh
  • scripts/local-ci.sh
👮 Files not reviewed due to content moderation or server errors (4)
  • example/argocd-repo-server-attacks.yaml
  • example/argocd-repo-server-functional-tests.yaml
  • example/argocd-server-attacks.yaml
  • example/argocd-server-functional-tests.yaml

Comment thread Makefile
Comment thread Makefile
Comment on lines 273 to 283
.PHONY: kubescape
kubescape:
kubescape:
helm repo add kubescape https://kubescape.github.io/helm-charts/
helm repo update
helm upgrade --install kubescape kubescape/kubescape-operator --version $(KUBESCAPE_CHART_VER) -n honey --create-namespace --values kubescape/values.yaml
helm upgrade --install kubescape kubescape/kubescape-operator --version $(KUBESCAPE_CHART_VER) -n honey --create-namespace --values kubescape/values.yaml --post-renderer $(KS_POST_RENDERER)
@echo "Ensuring CRDs are up-to-date (helm upgrade skips CRDs)..."
-helm show crds kubescape/kubescape-operator --version $(KUBESCAPE_CHART_VER) | kubectl apply --server-side --force-conflicts -f - 2>/dev/null || true
-kubectl apply -f kubescape/default-rules.yaml
sleep 5
-kubectl rollout status -n honey deploy/kubevuln --timeout=120s
$(MAKE) enable-streaming

# enable-streaming forces networkStreamingEnabled=true in the node-agent
# configmap and rolls the DaemonSet. The chart gates the flag behind
# cloud-submit (rendered = submit AND enable; submit = non-empty .Values.server)
# so on an on-prem stack with no server it renders FALSE regardless of
# capabilities.networkEventsStreaming. This target is idempotent and MUST be
# re-run after EVERY helm upgrade — kubescape/alertmanager/kubescape-vendor all
# call it — so the setting never drifts. See docs/portability-spec.md D7a.
.PHONY: enable-streaming
enable-streaming:
@echo "Forcing node-agent networkStreamingEnabled=true (chart renders FALSE without cloud-submit)..."
@PATCH=$$(kubectl -n honey get configmap node-agent -o jsonpath='{.data.config\.json}' | python3 -c 'import json,sys; cfg=json.load(sys.stdin); cfg["networkStreamingEnabled"]=True; print(json.dumps({"data":{"config.json":json.dumps(cfg)}}))'); \
kubectl -n honey patch configmap node-agent --type merge -p "$$PATCH"
-kubectl rollout restart -n honey ds node-agent
-kubectl rollout status -n honey ds node-agent --timeout=180s
$(MAKE) wait-node-agent
$(MAKE) verify-streaming

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated wait-node-agent + verify-streaming sequence across three targets.

kubescape, alertmanager, and kubescape-vendor each repeat the identical --post-renderer $(KS_POST_RENDERER) + $(MAKE) wait-node-agent + $(MAKE) verify-streaming trio. Consider a shared .PHONY prerequisite (e.g. post-kubescape-checks: wait-node-agent verify-streaming) that all three depend on, to avoid the copies drifting.

Also applies to: 313-326, 337-345

🧰 Tools
🪛 checkmake (0.3.2)

[warning] 274-274: Target body for "kubescape" exceeds allowed length of 5 lines (9).

(maxbodylength)

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

In `@Makefile` around lines 273 - 283, Deduplicate the repeated post-renderer and
validation sequence across the kubescape, alertmanager, and kubescape-vendor
targets by introducing a shared .PHONY target such as post-kubescape-checks that
runs wait-node-agent and verify-streaming. Make all three targets depend on or
invoke this shared check while preserving the existing KS_POST_RENDERER usage
and validation order.

Comment thread scripts/fast-tune.sh
Comment on lines +59 to +66
argocd-server)
NS=argocd
SUITE=example/argocd-server-attacks.yaml
FUNCTESTS=example/argocd-server-functional-tests.yaml
PROFILE_MATCH=argocd-server-.*-argocd-server
SERVICE_SCHEME=https
NEEDS_ARGOCD_TOKEN=true
;;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n 'PROFILE_MATCH' pkg scripts -A3 -B3

Repository: k8sstormcenter/bob

Length of output: 8163


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== fast-tune script outline =="
ast-grep outline scripts/fast-tune.sh --view expanded || true

echo
echo "== scripts/fast-tune.sh relevant sections =="
sed -n '1,190p' scripts/fast-tune.sh

echo
echo "== cp-argocd-server.yaml =="
if git ls-files | grep -qx 'cp-argocd-server.yaml'; then
  sed -n '1,220p' cp-argocd-server.yaml
else
  fd -a 'cp-argocd-server\.yaml' . -x sh -c 'echo "--- $1"; sed -n "1,220p" "$1"' sh {}
fi

echo
echo "== argocd-server profile references =="
rg -n 'argocd-server|user-defined-profile|containerprofiles|ContainerProfile|PROFILE_MATCH|learned-profile' . -S | head -200

Repository: k8sstormcenter/bob

Length of output: 33954


🌐 Web query:

kubescape node-agent user-defined-profile containerprofile name binding

💡 Result:

In Kubescape's architecture, user-defined profiles are bound to workloads (specifically pods) using metadata labels [1][2]. This binding mechanism allows the node-agent to associate specific ApplicationProfile and NetworkNeighborhood resources—collectively representing a Software Bill of Behavior (SBOB)—with the containers running in a pod [3][2][4]. To bind a user-defined profile, you apply the relevant CRD (ApplicationProfile or NetworkNeighborhood) to the cluster and then add the corresponding labels to the pod template in your workload definition [1][2]. The required labels are: kubescape.io/user-defined-profile: kubescape.io/user-defined-network: The node-agent monitors these labels during pod creation [1][4]. When a pod starts, the agent identifies these labels, fetches the referenced user-defined profiles, and applies them as an overlay to the workload's behavior profile [5][6]. Key points regarding this binding: 1. Lifecycle: Binding is currently "late-binding" at the pod level, meaning the node-agent picks up the labels and binds the profile only at pod startup [1][4]. If you change the profile or the label, you must restart the pod for the changes to take effect [1][4]. 2. Evolution to ContainerProfile: While the system has historically relied on separate ApplicationProfile and NetworkNeighborhood CRDs, Kubescape is in the process of consolidating these into a unified ContainerProfile CRD [3][2]. Functionality remains consistent during this migration, though internal implementation details in the node-agent have been updated to support consolidated, server-side merged profiles alongside the legacy client-side user-defined overlays [5][6]. 3. Verification: User-defined profiles can be cryptographically signed [1][6]. The node-agent performs signature verification at bind time to prevent tampering, and signed overlays are subject to tamper detection during runtime [1][6]. If a signed profile is modified without a valid signature update, the node-agent will detect the discrepancy [1][6]. While ContainerProfile is the newer standard for representing workload behavior, the user-defined binding mechanism primarily remains label-driven as of the current architecture [5][6][2].

Citations:


🌐 Web query:

site:github.com/kubescape/node-agent "kubescape.io/user-defined-profile" ContainerProfile

💡 Result:

In the context of the Kubescape node-agent, a ContainerProfile is a core object used to maintain the learned runtime behavior of a container [1]. While there is no single entity named "user-defined ContainerProfile" in the public documentation, the system supports user-defined overlays and custom profiles through a mechanism often referred to as user-managed or user-defined profiles [2][3][4]. Key technical aspects include: 1. User-Managed Overlays: The node-agent allows for the application of user-defined overlays, specifically targeting Application Profiles (AP) and Network Neighborhoods (NN) [2][4]. These are often identified by labels (e.g., via the UserDefinedProfileMetadataKey) and are projected into the container's runtime profile cache [2][3]. 2. Merging and Projection: User-defined overrides are merged with the base, automatically learned ContainerProfile [2]. Recent architecture changes have moved much of this merging logic to the server side (Kubescape storage) to ensure consistency, though some client-side overlay projections remain active [2]. 3. Integrity and Tamper Detection: User-managed profiles can be cryptographically signed (using tools like Cosign) [4]. The node-agent includes mechanisms to verify these signatures upon loading them into the cache. If a signed profile is found to be tampered with, the system is designed to emit an R1016 "Signed profile tampered" alert [3][4]. 4. Implementation: These profiles are managed via the NodeAgent's object cache system, which utilizes a projection mechanism to combine learned base profiles with user-provided specifications [5]. Users typically interact with these through Kubernetes custom resources or specific metadata labels attached to their pods [2][3]. For further configuration and details, please refer to the official Kubescape node-agent documentation and the project repository on GitHub [1][6][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate argocd manifests =="
git ls-files | rg '(^|/)argocd' | head -100

echo
echo "== argocd workloads and user-defined profile labels =="
rg -n 'kind: (Deployment|Pod|ServiceAccount|ConfigMap|Role|ClusterRole|RoleBinding)|metadata:|name: argocd-server|kubescape\.io/user-defined-profile|replicaset-hash|replicaset|chart' example/argocd example/argocd-vulnerable.yaml kubescape .github -S | head -200

echo
echo "== Search profile name patterns in argocd manifests =="
rg -n 'metadata:\n(  name:|  namespace:)|name: argocd-server|argocd-server|kubescape\.io/user-defined-profile' example/argocd example/argocd-vulnerable.yaml kubescape -S

echo
echo "== Read argocd setup/manifest files =="
for f in $(git ls-files | rg 'example/argocd|example/argocd-vulnerable.yaml'); do
  echo "--- $f"
  sed -n '1,220p' "$f"
done

Repository: k8sstormcenter/bob

Length of output: 13734


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== example/argocd/argocd.manifest.yaml =="
sed -n '1,120p' example/argocd/argocd.manifest.yaml

echo
echo "== example/argocd/argocd.yaml =="
sed -n '1,80p' example/argocd/argocd.yaml

echo
echo "== example/argocd/sbobs/cp-argocd-server.yaml first 30 lines =="
sed -n '1,30p' example/argocd/sbobs/cp-argocd-server.yaml

echo
echo "== Find exact user-defined-profile and replicaset references =="
rg -n -U 'kubescape\.io/user-defined-profile|profile_match:\s*replicaset-argocd-server|replicaset-argocd-server' example/argocd example/argocd-vulnerable.yaml kubescape -S || true

echo
echo "== Parse argocd manifests yaml-ish for ContainerProfile names/labels =="
python3 - <<'PY'
from pathlib import Path
for p in Path("example/argocd").rglob("*.yaml"):
    text = p.read_text()
    for i,line in enumerate(text.splitlines(),1):
        if any(s in line for s in ("profile_match", "user-defined-profile", "replicaset-argocd-server", "name: argocd-server")):
            print(f"{p}:{i}:{line}")
PY

Repository: k8sstormcenter/bob

Length of output: 6442


🌐 Web query:

kubescape argocd-server replicaset ContainerProfile profile_match argocd-server-.*-argocd-server

💡 Result:

The term profile_match is not a standard configuration field in Kubescape’s official core API but is typically used in user-defined scripts (such as custom automation or tuning scripts) to identify specific application profiles for container security analysis [1]. In the context of Kubescape and Kubernetes, ContainerProfile objects are automatically generated resources that capture the runtime behavior of workloads [2][3]. When scanning or tuning security policies for a workload like argocd-server, users often need to map a specific, learned ContainerProfile to their deployment [1]. Key technical insights regarding your query: 1. ContainerProfile Lookup: Kubescape maps ContainerProfile objects to workloads using a slug-based naming convention [2]. A profile_match string, such as argocd-server-.*-argocd-server, acts as a regular expression or pattern to select the correct ApplicationProfile or ContainerProfile from the cluster for analysis or tuning [1]. 2. ArgoCD Context: The argocd-server deployment is a standard Kubernetes workload [4]. If you are seeing profile_match errors or configurations, it indicates that a script is attempting to find a previously learned security profile for the ArgoCD server pod, likely to compare its current runtime behavior against a known-good baseline or to auto-generate security policies [1]. 3. Troubleshooting: If you are encountering issues where a profile is not being matched: - Ensure the Kubescape operator is correctly installed and configured to monitor the namespace where ArgoCD is running (typically argocd) [5]. - Verify that the ContainerProfile has actually been generated for the target workload by checking the applicationprofiles.spdx.softwarecomposition.kubescape.io custom resources in the relevant namespace [1]. - The regex argocd-server-.*-argocd-server suggests the script expects a naming structure that includes the deployment and potentially a replicaset or unique pod identifier suffix [1]. If the naming convention in your cluster differs (e.g., due to specific Helm chart configurations), the regex may fail to match [1][6]. For authoritative guidance, please refer to the official Kubescape documentation regarding Runtime Security and the ContainerProfile resource, as manual script tuning often relies on internal naming conventions that may change between versions [3].

Citations:


Set PROFILE_MATCH to the hand-authored argocd-server profile name

cp-argocd-server.yaml is named argocd-server and bound by kubescape.io/user-defined-profile: argocd-server, so the hash-based learned-profile regex is misleading for this leg. Use a stable match for the hand-authored profile, e.g. argocd-server, unless this tooling intentionally matches an autosync generated profile.

🧰 Tools
🪛 ast-grep (0.45.0)

[warning] 64-64: A credential-bearing variable (e.g. PASSWORD, PASSWD, SECRET, TOKEN, API_KEY) is assigned a hardcoded string literal. Secrets committed to a script are exposed in source control, process listings, and shell history, and cannot be rotated without a code change. Read the value from a secrets manager or an injected environment variable at runtime instead (e.g. PASSWORD="${DB_PASSWORD:?must be set}"), and never commit the literal.
Context: NEEDS_ARGOCD_TOKEN=true
Note: [CWE-798] Use of Hard-coded Credentials.

(hardcoded-password-assignment-bash)

🪛 Shellcheck (0.11.0)

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

(SC2125)

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

In `@scripts/fast-tune.sh` around lines 59 - 66, Update the PROFILE_MATCH
assignment in the argocd-server case to match the hand-authored profile name
argocd-server rather than the hash-based learned-profile regex. Keep the
surrounding case configuration unchanged.

entlein and others added 3 commits July 29, 2026 19:33
…path

The previous commit put global.overrideRuntimePath and a hostPath mount of
/mnt/dev-data straight into kubescape/values.yaml. That path only exists on
the dev laptop (k3s --data-dir /mnt/dev-data/k3s), and the volume declares
type: Directory, so on a CI runner node-agent could not schedule at all and
"Install kubescape" failed for every app leg.

Detect the runc actually in use from the live containerd shim instead. On a
stock layout — CI runners, ordinary k3s, plain docker — KS_RUNC_FLAGS is
EMPTY and nothing is overridden, so behaviour is byte-identical to before.
Only a non-standard data-dir sets RUNTIME_PATH, and only then is the
filesystem holding it hostPath-mounted (node-agent's own `host` volume is a
non-recursive bind of "/", so it does not carry a separate partition).

`make show-runc` prints what was detected.

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

Adds example/argocd/argocd-killchain.gif, rendered by the same design-1
kill-chain heatmap that produces example/redis-client/redis-killchain.gif.

Also corrects the opens wildcards. Per storage's dynamicpathdetector,
"*" matches ZERO-OR-MORE whole path segments (glob-style **) and is special
only as a complete segment; "⋯" matches exactly one. So "/proc/⋯/*" forced
"at least two segments below /proc" rather than the intended "anything under
/proc" — the ⋯ adds nothing a trailing * does not already cover. Replaced
with /proc/* and /sys/*.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jd9m962b1JAHdAy3rGe7nm
Every leg was dogfooded against the live cluster and every remaining miss
was chased to its cause rather than asserted away.

Real bugs fixed:
  - argocd-repo-server named its container "repo-server" in three legacy
    attacks; it is "argocd-repo-server". Those three had looked like a
    ledger attribution bug for hours — the new diagnostic in the inner repo
    identified it on first run. Leg goes 4 misses -> 0.
  - R1004 needs the payload on a REAL volume mount. /tmp is not a mount in
    the controllers and an exec from /dev/shm scores as R1000 here, so
    application-controller now execs from /home/argocd.
  - dex has no reachable benign surface (Argo CD only runs it with SSO
    configured, nothing dials it, it does not listen on localhost), so its
    leg is attack-only and FUNCTESTS is empty.

Honest boundaries, each verified by hand and commented in place:
  - argocd-redis R0001: the learned baseline is /bin/busybox and every
    coreutil IS busybox, so the exec path is already allowed and R0001
    cannot fire. The read is still caught by R0010.
  - argocd-redis R1004: a successful exec from the /data mount fires R0001
    only (checked with a valid applet name so the exec really succeeds).
  - argocd-notifications-controller R1004: its only writable mount is
    /dev/shm, which scores as R1000 on this build.

Coverage unchanged at 13 rules across the legs — the same set redis
asserts. Contrast gates report 0 Blind on every leg.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (3)
example/argocd-repo-server-attacks.yaml (3)

51-56: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Replace the empty success indicator.

responseContains: "" matches every response, including a failed ln command, so this leg cannot prove that the symlink was created. Emit a marker after successful creation and clean up the probe path.

🤖 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-repo-server-attacks.yaml` around lines 51 - 56, Update the
cve-2022-31036-symlink-sensitive command so it emits a unique success marker
only after the symlink is created successfully, and remove the probe path
afterward. Replace the empty responseContains value with that marker to ensure
the success indicator verifies the complete command sequence.

83-93: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Make the no-token boundary satisfiable.

The file states that no ServiceAccount token is mounted, but this command only emits token_read when the read succeeds. In the declared environment it produces no success marker, so the suite cannot validate the intended no-detection boundary. Add a fallback marker such as token_absent, or explicitly mark this leg unreachable.

🤖 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-repo-server-attacks.yaml` around lines 83 - 93, Update the
repo-server-sa-token-read command so it emits a success marker when the
ServiceAccount token is absent, such as token_absent, while retaining token_read
for successful reads. Add the corresponding success indicator so the no-token
boundary is satisfiable.

32-46: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

These CVE-labelled legs do not exercise the claimed render/API paths.

head /etc/passwd, ln -sf /etc/shadow ..., whoami, and the credential echo are direct command-injection probes. They do not drive repo-server path traversal, Git symlink rendering, config-management-plugin execution, or the repo-credential API error path. Either invoke the actual vulnerable render/API flows with fixtures, or rename these as generic post-exploitation behavior probes and document that CVE coverage is not being tested.

Also applies to: 48-81, 95-104

🤖 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-repo-server-attacks.yaml` around lines 32 - 46, The
CVE-labelled attack legs use direct command probes rather than exercising the
claimed repo-server render or API vulnerabilities. Update the scenarios around
cve-2022-24348-render-path-traversal and the referenced legs to invoke the
actual vulnerable render/API flows with appropriate fixtures; alternatively,
rename them as generic post-exploitation probes and document that they do not
provide CVE coverage.
🤖 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-notifications-controller-attacks.yaml`:
- Around line 77-84: Update the exec-from-volume-mount command in the cmdinject
scenario to copy, execute, and remove the payload under /dev/shm/mnt_payload
instead of /home/argocd/mnt_payload. Change expectedDetections to assert the
documented R1000 event, while keeping R1004 unasserted and preserving the
existing success indicator.

In `@example/argocd-redis-attacks.yaml`:
- Around line 71-78: The exec-from-volume-mount test currently documents an
expected R0001 detection but declares none. Update expectedDetections for
exec-from-volume-mount to assert the observed R0001 event, including the command
attribution required by the matcher; alternatively remove the successful
execution, while preserving the documented boundary behavior.

In `@Makefile`:
- Line 306: Update the Kubescape install command in the Makefile target to
invoke the configured $(HELM) variable instead of the hardcoded helm binary,
preserving all existing arguments and flags.

---

Outside diff comments:
In `@example/argocd-repo-server-attacks.yaml`:
- Around line 51-56: Update the cve-2022-31036-symlink-sensitive command so it
emits a unique success marker only after the symlink is created successfully,
and remove the probe path afterward. Replace the empty responseContains value
with that marker to ensure the success indicator verifies the complete command
sequence.
- Around line 83-93: Update the repo-server-sa-token-read command so it emits a
success marker when the ServiceAccount token is absent, such as token_absent,
while retaining token_read for successful reads. Add the corresponding success
indicator so the no-token boundary is satisfiable.
- Around line 32-46: The CVE-labelled attack legs use direct command probes
rather than exercising the claimed repo-server render or API vulnerabilities.
Update the scenarios around cve-2022-24348-render-path-traversal and the
referenced legs to invoke the actual vulnerable render/API flows with
appropriate fixtures; alternatively, rename them as generic post-exploitation
probes and document that they do not provide CVE coverage.
🪄 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: 5bdfb721-3be7-4287-aff6-fbc1e24a0ec0

📥 Commits

Reviewing files that changed from the base of the PR and between f5db7fc and 282e7c7.

⛔ Files ignored due to path filters (1)
  • example/argocd/argocd-killchain.gif is excluded by !**/*.gif
📒 Files selected for processing (8)
  • Makefile
  • example/argocd-application-controller-attacks.yaml
  • example/argocd-notifications-controller-attacks.yaml
  • example/argocd-redis-attacks.yaml
  • example/argocd-repo-server-attacks.yaml
  • example/argocd/sbobs/cp-argocd-server.yaml
  • pkg
  • scripts/fast-tune.sh

Comment on lines +77 to +84
- name: exec-from-volume-mount
type: cmdinject
exec: { command: ["sh", "-c", "cp /bin/echo /home/argocd/mnt_payload && chmod +x /home/argocd/mnt_payload && /home/argocd/mnt_payload shm_exec_done; rm -f /home/argocd/mnt_payload"] }
successIndicators: [{ responseContains: "shm_exec_done" }]
# This container's only writable mount is /dev/shm (everything else is a
# read-only TLS projection), and an exec from /dev/shm scores as R1000 here,
# not R1004. No reachable path to R1004 — honest boundary.
expectedDetections: []

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Align the command with the documented writable mount.

Line 79 writes to /home/argocd, while Lines 81-83 state that only /dev/shm is writable. With the declared filesystem boundary, the copy cannot reach shm_exec_done, so this leg does not exercise the intended mount boundary. Use /dev/shm/mnt_payload and update expectedDetections to include the documented R1000 event while leaving R1004 unasserted.

Proposed path correction
-    exec: { command: ["sh", "-c", "cp /bin/echo /home/argocd/mnt_payload && chmod +x /home/argocd/mnt_payload && /home/argocd/mnt_payload shm_exec_done; rm -f /home/argocd/mnt_payload"] }
+    exec: { command: ["sh", "-c", "cp /bin/echo /dev/shm/mnt_payload && chmod +x /dev/shm/mnt_payload && /dev/shm/mnt_payload shm_exec_done; rm -f /dev/shm/mnt_payload"] }
🤖 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-notifications-controller-attacks.yaml` around lines 77 - 84,
Update the exec-from-volume-mount command in the cmdinject scenario to copy,
execute, and remove the payload under /dev/shm/mnt_payload instead of
/home/argocd/mnt_payload. Change expectedDetections to assert the documented
R1000 event, while keeping R1004 unasserted and preserving the existing success
indicator.

Comment on lines +71 to +78
- name: exec-from-volume-mount
type: cmdinject
exec: { command: ["sh", "-c", "cp /bin/busybox /data/mnt_payload && chmod +x /data/mnt_payload && /data/mnt_payload true && echo shm_exec_done; rm -f /data/mnt_payload"] }
successIndicators: [{ responseContains: "shm_exec_done" }]
# Verified unreachable on this container: a successful exec of a binary copied
# onto the /data volume mount fires R0001 only, never R1004 (checked with a
# valid busybox applet name so the exec genuinely succeeds). Honest boundary.
expectedDetections: []

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not discard the documented R0001 detection.

The comment states that successful execution from /data fires R0001, but expectedDetections: [] declares no detection. This boundary leg will therefore fail or classify the observed process alert as unexpected. Assert the observed R0001 event, including command attribution if required by the matcher, or remove the successful execution.

🤖 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-redis-attacks.yaml` around lines 71 - 78, The
exec-from-volume-mount test currently documents an expected R0001 detection but
declares none. Update expectedDetections for exec-from-volume-mount to assert
the observed R0001 event, including the command attribution required by the
matcher; alternatively remove the successful execution, while preserving the
documented boundary behavior.

Comment thread Makefile
helm repo add kubescape https://kubescape.github.io/helm-charts/
helm repo update
helm upgrade --install kubescape kubescape/kubescape-operator --version $(KUBESCAPE_CHART_VER) -n honey --create-namespace --values kubescape/values.yaml
helm upgrade --install kubescape kubescape/kubescape-operator --version $(KUBESCAPE_CHART_VER) -n honey --create-namespace --values kubescape/values.yaml $(KS_RUNC_FLAGS) --post-renderer $(KS_POST_RENDERER)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Honor $(HELM) for the Kubescape install.

This primary install path bypasses the configured Helm binary/wrapper, unlike the related targets.

Proposed fix
-	helm upgrade --install kubescape kubescape/kubescape-operator --version $(KUBESCAPE_CHART_VER) -n honey --create-namespace --values kubescape/values.yaml $(KS_RUNC_FLAGS) --post-renderer $(KS_POST_RENDERER)
+	$(HELM) upgrade --install kubescape kubescape/kubescape-operator --version $(KUBESCAPE_CHART_VER) -n honey --create-namespace --values kubescape/values.yaml $(KS_RUNC_FLAGS) --post-renderer $(KS_POST_RENDERER)
📝 Committable suggestion

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

Suggested change
helm upgrade --install kubescape kubescape/kubescape-operator --version $(KUBESCAPE_CHART_VER) -n honey --create-namespace --values kubescape/values.yaml $(KS_RUNC_FLAGS) --post-renderer $(KS_POST_RENDERER)
$(HELM) upgrade --install kubescape kubescape/kubescape-operator --version $(KUBESCAPE_CHART_VER) -n honey --create-namespace --values kubescape/values.yaml $(KS_RUNC_FLAGS) --post-renderer $(KS_POST_RENDERER)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Makefile` at line 306, Update the Kubescape install command in the Makefile
target to invoke the configured $(HELM) variable instead of the hardcoded helm
binary, preserving all existing arguments and flags.

entlein and others added 2 commits July 30, 2026 06:34
…/tmp staging

The GIF. A single Argo CD subcomponent tunes in two or three iterations — its
profile is already minimal and the over-broad guard blocks the degenerate
collapse — so a per-iteration GIF is two or three frames. The story worth
showing for Argo CD is that the SAME contrast method lands on all seven
containers of one product, so each frame is a COMPONENT: its tuned profile
size, its kill-chain coverage, its score. All seven read PERFECT, 84 expected
detections in total. scripts/merge-leg-metrics.py builds the combined metrics
from the per-leg runs.

Renderer fix (scripts/render-metrics-gif.py, the maintained one):
savefig(bbox_inches="tight") trims each frame to its own content, so a frame
whose kill-chain panel carries different labels came out a different pixel
size. A GIF has one canvas, so mismatched frames jitter and the widest frame's
edges bleed over the narrower ones. Frames are now padded onto a common
canvas. Reproduced and verified on the 7-frame Argo CD metrics.

Suite fix: /tmp is READ-ONLY in argocd-application-controller,
argocd-notifications-controller and argocd-redis, so the drift attack could
never stage its binary — `cp` failed outright, and the leg was only "passing"
because a different attack emitted an alert that satisfied its comm-unpinned
expectation. All three now stage in /dev/shm, which is writable in all seven
containers. Each still tunes to 0, now for the right reason.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jd9m962b1JAHdAy3rGe7nm
The redis kill-chain GIF was a one-off with no generator in the repo, so it
could not be reproduced or made for any other app. This adds the generator and
a config, and renders a card for every SBoB.

scripts/render-rule-coverage-gif.py forces every rule in the cluster ruleset
into exactly one state per app:

  green  verified  asserted by an attack AND observed firing
  amber  probe     exercised but not assertable here — kernel-gated, EPERM,
                   baseline-suppressed, not emitted by this node-agent build
  grey   excluded  deliberately out of scope for this app
  red    gap       in the ruleset, nothing in the suite covers it

That four-way split is the point. A rule that CANNOT fire is not the same as a
rule nobody tried to cover, and a blank tile hides the difference. The red
tiles are the backlog, stated rather than omitted.

With `metrics_glob` pointing at tuner output, only rules the tune actually SAW
firing earn "verified" — an asserted-but-unseen rule drops to gap, so the card
cannot overstate coverage. Probes and exclusions are declared in
kubescape/rule-coverage.yaml with their reason, because a probe attack carries
`expectedDetections: []` and names no rule to derive from.

  make rule-coverage-gifs              # every app
  make rule-coverage-gifs APP=argocd   # one app

redis regenerates to its committed path and reproduces the original: 14 rules
fire, 8 kernel/gated probes, same amber and grey sets. The two differences are
the current ruleset carrying R1016/R1031 (the original was made against an
older 29-rule set with R2001), and R1000 now showing as a gap because nothing
asserts it and there is no evidence it fires here.

argocd-killchain.gif switches to this format — 14 verified, 2 probe, 6
excluded, 8 gap across the seven legs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jd9m962b1JAHdAy3rGe7nm
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SBOB for argocd

1 participant