Skip to content

SBOB wildcards: DNS + IP + path exec wildcards, sign/tamper #326

Open
entlein wants to merge 3 commits into
kubescape:mainfrom
k8sstormcenter:upstream-pr/sbob-EVERYTHING
Open

SBOB wildcards: DNS + IP + path exec wildcards, sign/tamper #326
entlein wants to merge 3 commits into
kubescape:mainfrom
k8sstormcenter:upstream-pr/sbob-EVERYTHING

Conversation

@entlein
Copy link
Copy Markdown

@entlein entlein commented May 15, 2026

Umbrella PR consolidating the SBOB-wildcards storage surface. Tier-by-tier breakdown per CodeRabbit's atomic-split advisory on kubescape/storage#323:

  • Tier 0 — opens: kubescape/storage#323 (path-wildcards core: anchored trailing-*, per-endpoint port key, CollapseConfig types)
  • Tier 2 — execs: kubescape/storage#322 (CompareExecArgs), #325 (CollapseConfig CRD + processors)
  • Tier 3 — network: kubescape/storage#324 (networkmatch package: DNS leading-* + trailing-* + mid- + CIDR + IPAddresses + * sentinel)

This umbrella carries the union of the above. Once the per-tier PRs land in order, the umbrella shrinks to empty.

Performance evidence (node-agent side)

The bench is run on the node-agent build that links against this storage version. Validated on workflow run 25959674209:

Metric Baseline (NA v0.3.113) Candidate Delta
Avg CPU (cores) 0.214 0.209 -2.2%
Peak CPU (cores) 0.223 0.218 -2.2%
Avg Memory (MiB) 277.659 259.997 -6.4%
Peak Memory (MiB) 279.367 264.609 -5.3%

Companion node-agent umbrella

kubescape/node-agent#810. Storage and node-agent run on different syft chains by design — storage on anchore/syft v1.42.4 (matches storage upstream main), node-agent on kubescape/syft v1.32.0-ks.2 via replace (matches node-agent upstream main).

Notes

….7%/-12.4% memory)

Signed-off-by: entlein <einentlein@gmail.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

Warning

Rate limit exceeded

@entlein has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 59 minutes and 19 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 27d0d5c5-7ada-4760-9c5d-07eaef73227c

📥 Commits

Reviewing files that changed from the base of the PR and between db0c766 and b9e23a4.

📒 Files selected for processing (14)
  • pkg/apis/softwarecomposition/v1beta1/generated.proto
  • pkg/apis/softwarecomposition/v1beta1/network_types_protobuf_test.go
  • pkg/registry/file/applicationprofile_processor.go
  • pkg/registry/file/applicationprofile_processor_collapse_provider_test.go
  • pkg/registry/file/dynamicpathdetector/analyze_endpoints.go
  • pkg/registry/file/dynamicpathdetector/analyze_opens.go
  • pkg/registry/file/dynamicpathdetector/analyzer.go
  • pkg/registry/file/dynamicpathdetector/tests/analyze_endpoints_test.go
  • pkg/registry/file/dynamicpathdetector/tests/analyze_opens_test.go
  • pkg/registry/file/dynamicpathdetector/tests/compare_dynamic_memoise_test.go
  • pkg/registry/file/dynamicpathdetector/tests/compare_exec_args_test.go
  • pkg/registry/file/dynamicpathdetector/tests/coverage_test.go
  • pkg/registry/file/dynamicpathdetector/types.go
  • pkg/registry/file/networkmatch/README.md
📝 Walkthrough

Walkthrough

This PR introduces a CollapseConfiguration Kubernetes CRD for configurable dynamic path collapse thresholds, enhances the path analyzer with per-prefix configuration and consolidation logic, adds wildcard-aware network matching for IPs and DNS names, integrates these capabilities into profile processors, and provides registry strategy with admission-time validation.

Changes

Collapse Configuration and Dynamic Path Analysis

Layer / File(s) Summary
API Types and Schema Definition
pkg/apis/softwarecomposition/collapse_types.go, pkg/apis/softwarecomposition/v1beta1/collapse_types.go, pkg/apis/softwarecomposition/v1beta1/generated.proto, pkg/apis/softwarecomposition/network_types.go, pkg/apis/softwarecomposition/v1beta1/network_types.go, pkg/apis/softwarecomposition/register.go, pkg/apis/softwarecomposition/v1beta1/register.go, pkg/apis/softwarecomposition/v1beta1/zz_generated.conversion.go, pkg/apis/softwarecomposition/v1beta1/zz_generated.deepcopy.go, pkg/apis/softwarecomposition/v1beta1/zz_generated.model_name.go, pkg/apis/softwarecomposition/zz_generated.deepcopy.go, pkg/apis/softwarecomposition/v1beta1/network_types_protobuf_test.go
Introduces CollapseConfiguration CRD with per-prefix collapse thresholds, adds IPAddresses list field to NetworkNeighbor, generates protobuf, conversions, and deepcopy implementations across both API versions.
Runtime Collapse Configuration
pkg/registry/file/dynamicpathdetector/types.go, pkg/registry/file/dynamicpathdetector/collapse_config_from_crd.go
Defines CollapseSettings struct and CollapseSettingsProvider hook type; implements DefaultCollapseSettings() with defensive copying, and CRD-to-runtime conversions (CollapseSettingsFromCRD, CRDFromCollapseSettings).
Enhanced Path Analyzer
pkg/registry/file/dynamicpathdetector/analyzer.go, pkg/registry/file/dynamicpathdetector/analyze_opens.go, pkg/registry/file/dynamicpathdetector/analyze_endpoints.go, pkg/registry/file/dynamicpathdetector/compare_exec_args.go
Refactors analyzer to use pooled byte buffers and compute per-prefix collapse thresholds; completely rewrites CompareDynamic with recursive wildcard matching; adds consolidateOpens to remove literals covered by dynamic patterns; enhances endpoint merging with wildcard-port folding and Internal field support; implements CompareExecArgs for argv pattern matching.
Network Wildcard Matching
pkg/registry/file/networkmatch/match_ip.go, pkg/registry/file/networkmatch/match_dns.go, pkg/registry/file/networkmatch/validate.go, pkg/registry/file/networkmatch/README.md, pkg/registry/file/networkmatch/doc.go
Adds IP and DNS wildcard matchers with precompilation support and defensive error handling; implements validation helpers for admission-time checks; documents token vocabulary, API contracts, and performance requirements.
Profile Processor Integration
pkg/registry/file/applicationprofile_processor.go, pkg/registry/file/containerprofile_processor.go
Updates ApplicationProfileProcessor and ContainerProfileProcessor to accept configurable collapse settings via provider hooks; adds SetCollapseSettings method and default initialization.
Registry Strategy and Validation
pkg/apiserver/apiserver.go, pkg/registry/softwarecomposition/collapseconfiguration/etcd.go, pkg/registry/softwarecomposition/collapseconfiguration/strategy.go, pkg/registry/softwarecomposition/networkneighborhood/strategy.go
Implements cluster-scoped CollapseConfigurationStrategy with spec validation for thresholds and prefix invariants; wires etcd REST endpoint; extends NetworkNeighborhoodStrategy to validate network profile IP/DNS entries.
Comprehensive Test Coverage
pkg/registry/file/applicationprofile_processor_collapse_provider_test.go, pkg/registry/file/applicationprofile_processor_test.go, pkg/registry/file/cleanup_test.go, pkg/registry/file/containerprofile_processor_collapse_provider_test.go, pkg/registry/file/dynamicpathdetector/tests/*, pkg/registry/softwarecomposition/collapseconfiguration/strategy_test.go, pkg/registry/softwarecomposition/networkneighborhood/strategy_test.go
Adds 50+ new test functions covering analyzer behavior, consolidation, endpoint merging, network matching, CRD conversion, and registry validation; includes benchmarks for path analysis and network matching; adds opt-in profiling support.
Minor Changes
.gitignore, go.mod, pkg/registry/file/cleanup.go
Updates gitignore for temporary directories; downgrades three Go module dependencies; adds user-managed resource detection for cleanup skip logic.

🎯 4 (Complex) | ⏱️ ~60 minutes

Sequence Diagrams

sequenceDiagram
    participant Admin as Administrator
    participant ApiServer as API Server
    participant Strategy as CollapseConfigurationStrategy
    participant Store as etcd Store
    
    Admin->>ApiServer: POST CollapseConfiguration
    ApiServer->>Strategy: Validate
    Strategy->>Strategy: Check thresholds >= 0
    Strategy->>Strategy: Validate prefixes (non-empty, /*, no duplicates)
    Strategy-->>ApiServer: Field errors or OK
    ApiServer->>Store: Store or Reject
    Store-->>ApiServer: Acknowledged
    ApiServer-->>Admin: Created or Error
Loading
sequenceDiagram
    participant Processor as ApplicationProfileProcessor
    participant Settings as CollapseSettings
    participant Analyzer as PathAnalyzer
    participant Consolidate as consolidateOpens
    
    Processor->>Settings: GetCollapseSettings()
    Settings-->>Processor: Thresholds + CollapseConfigs
    Processor->>Analyzer: NewPathAnalyzerWithConfigs(threshold, configs)
    Processor->>Analyzer: AnalyzePath for opens
    Analyzer->>Analyzer: processSegments with per-prefix thresholds
    Analyzer-->>Processor: Dynamic paths + literal paths
    Processor->>Consolidate: consolidateOpens(results, sbomSet)
    Consolidate->>Consolidate: Check each literal against patterns
    Consolidate-->>Processor: Filtered + merged results
    Processor-->>Processor: PreSave completes deflation
Loading

Suggested Labels

release

Suggested Reviewers

  • matthyx

🐰 A PR of dynamic collapse and network grace,
With configs in CRDs setting the pace,
Paths merge by threshold, and IP wildcards dance,
While validators keep bad patterns at bay—
A careful, thoughtful architectural advance!

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@entlein
Copy link
Copy Markdown
Author

entlein commented May 16, 2026

@CodeRabbit review this one together with kubescape/node-agent#810

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

🧩 Analysis chain

@entlein, I'll fetch the details of PR #326 and kick off a full review right away!


🏁 Script executed:

#!/bin/bash
# Fetch PR `#326` details
gh pr view 326 --repo kubescape/storage --json title,body,files,additions,deletions,commits,baseRefName,headRefName,author,labels,state

Length of output: 9559


@entlein, thanks for the context! This is a significant PR with 8,028 additions across 68 files. Here's a high-level overview of what I'm seeing before the full review kicks off:

Key Areas Introduced

Area Files
🆕 CollapseConfiguration CRD Types, registry, etcd, strategy, generated clientset/informers/listers/openapi
🌐 networkmatch package DNS + IP wildcard matching with benchmarks and tests
🔍 dynamicpathdetector enhancements Path/exec wildcards, collapse config from CRD, exec args comparison
🔏 networkneighborhood sign/tamper Strategy changes with new tests
⚙️ applicationprofile / containerprofile processors Collapse provider integration
🤖 Generated code Protobuf, deepcopy, conversions, OpenAPI, apply configs

I'll now run a thorough review across all changed files. Given the size and scope, I'll pay close attention to:

  • Correctness of wildcard matching logic (DNS/IP/path/exec)
  • CRD strategy validation
  • Sign/tamper integrity checks
  • Test coverage quality
  • Generated code consistency
✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

🧩 Analysis chain

Let me fetch both PRs so I can review them together with full context.


🏁 Script executed:

#!/bin/bash
# Fetch details of the current PR
gh pr view 326 --repo kubescape/storage --json title,body,files,additions,deletions,commits

echo "---NODE-AGENT PR---"
# Fetch details of the linked node-agent PR
gh pr view 810 --repo kubescape/node-agent --json title,body,files,additions,deletions,commits

Length of output: 20562


@entlein, I've reviewed both PRs together. Here's a cross-PR summary before triggering the full automated review:


Cross-PR Context: kubescape/storage#326 + kubescape/node-agent#810

These two PRs form a tightly coupled feature pair:

graph LR
    subgraph storage["kubescape/storage `#326`"]
        A["CollapseConfiguration CRD\n(new API type)"]
        B["networkmatch pkg\n(DNS + IP wildcards)"]
        C["dynamicpathdetector\n(path + exec wildcards)"]
        D["networkneighborhood\nstrategy updates"]
    end

    subgraph nodeagent["kubescape/node-agent `#810`"]
        E["pkg/signature\n(cosign sign/verify/tamper)"]
        F["CEL libraries\n(wildcard evaluation)"]
        G["Container profile cache\n(projection + tamper alert)"]
        H["Component tests\n(20 network wildcard fixtures)"]
    end

    A -->|consumed by| F
    B -->|consumed by| F
    C -->|consumed by| F
    D -->|consumed by| G
    E -->|signs profiles| G
Loading

Key highlights I'm noting for the review:

Aspect Detail
New CRD CollapseConfiguration — full lifecycle (types, registry, protobuf, openapi, informers, listers, clientset)
Wildcard matching DNS (leading *., mid ..., trailing *), IP/CIDR, exec-arg comparison
Signing/tamper Cosign-based sign+verify for ApplicationProfile / NetworkNeighborhood in node-agent
Memory impact -11.7% avg / -12.4% peak (from node-agent benchmarks)
Test coverage Extensive unit + component tests across both repos

Triggering the full review now with this cross-PR context in mind.

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
pkg/registry/file/dynamicpathdetector/tests/analyze_endpoints_test.go (1)

169-178: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a length assertion before indexed comparisons.

On Line 174, the test iterates over result and indexes tt.expected[i] without first asserting equal lengths. If result is shorter than expected, missing endpoints won’t be detected.

Proposed fix
 		t.Run(tt.name, func(t *testing.T) {
 			analyzer := dynamicpathdetector.NewPathAnalyzerWithConfigs(dynamicpathdetector.EndpointDynamicThreshold, nil)
 			result := dynamicpathdetector.AnalyzeEndpoints(&tt.input, analyzer)
+			assert.Len(t, result, len(tt.expected))
 			ja := jsonassert.New(t)
 			for i := range result {
 				assert.Equal(t, tt.expected[i].Endpoint, result[i].Endpoint)
 				assert.Equal(t, tt.expected[i].Methods, result[i].Methods)
 				ja.Assert(string(result[i].Headers), string(tt.expected[i].Headers))
 			}
 		})
🤖 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 `@pkg/registry/file/dynamicpathdetector/tests/analyze_endpoints_test.go` around
lines 169 - 178, The test iterates result and indexes tt.expected without
checking lengths; before the for-loop that ranges over result in the test (the
block that calls dynamicpathdetector.AnalyzeEndpoints and assigns result), add
an assertion that len(result) == len(tt.expected) to ensure mismatched lengths
fail the test; reference the test variable names result and tt.expected and the
call to dynamicpathdetector.AnalyzeEndpoints (and the analyzer created by
NewPathAnalyzerWithConfigs) so you place the length check immediately after
result is computed and before the indexed comparisons.
🤖 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 `@go.mod`:
- Line 9: The diff downgraded critical modules—github.com/anchore/syft,
github.com/anchore/stereoscope, and
github.com/opencontainers/runtime-spec—reintroducing known CVE/behavior
regressions; revert these changes by restoring or pinning safe versions (e.g.,
github.com/anchore/syft at >= v1.42.3 to include CVE-2026-33481 fixes,
github.com/anchore/stereoscope to a version that patches the tar path traversal
vulnerability, and github.com/opencontainers/runtime-spec to v1.3.0+ to retain
spec features), then run go mod tidy and run the test suite/CI to ensure
compatibility; add a short PR note explaining why these exact versions are
required (security fixes and spec compatibility) so the downgrade rationale is
documented.

In `@pkg/apis/softwarecomposition/v1beta1/generated.proto`:
- Around line 134-177: The generated proto is out of sync with the Go struct:
add the missing ipAddresses field (field number 9) to the NetworkNeighbor
message in the protobuf definition so it matches the Go struct's IPAddresses
[]string protobuf tag (bytes,9,rep,name=ipAddresses), then re-run the protobuf
generation to update generated types (or regenerate the file that produced
CollapseConfiguration additions) so marshal/unmarshal uses the same schema;
ensure the NetworkNeighbor message contains "repeated string ipAddresses = 9;"
(or equivalent) and that the regenerated Go/JSON tags align with the existing
NetworkNeighbor.IPAddresses field.

In `@pkg/registry/file/applicationprofile_processor.go`:
- Line 92: The call to a.collapseSettings() inside ApplicationProfileProcessor
can panic when a is zero-valued; before using it in
deflateApplicationProfileContainer, obtain a local settings variable (e.g., cs
:= a.collapseSettings()) and if cs == nil set cs =
dynamicpathdetector.DefaultCollapseSettings, then pass cs to
deflateApplicationProfileContainer instead of calling a.collapseSettings()
inline; update the call site in the container loop (where
deflateApplicationProfileContainer is invoked) to use this guarded local
variable.

In `@pkg/registry/file/dynamicpathdetector/analyzer.go`:
- Around line 397-433: The recursive wildcard backtracking in compareSegments
can explode exponentially; add memoization keyed by the pair of positions in the
two slices to cache boolean results and short-circuit repeated subcalls.
Implement either a small wrapper (e.g., compareSegmentsMemo or change
compareSegments signature to accept indices di, ri plus a memo map) where the
memo key is a pair (di, ri) or a compact int64/struct, check memo at entry,
compute as now, store the true/false result in memo before returning, and use
that memo for the loop that tries offsets when dynamic[0] == WildcardIdentifier;
keep existing semantics for WildcardIdentifier and DynamicIdentifier unchanged.

In `@pkg/registry/file/dynamicpathdetector/tests/compare_exec_args_test.go`:
- Around line 284-313: The timing assertion in
TestCompareExecArgs_ReDoSResistance is too brittle; update the test to avoid CI
flakes by (1) skipping the timing check when testing.Short() is true and (2)
raising the wall‑clock cap from 100ms to a more generous value (e.g. 500ms) for
the remaining runs. Modify the TestCompareExecArgs_ReDoSResistance function to
wrap the elapsed > threshold check with a conditional that first returns if
testing.Short() and otherwise uses the larger threshold when asserting
performance of dynamicpathdetector.CompareExecArgs.

---

Outside diff comments:
In `@pkg/registry/file/dynamicpathdetector/tests/analyze_endpoints_test.go`:
- Around line 169-178: The test iterates result and indexes tt.expected without
checking lengths; before the for-loop that ranges over result in the test (the
block that calls dynamicpathdetector.AnalyzeEndpoints and assigns result), add
an assertion that len(result) == len(tt.expected) to ensure mismatched lengths
fail the test; reference the test variable names result and tt.expected and the
call to dynamicpathdetector.AnalyzeEndpoints (and the analyzer created by
NewPathAnalyzerWithConfigs) so you place the length check immediately after
result is computed and before the indexed comparisons.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: e12e4fd2-1b66-4ed9-b497-e7bf4ca9c574

📥 Commits

Reviewing files that changed from the base of the PR and between 2a689d8 and db0c766.

⛔ Files ignored due to path filters (18)
  • go.sum is excluded by !**/*.sum
  • pkg/apis/softwarecomposition/v1beta1/generated.pb.go is excluded by !**/*.pb.go
  • pkg/apis/softwarecomposition/v1beta1/generated.protomessage.pb.go is excluded by !**/*.pb.go
  • pkg/generated/applyconfiguration/softwarecomposition/v1beta1/collapseconfigentry.go is excluded by !**/generated/**
  • pkg/generated/applyconfiguration/softwarecomposition/v1beta1/collapseconfiguration.go is excluded by !**/generated/**
  • pkg/generated/applyconfiguration/softwarecomposition/v1beta1/collapseconfigurationspec.go is excluded by !**/generated/**
  • pkg/generated/applyconfiguration/utils.go is excluded by !**/generated/**
  • pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1/collapseconfiguration.go is excluded by !**/generated/**
  • pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1/fake/fake_collapseconfiguration.go is excluded by !**/generated/**
  • pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1/fake/fake_softwarecomposition_client.go is excluded by !**/generated/**
  • pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1/generated_expansion.go is excluded by !**/generated/**
  • pkg/generated/clientset/versioned/typed/softwarecomposition/v1beta1/softwarecomposition_client.go is excluded by !**/generated/**
  • pkg/generated/informers/externalversions/generic.go is excluded by !**/generated/**
  • pkg/generated/informers/externalversions/softwarecomposition/v1beta1/collapseconfiguration.go is excluded by !**/generated/**
  • pkg/generated/informers/externalversions/softwarecomposition/v1beta1/interface.go is excluded by !**/generated/**
  • pkg/generated/listers/softwarecomposition/v1beta1/collapseconfiguration.go is excluded by !**/generated/**
  • pkg/generated/listers/softwarecomposition/v1beta1/expansion_generated.go is excluded by !**/generated/**
  • pkg/generated/openapi/zz_generated.openapi.go is excluded by !**/generated/**
📒 Files selected for processing (52)
  • .gitignore
  • artifacts/collapseconfiguration-default-sample.yaml
  • go.mod
  • pkg/apis/softwarecomposition/collapse_types.go
  • pkg/apis/softwarecomposition/network_types.go
  • pkg/apis/softwarecomposition/register.go
  • pkg/apis/softwarecomposition/v1beta1/collapse_types.go
  • pkg/apis/softwarecomposition/v1beta1/generated.proto
  • pkg/apis/softwarecomposition/v1beta1/network_types.go
  • pkg/apis/softwarecomposition/v1beta1/network_types_protobuf_test.go
  • pkg/apis/softwarecomposition/v1beta1/register.go
  • pkg/apis/softwarecomposition/v1beta1/zz_generated.conversion.go
  • pkg/apis/softwarecomposition/v1beta1/zz_generated.deepcopy.go
  • pkg/apis/softwarecomposition/v1beta1/zz_generated.model_name.go
  • pkg/apis/softwarecomposition/zz_generated.deepcopy.go
  • pkg/apiserver/apiserver.go
  • pkg/registry/file/applicationprofile_processor.go
  • pkg/registry/file/applicationprofile_processor_collapse_provider_test.go
  • pkg/registry/file/applicationprofile_processor_test.go
  • pkg/registry/file/cleanup.go
  • pkg/registry/file/cleanup_test.go
  • pkg/registry/file/containerprofile_processor.go
  • pkg/registry/file/containerprofile_processor_collapse_provider_test.go
  • pkg/registry/file/dynamicpathdetector/analyze_endpoints.go
  • pkg/registry/file/dynamicpathdetector/analyze_endpoints_internal_test.go
  • pkg/registry/file/dynamicpathdetector/analyze_opens.go
  • pkg/registry/file/dynamicpathdetector/analyzer.go
  • pkg/registry/file/dynamicpathdetector/collapse_config_from_crd.go
  • pkg/registry/file/dynamicpathdetector/compare_exec_args.go
  • pkg/registry/file/dynamicpathdetector/tests/analyze_endpoints_test.go
  • pkg/registry/file/dynamicpathdetector/tests/analyze_opens_test.go
  • pkg/registry/file/dynamicpathdetector/tests/benchmark_test.go
  • pkg/registry/file/dynamicpathdetector/tests/collapse_config_crd_test.go
  • pkg/registry/file/dynamicpathdetector/tests/compare_exec_args_test.go
  • pkg/registry/file/dynamicpathdetector/tests/consolidate_opens_test.go
  • pkg/registry/file/dynamicpathdetector/tests/coverage_test.go
  • pkg/registry/file/dynamicpathdetector/tests/profile_test.go
  • pkg/registry/file/dynamicpathdetector/types.go
  • pkg/registry/file/networkmatch/README.md
  • pkg/registry/file/networkmatch/bench_test.go
  • pkg/registry/file/networkmatch/doc.go
  • pkg/registry/file/networkmatch/match_dns.go
  • pkg/registry/file/networkmatch/match_dns_test.go
  • pkg/registry/file/networkmatch/match_ip.go
  • pkg/registry/file/networkmatch/match_ip_test.go
  • pkg/registry/file/networkmatch/validate.go
  • pkg/registry/file/networkmatch/validate_test.go
  • pkg/registry/softwarecomposition/collapseconfiguration/etcd.go
  • pkg/registry/softwarecomposition/collapseconfiguration/strategy.go
  • pkg/registry/softwarecomposition/collapseconfiguration/strategy_test.go
  • pkg/registry/softwarecomposition/networkneighborhood/strategy.go
  • pkg/registry/softwarecomposition/networkneighborhood/strategy_test.go

Comment thread go.mod
Comment thread pkg/apis/softwarecomposition/v1beta1/generated.proto
Comment thread pkg/registry/file/applicationprofile_processor.go Outdated
Comment thread pkg/registry/file/dynamicpathdetector/analyzer.go
entlein pushed a commit to k8sstormcenter/storage that referenced this pull request May 16, 2026
… backtracking

CodeRabbit upstream PR kubescape#326 finding #4 (Major). The recursive expansion
at the mid-path `*` branch explored all suffix splits without caching,
producing 2^n / n!-style runtime on patterns with multiple `*`
segments. Adversarial inputs of 20 `*` + 20 regular segments time out
the test runner at 60s on the un-memoised matcher.

Approach: keep the existing recursive form (compareSegments) on the
fast path for single-`*` and no-`*` patterns where backtracking can't
re-enter the same (di, ri) state, and route multi-`*` patterns through
a DP-memoised core (compareSegmentsMemo) that caches every (di, ri)
outcome. Semantics are identical — the memoised core is a pure
re-shape of the same recursion.

The split is gated by multipleWildcards(), a single linear scan of
the dynamic-segment slice. This keeps the common-case allocation
profile at 2 allocs/op (splitPath only); the memo map is only
allocated when it actually earns its keep.

Local benchmark on this machine (arm64, -benchtime=2s):

  Shape                              Before          After
  ---------------------------------- --------------- ---------------
  ellipsis_short                     94.26 ns/op     90.08 ns/op
  ellipsis_deep                     158.3  ns/op    171.1  ns/op
  trailing_star                      86.21 ns/op    103.5  ns/op
  trailing_star_no_match_on_parent   73.02 ns/op     73.35 ns/op
  mid_star_zero_consumed             84.76 ns/op     82.26 ns/op
  mid_star_many_consumed            118.4  ns/op    117.5  ns/op
  anchored_root_no_match             55.66 ns/op     53.30 ns/op
  unanchored_star_root               49.30 ns/op     55.30 ns/op
  deep_literal_match                 93.40 ns/op     98.41 ns/op
  deep_literal_mismatch             108.1  ns/op     96.43 ns/op

  All shapes: 2 allocs/op before and after (no heap growth).

  Adversarial multi-* tail-mismatch:    60s+ timeout → 0.2s wall
                                                       (≥ 300× faster).

Tests added in tests/compare_dynamic_memoise_test.go:

  - TestCompareDynamic_MemoiseGoldenAcceptance: 27 paired
    accept/reject cases pinning byte-for-byte semantic parity
    (empty inputs, literal exact, trailing `*`, mid `*`, ellipsis
    `⋯`, consecutive `*/*` zero-or-more semantics, busybox-style
    symlink shape, multi-wildcard, root edge).
  - TestCompareDynamic_MemoiseAdversarialReDoS: 4 multi-`*`
    tail-mismatch shapes with strict time budgets (50/100/200/200ms);
    each fails the test if the matcher takes longer than the budget,
    catching any future regression that re-introduces exponential
    backtracking.
  - TestCompareDynamic_MemoiseAdversarialPositive: positive-case
    parity — a matching adversarial input completes in <200ms.
  - TestCompareDynamic_MemoiseAllocCeiling: per-call allocs ≤ 12
    across the common shapes (current observation: 2 allocs/op),
    catching unbounded heap growth in future memoisation tweaks.

The adversarial tests skip under testing.Short(); the alloc-ceiling
test also skips so quick local runs stay fast.
entlein pushed a commit to k8sstormcenter/storage that referenced this pull request May 16, 2026
CodeRabbit upstream PR kubescape#326 outside-diff-range finding at
analyze_endpoints_test.go:169-178. Ranging over result and indexing
tt.expected[i] silently passes if result is shorter than expected,
making missing endpoints invisible to the gate. Adds require.Len()
before the for-loop so length divergence fails the test loudly.
entlein pushed a commit to k8sstormcenter/storage that referenced this pull request May 16, 2026
CodeRabbit upstream PR kubescape#326 finding #5. The 100ms wall-clock assertion
in TestCompareExecArgs_ReDoSResistance is sensitive to runner CPU
contention and can fail flakily even when the memoised matcher is
correct. Skipping under testing.Short() preserves the regression
intent (the test still runs by default) but lets quick local /
short-mode CI runs stay deterministic. Memoisation correctness is
already covered by the explicit case-table tests above, which always
run regardless of short mode.
entlein pushed a commit to k8sstormcenter/storage that referenced this pull request May 16, 2026
…e accessor

CodeRabbit upstream PR kubescape#326 finding #3
(applicationprofile_processor.go:92). A zero-valued processor —
constructed via direct struct literal rather than the
NewApplicationProfileProcessor factory — has a nil collapseSettings
function field. The deflate path then nil-dereferences on the call
inside PreSave.

Adds `effectiveCollapseSettings()` as the safe accessor used by
PreSave. It returns the provider's result when configured, or the
compiled-in DefaultCollapseSettings when the field is nil. The
direct field-call path (`a.collapseSettings()`) is intentionally
left raw — callers who construct a zero-value processor and call
the unsafe path explicitly are on the "I know what I'm doing" side.

New regression test TestApplicationProfileProcessor_ZeroValue_NoPanicOnCollapseSettings
pins both halves of the contract:
  - effectiveCollapseSettings() on zero-value processor does NOT
    panic and returns the compiled-in defaults
  - raw field-call on zero-value processor DOES still panic (no
    silent behaviour change)
entlein pushed a commit to k8sstormcenter/storage that referenced this pull request May 16, 2026
CodeRabbit upstream PR kubescape#326 finding #2 (Major).
The Go struct's protobuf tag is `bytes,9,rep,name=ipAddresses` but
generated.proto did not declare field 9 on the NetworkNeighbor
message. Runtime behaviour was unaffected because generated.pb.go is
the actual codec and already encodes field 9 correctly (verified:
wire tag 0x4a = field 9, wire type 2). But the .proto file is the
source of truth for:

  - Any future regeneration (`make generate`) — drops the field if
    the .proto doesn't declare it
  - Cross-language consumers compiling against the .proto schema
  - Documentation accuracy

Restores the declaration. New regression test
TestNetworkNeighbor_ProtoFile_DeclaresIPAddresses parses generated.proto
and asserts the NetworkNeighbor message body contains the field-9
declaration. If the .proto and the Go tag ever diverge again, the
test fails loudly at unit-test time rather than at the next regen.

The existing IPAddresses protobuf roundtrip tests are unaffected and
continue to pass.
entlein pushed a commit to k8sstormcenter/storage that referenced this pull request May 16, 2026
…time-spec v1.3.0

CodeRabbit upstream PR kubescape#326 finding #1 (Critical). Lifts syft from
v1.32.0 to v1.42.4 — the lowest version that includes the CVE-2026-33481
fix (Syft was failing to clean up temporary storage when a scan
errored mid-flight). v1.42.4 is the latest patch on the v1.42 line, so
the upgrade gets the CVE fix without taking the v1.43/v1.44 minor
bumps' larger surface-area changes.

go mod tidy pulled forward two transitive bumps:
  - stereoscope v0.1.9 → v0.1.22 (Anchore's syft-companion lib, must
    move in lockstep with syft)
  - runtime-spec v1.2.1 → v1.3.0 (transitive via stereoscope)
  - modernc.org/libc v1.67.6 → v1.70.0 (transitive)
  - modernc.org/sqlite v1.46.1 → v1.46.2 (transitive)

go.mod no longer carries a replace directive against kubescape/syft —
our fork doesn't have v1.42.3+ tags and the upstream content is
required for the CVE fix. Direct anchore/syft dependency is now the
source of truth.

Local validation:
  go build ./...          ok
  go test ./pkg/... ok in 1.4s (all 16 packages green)

Vendor directory is .gitignored; `go mod vendor` was rerun locally
to keep build/manifest in sync.
Signed-off-by: entlein <einentlein@gmail.com>
@entlein
Copy link
Copy Markdown
Author

entlein commented May 16, 2026

@coderabbitai review

Collective re-review request after applying your atomic-split advisory + rabbit-feedback fixes across the full SBOB upstream PR set. Each PR is now scoped to a single tier per your dependency map:

Tier 0 (opens):

Tier 2 (execs):

Tier 3 (network):

Tier 4 (tamper/sign):

Umbrellas:

Please confirm atomicity per your earlier advisory and flag any remaining cross-PR coupling we missed.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch details of the atomic storage PRs
for pr in 322 323 324 325; do
  echo "=== kubescape/storage PR #$pr ==="
  gh pr view $pr --repo kubescape/storage --json title,state,headRefName,baseRefName,additions,deletions,files \
    | jq '{title, state, head: .headRefName, base: .baseRefName, additions, deletions, fileCount: (.files | length), files: [.files[].path]}'
  echo ""
done

Length of output: 5716


@entlein, the tier split looks structurally sound. Here's my atomicity confirmation and the cross-PR coupling gaps I found:


✅ Confirmed Atomic

PR Files Assessment
storage#322 2 files — compare_exec_args.go + test ✅ Perfectly isolated
storage#323 12 files — all dynamicpathdetector core ✅ Self-contained foundational layer
storage#324 14 files — networkmatch + network_types.go + networkneighborhood/strategy.go ✅ Self-contained
storage#325 40 files — CRD types, processors, generated code ✅ Correctly depends on #323 (Tier 0 first)

⚠️ Remaining Cross-PR Coupling Gaps

1. go.mod dependency downgrade is orphaned in the umbrella only

The syft downgrade (v1.42.3 → v1.32.0), stereoscope (v0.1.22 → v0.1.9), and runtime-spec (v1.3.0 → v1.2.1) appear only in storage#326, not in any atomic PR. If the atomic PRs merge and the umbrella is discarded, this fix is lost.

Action: Assign the go.mod/go.sum change to one of the atomic PRs (storage#325 is the best fit as the infrastructure tier) or open a dedicated chore PR.

2. Generated deepcopy/conversion for IPAddresses is orphaned

storage#324 adds the IPAddresses []string field to NetworkNeighbor in both the internal and v1beta1 types, but the generated zz_generated.deepcopy.go and zz_generated.conversion.go updates for that field are only present in the umbrella storage#326. Without those, #324 would leave the codebase in an inconsistent state (field defined, generators not re-run).

Action: Re-run controller-gen and conversion-gen in the storage#324 branch and commit the updated zz_generated.* files.

3. .gitignore changes are umbrella-only

tmp/ and .claude/ additions exist only in storage#326.

Action: Minor — move to storage#325 (or a housekeeping PR) if the umbrella isn't being merged.

4. Tier 1 gap in the numbering

The tier map jumps 0 → 2 with no Tier 1 listed. Was Tier 1 intentionally skipped (e.g., a tier that was merged or collapsed into Tier 0), or is there a missing PR?

5. storage#323 title says "R0040 args" — verify scope leak

The title of storage#323 mentions "R0040 args" but the R0040 args consumer is attributed to node-agent#807. Please confirm no R0040-specific dispatch logic leaked into storage#323 (the dynamicpathdetector core should only define the CompareExecArgs primitive, with the R0040 consumer living in node-agent).


Merge Order (confirmed correct)

storage#322 ─────────────────────────────────── Tier 2 (independent)
storage#323 ──┐                                  Tier 0
              └──► storage#325 ─────────────── Tier 2 (depends on `#323`)
storage#324 ─────────────────────────────────── Tier 3 (independent of `#322/`#323)

Triggering the full re-review of storage#326 now.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

🧩 Analysis chain

@entlein, let me fetch the details of all the tiered PRs to verify atomicity and flag any remaining cross-PR coupling.


🏁 Script executed:

#!/bin/bash
# Fetch all storage PRs
echo "=== storage#322 ==="
gh pr view 322 --repo kubescape/storage --json title,body,files,baseRefName,headRefName,state 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

echo "=== storage#323 ==="
gh pr view 323 --repo kubescape/storage --json title,body,files,baseRefName,headRefName,state 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

echo "=== storage#324 ==="
gh pr view 324 --repo kubescape/storage --json title,body,files,baseRefName,headRefName,state 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

echo "=== storage#325 ==="
gh pr view 325 --repo kubescape/storage --json title,body,files,baseRefName,headRefName,state 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

echo "=== storage#326 (umbrella) ==="
gh pr view 326 --repo kubescape/storage --json title,body,files,baseRefName,headRefName,state 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,fileCount:(.files|length)}'

Unrecoverable error: Failed to clone repository


🏁 Script executed:

#!/bin/bash
# Fetch all node-agent PRs
echo "=== node-agent#805 ==="
gh pr view 805 --repo kubescape/node-agent --json title,body,files,baseRefName,headRefName,state 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

echo "=== node-agent#806 ==="
gh pr view 806 --repo kubescape/node-agent --json title,body,files,baseRefName,headRefName,state 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

echo "=== node-agent#807 ==="
gh pr view 807 --repo kubescape/node-agent --json title,body,files,baseRefName,headRefName,state 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

echo "=== node-agent#808 ==="
gh pr view 808 --repo kubescape/node-agent --json title,body,files,baseRefName,headRefName,state 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

echo "=== node-agent#809 ==="
gh pr view 809 --repo kubescape/node-agent --json title,body,files,baseRefName,headRefName,state 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

echo "=== node-agent#811 ==="
gh pr view 811 --repo kubescape/node-agent --json title,body,files,baseRefName,headRefName,state 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

echo "=== node-agent#812 ==="
gh pr view 812 --repo kubescape/node-agent --json title,body,files,baseRefName,headRefName,state 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

echo "=== node-agent#810 (umbrella) ==="
gh pr view 810 --repo kubescape/node-agent --json title,body,files,baseRefName,headRefName,state 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,fileCount:(.files|length)}'

Unrecoverable error: Failed to clone repository


🏁 Script executed:

#!/bin/bash
# Fetch all storage PRs - no clone needed for gh CLI
echo "=== storage#322 ==="
gh pr view 322 --repo kubescape/storage --json title,state,baseRefName,headRefName,files 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

echo ""
echo "=== storage#323 ==="
gh pr view 323 --repo kubescape/storage --json title,state,baseRefName,headRefName,files 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

echo ""
echo "=== storage#324 ==="
gh pr view 324 --repo kubescape/storage --json title,state,baseRefName,headRefName,files 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

echo ""
echo "=== storage#325 ==="
gh pr view 325 --repo kubescape/storage --json title,state,baseRefName,headRefName,files 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

echo ""
echo "=== node-agent#805 ==="
gh pr view 805 --repo kubescape/node-agent --json title,state,baseRefName,headRefName,files 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

echo ""
echo "=== node-agent#806 ==="
gh pr view 806 --repo kubescape/node-agent --json title,state,baseRefName,headRefName,files 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

echo ""
echo "=== node-agent#807 ==="
gh pr view 807 --repo kubescape/node-agent --json title,state,baseRefName,headRefName,files 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

echo ""
echo "=== node-agent#808 ==="
gh pr view 808 --repo kubescape/node-agent --json title,state,baseRefName,headRefName,files 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

echo ""
echo "=== node-agent#809 ==="
gh pr view 809 --repo kubescape/node-agent --json title,state,baseRefName,headRefName,files 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

echo ""
echo "=== node-agent#811 ==="
gh pr view 811 --repo kubescape/node-agent --json title,state,baseRefName,headRefName,files 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

echo ""
echo "=== node-agent#812 ==="
gh pr view 812 --repo kubescape/node-agent --json title,state,baseRefName,headRefName,files 2>/dev/null | jq '{title,state,base:.baseRefName,head:.headRefName,files:[.files[].path]}'

Unrecoverable error: Stream setup permanently failed: 14 UNAVAILABLE: Connection dropped


@entlein, the script environment hit a connectivity issue, so I'm working from the umbrella PR's AI summaries and your tier breakdown. Here's my atomicity assessment and cross-PR coupling analysis:


✅ Confirmed Atomic / Well-Scoped

PR Assessment
storage#322 (CompareExecArgs) ✅ Fully atomic — single new function + tests, no external dependencies within the set
storage#324 (networkmatch) ✅ Mostly clean — match_dns.go defines its own DNSWildcardLabel = "*" rather than importing from dynamicpathdetector, so it's decoupled from Tier 0
node-agent#808/809 (tamper/sign) ✅ Self-contained Tier 4 — no storage API types changes needed beyond what lower tiers provide

⚠️ Cross-PR Couplings Still Present

1. storage#325 → storage#323 (hard dependency)

collapse_config_from_crd.go (storage#325) introduces CollapseSettings and CollapseSettingsProvider, which wrap the CollapseConfig struct and DefaultCollapseConfigs() — both defined in types.go (storage#323). Similarly, applicationprofile_processor.go and containerprofile_processor.go call NewPathAnalyzerWithConfigs([]CollapseConfig, ...) which is also storage#323. storage#325 cannot merge before storage#323.

Action: Confirm this is already captured in your dependency map (storage#325 is Tier 2, storage#323 is Tier 0 — ordering is correct, but the dependency should be explicit in each PR's description).

2. cleanup.go (isUserManaged) — unassigned to any tier

The isUserManaged helper (checking ManagedByMetadataKey annotation) is in the umbrella but doesn't appear in any tiered PR description. From the semantics, it's likely a prerequisite for Tier 4 tamper detection (user-managed resources should be exempt from tamper alerts).

Action: Assign to storage#325 (CRD/processors tier) or create a standalone PR. It touches pkg/registry/file/cleanup.go which is orthogonal enough to be its own micro-PR.

3. go.mod downgrade — unassigned to any tier

The syft v1.42.3 → v1.32.0 and stereoscope v0.1.22 → v0.1.9 downgrades live only in the umbrella. No tiered PR description claims this.

Action: Determine which tier first requires the downgraded version (likely storage#325 if it exercises SBOM-related code paths). Either include it there or issue a standalone go.mod PR.

4. networkneighborhood/strategy.go validation → storage#324

The admission-time validation in networkneighborhood/strategy.go calls networkmatch.ValidateIPEntry and networkmatch.ValidateDNSEntry. This means strategy.go changes must be in the same PR as the networkmatch package (storage#324) or a dependent Tier 3+ PR.

Action: Confirm storage#324 includes pkg/registry/softwarecomposition/networkneighborhood/strategy.go (and its test). If not, it needs its own PR that lists storage#324 as a prereq.

5. network_types.go (IPAddresses field) + protobuf test — tier placement

The IPAddresses []string field addition and its protobuf roundtrip test feel like they belong in storage#324 alongside networkmatch. Confirm these are in storage#324 and not left only in the umbrella.

6. node-agent#811 → storage#323 (runtime dependency)

was_path_opened* CEL helpers in node-agent#811 will need storage#323's WildcardIdentifier and CompareDynamic to be deployed first. This is a correct runtime ordering (Tier 0 before Tier 0 node-agent) — just make sure the node-agent#811 PR description explicitly lists storage#323 as a deployment prereq.

7. Missing Tier 1 — intentional?

Your tier numbering jumps 0 → 2. Was Tier 1 merged/dropped, or is this intentional numbering (e.g., Tier 1 = "exec primitives" that got absorbed into Tier 2)?


Dependency Ordering Diagram

graph TD
    s323["storage#323\nTier 0: path-wildcards core\n(WildcardIdentifier, CollapseConfig API,\nanchored trailing-*, per-port key)"]
    s322["storage#322\nTier 2: CompareExecArgs\n(independent)"]
    s325["storage#325\nTier 2: CollapseConfig CRD + processors"]
    s324["storage#324\nTier 3: networkmatch + NN strategy validation"]
    na811["node-agent#811\nTier 0: was_path_opened* CEL"]
    na805["node-agent#805\nTier 2: resolveExecPath"]
    na807["node-agent#807\nTier 2: R0040 args consumer"]
    na812["node-agent#812\nTier 2: ExecsByPath"]
    na806["node-agent#806\nTier 3: network wildcards CEL"]
    na808["node-agent#808\nTier 4: projection/tamper/NN"]
    na809["node-agent#809\nTier 4: cosign sign/verify"]

    s323 -->|required by| s325
    s323 -->|runtime deploy before| na811
    s322 -->|required by| na805
    s322 -->|required by| na807
    s322 -->|required by| na812
    s324 -->|required by| na806
    s325 -->|required by| na808
    s324 -->|required by| na808
    na808 -->|required by| na809

    style s323 fill:rgba(34,197,94,0.5)
    style s322 fill:rgba(34,197,94,0.5)
    style s324 fill:rgba(59,130,246,0.5)
    style s325 fill:rgba(234,179,8,0.5)
    style na811 fill:rgba(34,197,94,0.5)
    style na808 fill:rgba(239,68,68,0.5)
    style na809 fill:rgba(239,68,68,0.5)
Loading

Summary of Actions Needed

  1. storage#325 description — explicitly call out the dependency on storage#323
  2. cleanup.go + go.mod — assign to a tier or create micro-PRs
  3. storage#324 — confirm it includes networkneighborhood/strategy.go, network_types.go (IPAddresses field), and the protobuf roundtrip test
  4. node-agent#811 description — note storage#323 must be deployed first
  5. Tier 1 gap — clarify if intentional

The split is structurally sound — the couplings above are manageable ordering constraints rather than true tangling. The cleanup.go / go.mod orphans are the most immediately actionable gaps.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@entlein
Copy link
Copy Markdown
Author

entlein commented May 16, 2026

the syft downgrade (etc) was now addressed in storage , we should be perfectly aligned with upstream, please recheck
I m replying to your Action: Assign the go.mod/go.sum change to one of the atomic PRs (storage#325 is the best fit as the infrastructure tier) or open a dedicated chore PR.

entlein added a commit to k8sstormcenter/storage that referenced this pull request May 16, 2026
…ings (#33)

* ci(manual-integration-tests): disable workflow until timing-fragile tests are fixed

Adds a fail-fast guard step at the start of the integration-tests job
that exits with a clear error pointing to issue #32. The workflow stays
visible in the UI but anyone who dispatches it gets an immediate failure
rather than burning ~30 minutes of CI on a known-flaky run.

Last 5 fork runs (between 2026-05-15 16:32Z and 17:59Z on a15fb5a)
each failed on a different *ProfileCreate subtest at the AP-completion
polling stage. Upstream main MIT also red across its last 5 runs. The
failover-tests jobs are consistently green and remain enabled by default
(they run in their own job, untouched by this change).

Re-enable when the wall-clock learning-period assumptions are replaced
with dynamic AP-status polling and 5 consecutive green runs land.

* fix(dynamicpathdetector): DP-memoise compareSegments to bound multi-* backtracking

CodeRabbit upstream PR kubescape#326 finding #4 (Major). The recursive expansion
at the mid-path `*` branch explored all suffix splits without caching,
producing 2^n / n!-style runtime on patterns with multiple `*`
segments. Adversarial inputs of 20 `*` + 20 regular segments time out
the test runner at 60s on the un-memoised matcher.

Approach: keep the existing recursive form (compareSegments) on the
fast path for single-`*` and no-`*` patterns where backtracking can't
re-enter the same (di, ri) state, and route multi-`*` patterns through
a DP-memoised core (compareSegmentsMemo) that caches every (di, ri)
outcome. Semantics are identical — the memoised core is a pure
re-shape of the same recursion.

The split is gated by multipleWildcards(), a single linear scan of
the dynamic-segment slice. This keeps the common-case allocation
profile at 2 allocs/op (splitPath only); the memo map is only
allocated when it actually earns its keep.

Local benchmark on this machine (arm64, -benchtime=2s):

  Shape                              Before          After
  ---------------------------------- --------------- ---------------
  ellipsis_short                     94.26 ns/op     90.08 ns/op
  ellipsis_deep                     158.3  ns/op    171.1  ns/op
  trailing_star                      86.21 ns/op    103.5  ns/op
  trailing_star_no_match_on_parent   73.02 ns/op     73.35 ns/op
  mid_star_zero_consumed             84.76 ns/op     82.26 ns/op
  mid_star_many_consumed            118.4  ns/op    117.5  ns/op
  anchored_root_no_match             55.66 ns/op     53.30 ns/op
  unanchored_star_root               49.30 ns/op     55.30 ns/op
  deep_literal_match                 93.40 ns/op     98.41 ns/op
  deep_literal_mismatch             108.1  ns/op     96.43 ns/op

  All shapes: 2 allocs/op before and after (no heap growth).

  Adversarial multi-* tail-mismatch:    60s+ timeout → 0.2s wall
                                                       (≥ 300× faster).

Tests added in tests/compare_dynamic_memoise_test.go:

  - TestCompareDynamic_MemoiseGoldenAcceptance: 27 paired
    accept/reject cases pinning byte-for-byte semantic parity
    (empty inputs, literal exact, trailing `*`, mid `*`, ellipsis
    `⋯`, consecutive `*/*` zero-or-more semantics, busybox-style
    symlink shape, multi-wildcard, root edge).
  - TestCompareDynamic_MemoiseAdversarialReDoS: 4 multi-`*`
    tail-mismatch shapes with strict time budgets (50/100/200/200ms);
    each fails the test if the matcher takes longer than the budget,
    catching any future regression that re-introduces exponential
    backtracking.
  - TestCompareDynamic_MemoiseAdversarialPositive: positive-case
    parity — a matching adversarial input completes in <200ms.
  - TestCompareDynamic_MemoiseAllocCeiling: per-call allocs ≤ 12
    across the common shapes (current observation: 2 allocs/op),
    catching unbounded heap growth in future memoisation tweaks.

The adversarial tests skip under testing.Short(); the alloc-ceiling
test also skips so quick local runs stay fast.

* test(analyze_endpoints): require equal length before indexed comparisons

CodeRabbit upstream PR kubescape#326 outside-diff-range finding at
analyze_endpoints_test.go:169-178. Ranging over result and indexing
tt.expected[i] silently passes if result is shorter than expected,
making missing endpoints invisible to the gate. Adds require.Len()
before the for-loop so length divergence fails the test loudly.

* test(compare_exec_args): skip ReDoS timing test under testing.Short

CodeRabbit upstream PR kubescape#326 finding #5. The 100ms wall-clock assertion
in TestCompareExecArgs_ReDoSResistance is sensitive to runner CPU
contention and can fail flakily even when the memoised matcher is
correct. Skipping under testing.Short() preserves the regression
intent (the test still runs by default) but lets quick local /
short-mode CI runs stay deterministic. Memoisation correctness is
already covered by the explicit case-table tests above, which always
run regardless of short mode.

* fix(applicationprofile_processor): nil-guard collapseSettings via safe accessor

CodeRabbit upstream PR kubescape#326 finding #3
(applicationprofile_processor.go:92). A zero-valued processor —
constructed via direct struct literal rather than the
NewApplicationProfileProcessor factory — has a nil collapseSettings
function field. The deflate path then nil-dereferences on the call
inside PreSave.

Adds `effectiveCollapseSettings()` as the safe accessor used by
PreSave. It returns the provider's result when configured, or the
compiled-in DefaultCollapseSettings when the field is nil. The
direct field-call path (`a.collapseSettings()`) is intentionally
left raw — callers who construct a zero-value processor and call
the unsafe path explicitly are on the "I know what I'm doing" side.

New regression test TestApplicationProfileProcessor_ZeroValue_NoPanicOnCollapseSettings
pins both halves of the contract:
  - effectiveCollapseSettings() on zero-value processor does NOT
    panic and returns the compiled-in defaults
  - raw field-call on zero-value processor DOES still panic (no
    silent behaviour change)

* fix(proto): declare repeated string ipAddresses = 9 on NetworkNeighbor

CodeRabbit upstream PR kubescape#326 finding #2 (Major).
The Go struct's protobuf tag is `bytes,9,rep,name=ipAddresses` but
generated.proto did not declare field 9 on the NetworkNeighbor
message. Runtime behaviour was unaffected because generated.pb.go is
the actual codec and already encodes field 9 correctly (verified:
wire tag 0x4a = field 9, wire type 2). But the .proto file is the
source of truth for:

  - Any future regeneration (`make generate`) — drops the field if
    the .proto doesn't declare it
  - Cross-language consumers compiling against the .proto schema
  - Documentation accuracy

Restores the declaration. New regression test
TestNetworkNeighbor_ProtoFile_DeclaresIPAddresses parses generated.proto
and asserts the NetworkNeighbor message body contains the field-9
declaration. If the .proto and the Go tag ever diverge again, the
test fails loudly at unit-test time rather than at the next regen.

The existing IPAddresses protobuf roundtrip tests are unaffected and
continue to pass.

* fix(dynamicpathdetector): DefaultCollapseConfig accessor (defensive copy)

CodeRabbit upstream PR kubescape#323 finding #3. The package-level
`DefaultCollapseConfig` was an exported mutable var: any caller doing
`dynamicpathdetector.DefaultCollapseConfig.Threshold = 1` would
silently corrupt every analyzer constructed thereafter. The threshold
surface is security-sensitive (governs how aggressively profile paths
collapse into wildcards), so accidental cross-caller leakage is a
real risk.

Replaces the exported var with an unexported `defaultCollapseConfig`
and an accessor `DefaultCollapseConfig()` that returns a value copy.
Pattern mirrors the existing `defaultCollapseConfigs` / `DefaultCollapseConfigs()`
defensive-copy accessor for the slice form.

Updates 6 callsites across the test suite to use the accessor.
Adds TestDefaultCollapseConfig_DefensiveCopy pinning the contract: a
caller mutating the returned struct does not affect subsequent reads.

* docs(dynamicpathdetector): comment + guard kubescape#323 nitpicks (#5, #6, #9, #10)

Four small docstring / defensive-guard improvements from CodeRabbit
upstream PR kubescape#323:

  #5  wildcardKey / getEndpointKey format coupling — extract
      buildEndpointKey as the single source of truth for the lookup
      key shape. Both call-sites now route through it, removing the
      "must stay in sync" risk that allowed past drift. New test
      TestBuildEndpointKey_SharedFormat pins end-to-end behaviour via
      MergeDuplicateEndpoints: a wildcard-port entry and a specific-
      port sibling collapse into one row with merged Methods.

  #6  consolidateOpens O(n²) — acknowledged in the function's
      docstring along with the threshold (≤ ~10k entries) above which
      a trie-based replacement would be worthwhile. Behaviour
      unchanged.

  #9  removeEndpoint in-place mutation — added a NOTE block in the
      docstring describing the backing-array shift, why the sole
      caller is safe today, and the swap-to-copy guidance for any
      future caller that stores intermediate slice references.

  #10 hasPrefixAtBoundary("") edge — added an explicit empty-prefix
      branch returning true. Without it the function fell through to
      pathPrefix[0] == '/', which is true for any absolute path —
      effectively treating "" as a root-matching prefix incidentally.
      The explicit branch makes the invariant load-bearing.
      New test TestHasPrefixAtBoundary_EmptyPrefix pins the surface
      via FindConfigForPath.

All existing tests still pass. Local run on this arm64 machine:
  go test ./pkg/registry/file/... ok in 0.93s

* deps: bump syft to v1.42.4 (CVE-2026-33481), stereoscope v0.1.22, runtime-spec v1.3.0

CodeRabbit upstream PR kubescape#326 finding #1 (Critical). Lifts syft from
v1.32.0 to v1.42.4 — the lowest version that includes the CVE-2026-33481
fix (Syft was failing to clean up temporary storage when a scan
errored mid-flight). v1.42.4 is the latest patch on the v1.42 line, so
the upgrade gets the CVE fix without taking the v1.43/v1.44 minor
bumps' larger surface-area changes.

go mod tidy pulled forward two transitive bumps:
  - stereoscope v0.1.9 → v0.1.22 (Anchore's syft-companion lib, must
    move in lockstep with syft)
  - runtime-spec v1.2.1 → v1.3.0 (transitive via stereoscope)
  - modernc.org/libc v1.67.6 → v1.70.0 (transitive)
  - modernc.org/sqlite v1.46.1 → v1.46.2 (transitive)

go.mod no longer carries a replace directive against kubescape/syft —
our fork doesn't have v1.42.3+ tags and the upstream content is
required for the CVE fix. Direct anchore/syft dependency is now the
source of truth.

Local validation:
  go build ./...          ok
  go test ./pkg/... ok in 1.4s (all 16 packages green)

Vendor directory is .gitignored; `go mod vendor` was rerun locally
to keep build/manifest in sync.

* fix: address 3 follow-up rabbit findings on PR #33

1. ci(manual-integration-tests): use job-level if: false instead of
   exit 1 — workflow shows as "skipped" not "failed red X" in the
   Actions UI when dispatched, removing the cosmetic noise while
   keeping the ~30-minute waste prevention. Issue #32 reference kept
   in the YAML comment above the conditional.

2. test(network_types_protobuf): also assert the Go struct's protobuf
   tag, not just the .proto-file text. The earlier test only checked
   one side of the schema/tag contract, so tag drift on the Go side
   would silently pass. Now both halves are pinned.

3. test(applicationprofile_processor): assert the FULL
   CollapseSettings struct (not just OpenDynamicThreshold). Without
   this, a regression that resets any field added to
   CollapseSettings in the future would silently pass the zero-value
   defensive-fallback test.

All from CodeRabbit follow-up review on storage PR #33. Local validation:
  go test ./pkg/registry/file/ ./pkg/apis/softwarecomposition/v1beta1/
  → both packages ok

* docs(networkmatch): correct spec URL to billofbehavior.com

---------

Co-authored-by: Entlein <eineintlein@gmail.com>
entlein added a commit to k8sstormcenter/storage that referenced this pull request May 16, 2026
…eanup) (#31)

* fix(cleanup): enhance pod deletion logic and add tests for standalone pods

Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>

* feat(networkmatch): IP matcher with literal + CIDR + '*' sentinel

Runtime counterpart to spec §5.7 (v0.0.2). Profile entries are matched
disjunctively — any single entry hit means match. Each entry MAY be:

  - a literal IP (canonicalised by net.ParseIP, IPv4 ↔ ::ffff:v4 unified)
  - a CIDR (a.b.c.d/n, validated by net.ParseCIDR)
  - the '*' sentinel for any IP (sugar for 0.0.0.0/0 ∪ ::/0)

Public API:

  CompileIP([]string) *IPMatcher    // compile-once for hot paths
  (*IPMatcher).Match(string) bool   // amortised match
  MatchIP([]string, string) bool    // cold-path convenience

The compiled-matcher pattern eliminates per-call allocs from net.ParseCIDR
on the hot path — node-agent's nn.* CEL functions call this on every
network event captured by the eBPF tracer.

Tests cover:
  - literal equality (incl. IPv4/IPv6 canonicalisation, ::ffff: mapping)
  - CIDR membership (incl. /32 == literal, edge addresses)
  - '*' sentinel (and rejection of empty/garbage observations)
  - 0.0.0.0/0 + ::/0 RFC-aligned alternatives (do NOT cross address families)
  - malformed entry skipping (admission validates, runtime defends)
  - mixed-list disjunction

Benchmarks (arm64, hot-path compiled form):
  Literal       : 12.73 ns/op   0 allocs
  CIDR          : 18.48 ns/op   0 allocs
  10-entry miss : 59.10 ns/op   0 allocs

All well under the README target of <200 ns/op for IP literal/CIDR.

* feat(networkmatch): DNS matcher with leading/trailing star + mid ellipsis

Runtime counterpart to spec §5.8 (v0.0.2). Profile entries are matched
disjunctively against an observed FQDN. Each entry's labels are walked
left-to-right with positional token semantics:

  literal label   : byte-equality (case-insensitive)
  leading '*'     : matches EXACTLY ONE label (RFC 4592)
  trailing '*'    : matches ONE OR MORE labels (project extension)
  '⋯' anywhere    : matches EXACTLY ONE label (DynamicLabel)
  '**'            : reserved/recursive — admission rejects, runtime drops

Trailing dot is normalised on both sides (entry MAY or MAY NOT have it).
Empty inner labels (e.g. 'foo..bar') are treated as malformed and skipped.

Public API:

  CompileDNS([]string) *DNSMatcher
  (*DNSMatcher).Match(string) bool
  MatchDNS([]string, string) bool   // cold-path convenience

Tests cover:
  - literal equality (case-insensitive, exact-label-count)
  - trailing-dot normalisation in all four combinations
  - RFC 4592 leading '*' (exactly one label)
  - mid '⋯' DynamicLabel (the kubernetes service-FQDN case)
  - trailing '*' extension (one or more, never zero)
  - disjunction across entry list
  - malformed input rejection ('**', empty labels, lone '*')
  - compiled-form reuse contract

Benchmarks (arm64, hot-path compiled form):
  Literal           : 47.68 ns/op   1 alloc (label-split)
  Leading wildcard  : 53.20 ns/op   1 alloc
  Deep name (10 lbl): 144.9 ns/op   1 alloc
  Long mixed list   : 103.3 ns/op   1 alloc

All under the README target of <600 ns/op for DNS wildcards.

* feat(api): NetworkNeighbor.IPAddresses field + admission validation

Adds IPAddresses []string to the NetworkNeighbor schema (internal +
v1beta1) as the v0.0.2 list-form replacement for the deprecated singular
IPAddress field. Each entry MAY be a literal IP, a CIDR, or '*'.

Hand-edited generated code (deepcopy, conversion, protobuf) because
the codegen pipeline isn't run as part of build; verified by a
new round-trip test that pins the protobuf wire contract for field
number 9.

Validation lives in two places:

  pkg/registry/file/networkmatch/validate.go
    - ValidateIPEntry  : literal | CIDR | '*'
    - ValidateDNSEntry : literal | leading-* | trailing-* | mid-⋯
                         rejects '**', empty inner labels, mid-* without ⋯

  pkg/registry/softwarecomposition/networkneighborhood/strategy.go
    REST Validate() walks every neighbor in containers/initContainers/
    ephemeralContainers × egress/ingress and surfaces a field.Invalid
    error per malformed entry. Apiserver translates this into a 400
    so the user gets immediate feedback at write time.

Runtime matchers (MatchIP/MatchDNS) keep their tolerance of malformed
entries — a misconfigured profile that slips past admission won't kill
the detection path.

New tests:
  - protobuf round-trip (field 9 survives Marshal → Unmarshal)
  - ValidateIPEntry / ValidateDNSEntry edge cases
  - REST strategy Validate rejects malformed entries with the right
    field paths

* chore: gitignore .claude agent state directory

* fix(networkmatch): address CodeRabbit review on PR #30

Five findings, all legit, all fixed:

- ValidateUpdate now also runs validateNetworkProfileEntries, so
  malformed ipAddresses / dnsNames can't land via PUT after a clean
  POST. New TestValidateUpdate_NetworkProfileEntries pins this.

- validateNeighborList now also validates the deprecated singular
  IPAddress field while it remains supported. Bypass via the old form
  is closed.

- TestValidate_NetworkProfileEntries asserts each error's field path
  (multiset, order-insensitive) instead of just the count. Field-path
  contract is now pinned: if validation starts emitting errors on the
  wrong path, downstream tooling that surfaces these to users will
  catch it here first. New case for deprecated-singular path.

- TestNetworkNeighbor_IPAddresses_EmptyOmitted now checks Marshal
  errors with t.Fatalf instead of swallowing them via _.

- README links to spec-v0.0.2 (was v0.0.1) and DNS normalisation
  wording matches runtime: trailing dot stripped, labels lowercased
  (was incorrectly described as 'appended').

* fix(networkmatch): address CodeRabbit round 2 on PR #30

Three findings, two fixed, one skipped:

- validateNetworkProfileEntries now iterates an ordered []struct of
  (groupName, items) pairs rather than a map. Go map iteration is
  non-deterministic and admission errors flow to clients via the
  apiserver — stable ordering keeps error messages reproducible.

- TestValidateUpdate_NetworkProfileEntries asserts each error's field
  path (multiset, order-insensitive). Same pattern already applied to
  TestValidate_NetworkProfileEntries; this closes the parallel gap.

Skipped:

- README.md "canonicalisation" → "canonicalization" (Trivial nit).
  Project already uses British spelling consistently across other docs;
  changing one occurrence creates inconsistency without fixing
  anything.

Tests pass on the new ordering — note the test uses an order-insensitive
multiset comparison, so the deterministic-order fix is a separate
concern from the test (the test would pass with either ordering, but
the runtime is now stable for downstream consumers).

* fix(networkmatch): validate deprecated DNS singular field (CR round 3)

Closes a parity gap with IPAddress (which round-1 already validates).
Now both deprecated singular fields receive the same admission grammar
check while they remain supported.

Test extended with a deprecated-DNS path case mirroring the existing
IPAddress one.

* style(networkmatch): use American English spelling consistently

Reverses the round-2 skip on the CodeRabbit nitpick (PR #30, README
line 17). Files added in this branch now use American spelling
(canonicalize/normalize/behavior) matching the Go ecosystem convention
that 'canonicalisation' originally diverged from.

Files touched:
- pkg/registry/file/networkmatch/README.md
- pkg/registry/file/networkmatch/match_ip.go (comment)
- pkg/registry/file/networkmatch/match_dns.go (comment)
- pkg/registry/file/networkmatch/match_ip_test.go (comment)

No behaviour change.

* test(dynamicpathdetector): pin bare-name argv[0] contract for CompareExecArgs

Storage-side recording stores execs as [resolved_path, ...argv] and
returns to consumers as (Path=resolved, Args=full-argv-with-argv[0]).
The matcher does strict positional compare with no special argv[0]
normalisation. That means profile.Args[0] MUST equal runtime argv[0]
as captured by eBPF — typically the BARE program name ("sh"), not
the resolved exepath ("/bin/sh").

The realistic-patterns table earlier in this file uses 2-element
vectors that skip argv[0] entirely, which doesn't pin the convention.
Add TestCompareExecArgs_Argv0BareName covering all the Test_32
scenarios end-to-end against the matcher, plus the two
profile-shape-mismatch cases (Args[0]=full-path vs bare argv[0], and
vice versa) so future drift on either side trips the test.

Pairs with the rewrite of node-agent's Test_32_UnexpectedProcessArguments
that changes profile.Args from [/bin/sh, -c, *] to [sh, -c, *], so
R0040 can be tested in isolation from R0001's path-resolution path.

* release/sbob-rc1: TDD fixes for upstream PR kubescape#326 rabbit findings (#33)

* ci(manual-integration-tests): disable workflow until timing-fragile tests are fixed

Adds a fail-fast guard step at the start of the integration-tests job
that exits with a clear error pointing to issue #32. The workflow stays
visible in the UI but anyone who dispatches it gets an immediate failure
rather than burning ~30 minutes of CI on a known-flaky run.

Last 5 fork runs (between 2026-05-15 16:32Z and 17:59Z on a15fb5a)
each failed on a different *ProfileCreate subtest at the AP-completion
polling stage. Upstream main MIT also red across its last 5 runs. The
failover-tests jobs are consistently green and remain enabled by default
(they run in their own job, untouched by this change).

Re-enable when the wall-clock learning-period assumptions are replaced
with dynamic AP-status polling and 5 consecutive green runs land.

* fix(dynamicpathdetector): DP-memoise compareSegments to bound multi-* backtracking

CodeRabbit upstream PR kubescape#326 finding #4 (Major). The recursive expansion
at the mid-path `*` branch explored all suffix splits without caching,
producing 2^n / n!-style runtime on patterns with multiple `*`
segments. Adversarial inputs of 20 `*` + 20 regular segments time out
the test runner at 60s on the un-memoised matcher.

Approach: keep the existing recursive form (compareSegments) on the
fast path for single-`*` and no-`*` patterns where backtracking can't
re-enter the same (di, ri) state, and route multi-`*` patterns through
a DP-memoised core (compareSegmentsMemo) that caches every (di, ri)
outcome. Semantics are identical — the memoised core is a pure
re-shape of the same recursion.

The split is gated by multipleWildcards(), a single linear scan of
the dynamic-segment slice. This keeps the common-case allocation
profile at 2 allocs/op (splitPath only); the memo map is only
allocated when it actually earns its keep.

Local benchmark on this machine (arm64, -benchtime=2s):

  Shape                              Before          After
  ---------------------------------- --------------- ---------------
  ellipsis_short                     94.26 ns/op     90.08 ns/op
  ellipsis_deep                     158.3  ns/op    171.1  ns/op
  trailing_star                      86.21 ns/op    103.5  ns/op
  trailing_star_no_match_on_parent   73.02 ns/op     73.35 ns/op
  mid_star_zero_consumed             84.76 ns/op     82.26 ns/op
  mid_star_many_consumed            118.4  ns/op    117.5  ns/op
  anchored_root_no_match             55.66 ns/op     53.30 ns/op
  unanchored_star_root               49.30 ns/op     55.30 ns/op
  deep_literal_match                 93.40 ns/op     98.41 ns/op
  deep_literal_mismatch             108.1  ns/op     96.43 ns/op

  All shapes: 2 allocs/op before and after (no heap growth).

  Adversarial multi-* tail-mismatch:    60s+ timeout → 0.2s wall
                                                       (≥ 300× faster).

Tests added in tests/compare_dynamic_memoise_test.go:

  - TestCompareDynamic_MemoiseGoldenAcceptance: 27 paired
    accept/reject cases pinning byte-for-byte semantic parity
    (empty inputs, literal exact, trailing `*`, mid `*`, ellipsis
    `⋯`, consecutive `*/*` zero-or-more semantics, busybox-style
    symlink shape, multi-wildcard, root edge).
  - TestCompareDynamic_MemoiseAdversarialReDoS: 4 multi-`*`
    tail-mismatch shapes with strict time budgets (50/100/200/200ms);
    each fails the test if the matcher takes longer than the budget,
    catching any future regression that re-introduces exponential
    backtracking.
  - TestCompareDynamic_MemoiseAdversarialPositive: positive-case
    parity — a matching adversarial input completes in <200ms.
  - TestCompareDynamic_MemoiseAllocCeiling: per-call allocs ≤ 12
    across the common shapes (current observation: 2 allocs/op),
    catching unbounded heap growth in future memoisation tweaks.

The adversarial tests skip under testing.Short(); the alloc-ceiling
test also skips so quick local runs stay fast.

* test(analyze_endpoints): require equal length before indexed comparisons

CodeRabbit upstream PR kubescape#326 outside-diff-range finding at
analyze_endpoints_test.go:169-178. Ranging over result and indexing
tt.expected[i] silently passes if result is shorter than expected,
making missing endpoints invisible to the gate. Adds require.Len()
before the for-loop so length divergence fails the test loudly.

* test(compare_exec_args): skip ReDoS timing test under testing.Short

CodeRabbit upstream PR kubescape#326 finding #5. The 100ms wall-clock assertion
in TestCompareExecArgs_ReDoSResistance is sensitive to runner CPU
contention and can fail flakily even when the memoised matcher is
correct. Skipping under testing.Short() preserves the regression
intent (the test still runs by default) but lets quick local /
short-mode CI runs stay deterministic. Memoisation correctness is
already covered by the explicit case-table tests above, which always
run regardless of short mode.

* fix(applicationprofile_processor): nil-guard collapseSettings via safe accessor

CodeRabbit upstream PR kubescape#326 finding #3
(applicationprofile_processor.go:92). A zero-valued processor —
constructed via direct struct literal rather than the
NewApplicationProfileProcessor factory — has a nil collapseSettings
function field. The deflate path then nil-dereferences on the call
inside PreSave.

Adds `effectiveCollapseSettings()` as the safe accessor used by
PreSave. It returns the provider's result when configured, or the
compiled-in DefaultCollapseSettings when the field is nil. The
direct field-call path (`a.collapseSettings()`) is intentionally
left raw — callers who construct a zero-value processor and call
the unsafe path explicitly are on the "I know what I'm doing" side.

New regression test TestApplicationProfileProcessor_ZeroValue_NoPanicOnCollapseSettings
pins both halves of the contract:
  - effectiveCollapseSettings() on zero-value processor does NOT
    panic and returns the compiled-in defaults
  - raw field-call on zero-value processor DOES still panic (no
    silent behaviour change)

* fix(proto): declare repeated string ipAddresses = 9 on NetworkNeighbor

CodeRabbit upstream PR kubescape#326 finding #2 (Major).
The Go struct's protobuf tag is `bytes,9,rep,name=ipAddresses` but
generated.proto did not declare field 9 on the NetworkNeighbor
message. Runtime behaviour was unaffected because generated.pb.go is
the actual codec and already encodes field 9 correctly (verified:
wire tag 0x4a = field 9, wire type 2). But the .proto file is the
source of truth for:

  - Any future regeneration (`make generate`) — drops the field if
    the .proto doesn't declare it
  - Cross-language consumers compiling against the .proto schema
  - Documentation accuracy

Restores the declaration. New regression test
TestNetworkNeighbor_ProtoFile_DeclaresIPAddresses parses generated.proto
and asserts the NetworkNeighbor message body contains the field-9
declaration. If the .proto and the Go tag ever diverge again, the
test fails loudly at unit-test time rather than at the next regen.

The existing IPAddresses protobuf roundtrip tests are unaffected and
continue to pass.

* fix(dynamicpathdetector): DefaultCollapseConfig accessor (defensive copy)

CodeRabbit upstream PR kubescape#323 finding #3. The package-level
`DefaultCollapseConfig` was an exported mutable var: any caller doing
`dynamicpathdetector.DefaultCollapseConfig.Threshold = 1` would
silently corrupt every analyzer constructed thereafter. The threshold
surface is security-sensitive (governs how aggressively profile paths
collapse into wildcards), so accidental cross-caller leakage is a
real risk.

Replaces the exported var with an unexported `defaultCollapseConfig`
and an accessor `DefaultCollapseConfig()` that returns a value copy.
Pattern mirrors the existing `defaultCollapseConfigs` / `DefaultCollapseConfigs()`
defensive-copy accessor for the slice form.

Updates 6 callsites across the test suite to use the accessor.
Adds TestDefaultCollapseConfig_DefensiveCopy pinning the contract: a
caller mutating the returned struct does not affect subsequent reads.

* docs(dynamicpathdetector): comment + guard kubescape#323 nitpicks (#5, #6, #9, #10)

Four small docstring / defensive-guard improvements from CodeRabbit
upstream PR kubescape#323:

  #5  wildcardKey / getEndpointKey format coupling — extract
      buildEndpointKey as the single source of truth for the lookup
      key shape. Both call-sites now route through it, removing the
      "must stay in sync" risk that allowed past drift. New test
      TestBuildEndpointKey_SharedFormat pins end-to-end behaviour via
      MergeDuplicateEndpoints: a wildcard-port entry and a specific-
      port sibling collapse into one row with merged Methods.

  #6  consolidateOpens O(n²) — acknowledged in the function's
      docstring along with the threshold (≤ ~10k entries) above which
      a trie-based replacement would be worthwhile. Behaviour
      unchanged.

  #9  removeEndpoint in-place mutation — added a NOTE block in the
      docstring describing the backing-array shift, why the sole
      caller is safe today, and the swap-to-copy guidance for any
      future caller that stores intermediate slice references.

  #10 hasPrefixAtBoundary("") edge — added an explicit empty-prefix
      branch returning true. Without it the function fell through to
      pathPrefix[0] == '/', which is true for any absolute path —
      effectively treating "" as a root-matching prefix incidentally.
      The explicit branch makes the invariant load-bearing.
      New test TestHasPrefixAtBoundary_EmptyPrefix pins the surface
      via FindConfigForPath.

All existing tests still pass. Local run on this arm64 machine:
  go test ./pkg/registry/file/... ok in 0.93s

* deps: bump syft to v1.42.4 (CVE-2026-33481), stereoscope v0.1.22, runtime-spec v1.3.0

CodeRabbit upstream PR kubescape#326 finding #1 (Critical). Lifts syft from
v1.32.0 to v1.42.4 — the lowest version that includes the CVE-2026-33481
fix (Syft was failing to clean up temporary storage when a scan
errored mid-flight). v1.42.4 is the latest patch on the v1.42 line, so
the upgrade gets the CVE fix without taking the v1.43/v1.44 minor
bumps' larger surface-area changes.

go mod tidy pulled forward two transitive bumps:
  - stereoscope v0.1.9 → v0.1.22 (Anchore's syft-companion lib, must
    move in lockstep with syft)
  - runtime-spec v1.2.1 → v1.3.0 (transitive via stereoscope)
  - modernc.org/libc v1.67.6 → v1.70.0 (transitive)
  - modernc.org/sqlite v1.46.1 → v1.46.2 (transitive)

go.mod no longer carries a replace directive against kubescape/syft —
our fork doesn't have v1.42.3+ tags and the upstream content is
required for the CVE fix. Direct anchore/syft dependency is now the
source of truth.

Local validation:
  go build ./...          ok
  go test ./pkg/... ok in 1.4s (all 16 packages green)

Vendor directory is .gitignored; `go mod vendor` was rerun locally
to keep build/manifest in sync.

* fix: address 3 follow-up rabbit findings on PR #33

1. ci(manual-integration-tests): use job-level if: false instead of
   exit 1 — workflow shows as "skipped" not "failed red X" in the
   Actions UI when dispatched, removing the cosmetic noise while
   keeping the ~30-minute waste prevention. Issue #32 reference kept
   in the YAML comment above the conditional.

2. test(network_types_protobuf): also assert the Go struct's protobuf
   tag, not just the .proto-file text. The earlier test only checked
   one side of the schema/tag contract, so tag drift on the Go side
   would silently pass. Now both halves are pinned.

3. test(applicationprofile_processor): assert the FULL
   CollapseSettings struct (not just OpenDynamicThreshold). Without
   this, a regression that resets any field added to
   CollapseSettings in the future would silently pass the zero-value
   defensive-fallback test.

All from CodeRabbit follow-up review on storage PR #33. Local validation:
  go test ./pkg/registry/file/ ./pkg/apis/softwarecomposition/v1beta1/
  → both packages ok

* docs(networkmatch): correct spec URL to billofbehavior.com

---------

Co-authored-by: Entlein <eineintlein@gmail.com>

---------

Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
Co-authored-by: Matthias Bertschy <matthias.bertschy@gmail.com>
Co-authored-by: Entlein <eineintlein@gmail.com>
Signed-off-by: entlein <einentlein@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants