Skip to content

tier-1 (path+opens): narrower-fallback Patterns scan for suffix/prefix queries (Matthias #811)#48

Open
entlein wants to merge 1 commit into
mainfrom
release/v0.3.26/tier-1-path-opens
Open

tier-1 (path+opens): narrower-fallback Patterns scan for suffix/prefix queries (Matthias #811)#48
entlein wants to merge 1 commit into
mainfrom
release/v0.3.26/tier-1-path-opens

Conversation

@entlein
Copy link
Copy Markdown

@entlein entlein commented May 19, 2026

Tier 1 of the v0.3.26 release chain

Path+opens layer. Pairs with storage#35 (release/v0.0.240/tier-1-path-opens) — both must deploy together; storage tier-1 SHA 511a9c11 is pinned in this branch's CT build.

Addresses Matthias's upstream PR kubescape#811 BLOCKING review (2026-05-19T10:35Z).

Matthias's concern

On pkg/rulemanager/cel/libraries/applicationprofile/open.go:125:

Blocking: projectField() still keeps dynamic open paths only in cp.Opens.Patterns when cp.Opens.All == true (see TestApply_DynamicRetainedInPassThrough). Removing the Patterns scan here makes ap.was_path_opened_with_suffix() / _with_prefix() return false for dynamic entries in pass-through mode, so rules that omit profileDataRequired.opens regress. If the old strings.HasPrefix/HasSuffix behavior on pattern text is too permissive, we still need a narrower fallback here instead of ignoring Patterns entirely.

Context

Pre-PR-kubescape#811, CodeRabbit (PR #43 review on open.go:79) flagged that running strings.HasSuffix("/var/log/pods/*/volumes/...", "foo.log") returns false even though the pattern's concrete realisations might end in foo.log — a false negative. We addressed it by SKIPPING the Patterns scan in the All=true branch. Matthias's review reverses that: skipping is too aggressive and silently regresses rules that omit profileDataRequired.opens (which rely on the All=true pass-through behavior).

Fix — narrower fallback

New helpers:

  • patternConcreteSuffix(p string) → literal text after the LAST wildcard segment in p; returns "" if p ends in a wildcard segment.
  • patternConcretePrefix(p string) → literal text before the FIRST wildcard segment in p; returns "" if p starts with a wildcard segment.

Suffix/prefix matcher then:

for each pattern p:
    tail/head := patternConcreteSuffix/Prefix(p)
    if tail/head == "":
        return true                                  // permissive on wildcard tail/head
    if strings.HasSuffix/HasPrefix(tail/head, query):
        return true
return false

Boundary slashes are KEPT in the returned concrete suffix/prefix so queries with leading/trailing slashes match correctly (every concrete realisation has the boundary slash too).

Local bench evidence (-count=3 -benchtime=200ms)

Helper itself — zero-alloc on every shape:

Pattern ns/op B/op allocs/op
/var/log/⋯/foo.log 11.5 0 0
/var/log/pods/* 10.0 0 0
/var/log/foo.log (no wildcards) 9.4 0 0
* 1.6 0 0
/var/⋯/log/⋯/foo.log 14.8 0 0

Full matcher (50 Values + 10 Patterns):

Shape suffix variant prefix variant
values_only 372 ns/op, 56 B/op, 3 allocs/op 351 ns/op, 56 B/op, 3 allocs/op
patterns_concrete 391 ns/op, 56 B/op, 3 allocs/op 395 ns/op, 56 B/op, 3 allocs/op
patterns_wildcard 393 ns/op, 56 B/op, 3 allocs/op 428 ns/op, 56 B/op, 3 allocs/op

Patterns scan adds ~20 ns vs Values-only with no extra allocs. The steady 56 B / 3 allocs is pre-existing CEL value-wrapping cost, unchanged by this PR.

Reproduce:

git worktree add /tmp/wt-na-tier1 release/v0.3.26/tier-1-path-opens
cd /tmp/wt-na-tier1
go test -run=^$ -bench='Benchmark' -benchmem -count=3 -benchtime=200ms ./pkg/rulemanager/cel/libraries/applicationprofile/...

Tests

Updated previous "PatternsNotScanned" tests (which pinned the pre-kubescape#811 CR fix) to reflect Matthias's new contract. New / updated assertions:

  • TestWasPathOpenedWithSuffix_PatternsScannedWithConcreteTail — pattern /var/log/⋯/foo.log, query .log → true (tail /foo.log has suffix .log); query .txt → false
  • TestWasPathOpenedWithSuffix_PatternWildcardTail_Permissive — pattern /var/log/pods/*, any suffix → true
  • TestWasPathOpenedWithPrefix_PatternsScannedWithConcreteHead — pattern /var/⋯/log/foo, query /var/ → true; query /etc/ → false
  • TestWasPathOpenedWithPrefix_PatternWildcardHead_Permissive — pattern */run, any prefix → true
  • TestPatternConcreteSuffix_AndPrefix — pins the splitter helpers on 7 shapes (no_wildcards, trailing_star, leading_star, mid_ellipsis, both_mid, lone_star, lone_ellipsis)

Component test evidence

Run 26121369338 (NA Component Tests workflow on release/v0.3.26/tier-1-path-opens with STORAGE_TAG=tier1 + STORAGE_REF=511a9c11):

  • 27/29 component matrix legs green — includes:
    • Test_27_ApplicationProfileOpens ✅ — directly exercises was_path_opened* matchers
    • Test_33_AnalyzeOpensWildcardAnchoring ✅ — directly exercises wildcard-anchored open matching
  • 2 failures: Test_09_FalsePositiveTest + Test_10_MalwareDetectionTestpre-existing on main (same legs were red on yesterday's post-PR#43 main run 25969722538), not introduced by this PR

Test plan

  • go build ./... clean
  • go test ./pkg/rulemanager/... -count=1 green
  • Helper benchmarks zero-alloc (table above)
  • Matcher benchmarks: ~20 ns Patterns-scan overhead, no extra allocs
  • Component tests 27/29 green (run 26121369338), Matthias-relevant legs green
  • No regression vs pre-existing main flakes

…true)

Addresses Matthias's upstream PR kubescape#811 blocking review (2026-05-19T10:35Z):

  Blocking: projectField() still keeps dynamic open paths only in
  cp.Opens.Patterns when cp.Opens.All == true. Removing the Patterns
  scan here makes ap.was_path_opened_with_suffix() / _with_prefix()
  return false for dynamic entries in pass-through mode, so rules
  that omit profileDataRequired.opens regress. If the old
  strings.HasPrefix/HasSuffix behavior on pattern text is too
  permissive, we still need a narrower fallback here instead of
  ignoring Patterns entirely.

Context
-------

Pre-PR-kubescape#811, CodeRabbit (PR #43 review on open.go:79) flagged that
running strings.HasSuffix("/var/log/pods/*/volumes/...", "foo.log")
returns false even though the pattern's concrete realisations might
end in "foo.log" — a false negative. We addressed it by SKIPPING
the Patterns scan in the All=true branch. Matthias's review reverses
that: skipping is too aggressive and silently regresses rules that
omit profileDataRequired.opens (which rely on the All=true pass-through
behavior).

Fix
---

Narrower fallback — when Opens.All == true and the Values scan didn't
match, scan Patterns with split semantics:

  patternConcreteSuffix(p)  → literal text after the LAST wildcard
                              segment in p; "" if p ends in a wildcard
                              segment.
  patternConcretePrefix(p)  → literal text before the FIRST wildcard
                              segment in p; "" if p starts with a
                              wildcard segment.

Suffix/prefix matcher then:
  for each pattern:
    tail/head := patternConcrete{Suffix,Prefix}(pattern)
    if tail/head == "":
        return true   // wildcard tail/head → permissive (concrete
                      // realisations could match ANY suffix/prefix)
    if strings.HasSuffix/HasPrefix(tail/head, query):
        return true

Boundary slashes are KEPT in the returned concrete suffix/prefix so
queries with leading/trailing slashes match correctly (every concrete
realisation has the boundary slash too).

Tests
-----

Updated previous "PatternsNotScanned" tests (which pinned the
pre-kubescape#811 CR fix) to reflect Matthias's new contract:

  TestWasPathOpenedWithSuffix_PatternsScannedWithConcreteTail
    pattern "/var/log/⋯/foo.log", query ".log"   → true (tail "/foo.log" has suffix ".log")
    pattern "/var/log/⋯/foo.log", query ".txt"   → false

  TestWasPathOpenedWithSuffix_PatternWildcardTail_Permissive
    pattern "/var/log/pods/*", any suffix         → true

  TestWasPathOpenedWithPrefix_PatternsScannedWithConcreteHead
    pattern "/var/⋯/log/foo", query "/var/"      → true (head "/var/" has prefix "/var/")
    pattern "/var/⋯/log/foo", query "/etc/"      → false

  TestWasPathOpenedWithPrefix_PatternWildcardHead_Permissive
    pattern "*/run", any prefix                   → true

  TestPatternConcreteSuffix_AndPrefix
    pins the splitter helpers against 7 representative shapes
    (no_wildcards, trailing_star, leading_star, mid_ellipsis,
    both_mid, lone_star, lone_ellipsis).

Benchmarks (new file: open_bench_test.go)
-----------------------------------------

Helper itself — zero allocation across all shapes:

  BenchmarkPatternConcreteSuffix//var/log/⋯/foo.log   11.5 ns/op  0 B/op  0 allocs/op
  BenchmarkPatternConcreteSuffix//var/log/pods/*      10.0 ns/op  0 B/op  0 allocs/op
  BenchmarkPatternConcreteSuffix//var/log/foo.log      9.4 ns/op  0 B/op  0 allocs/op
  BenchmarkPatternConcreteSuffix/*                     1.6 ns/op  0 B/op  0 allocs/op
  BenchmarkPatternConcreteSuffix//var/⋯/log/⋯/foo.log 14.8 ns/op  0 B/op  0 allocs/op

Full matcher (50 Values + 10 Patterns) — the Patterns scan adds
~20 ns on top of the Values-only path with no extra allocations
(the steady 56 B/op / 3 allocs/op is pre-existing CEL value-wrapping
cost, unrelated to this change):

  BenchmarkWasPathOpenedWithSuffix_AllMode/values_only         372 ns/op  56 B/op  3 allocs/op
  BenchmarkWasPathOpenedWithSuffix_AllMode/patterns_concrete   391 ns/op  56 B/op  3 allocs/op
  BenchmarkWasPathOpenedWithSuffix_AllMode/patterns_wildcard   393 ns/op  56 B/op  3 allocs/op
  BenchmarkWasPathOpenedWithPrefix_AllMode/values_only         351 ns/op  56 B/op  3 allocs/op
  BenchmarkWasPathOpenedWithPrefix_AllMode/patterns_concrete   395 ns/op  56 B/op  3 allocs/op
  BenchmarkWasPathOpenedWithPrefix_AllMode/patterns_wildcard   428 ns/op  56 B/op  3 allocs/op
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Updates suffix and prefix path opening checks to evaluate wildcard patterns in Opens.Patterns by extracting their concrete literal tails/heads and applying string comparison, with permissive matching when patterns end/begin with wildcards. Adds test coverage for the new behavior and performance benchmarks.

Changes

Pattern-aware suffix/prefix matching

Layer / File(s) Summary
Pattern tail/head extraction helpers
pkg/rulemanager/cel/libraries/applicationprofile/open.go
New patternConcreteSuffix and patternConcretePrefix helpers parse path patterns into concrete literal substrings by iterating segments and detecting wildcard identifiers; patterns ending/beginning with wildcards return empty strings for permissive matching.
Suffix path matching with pattern scanning
pkg/rulemanager/cel/libraries/applicationprofile/open.go, open_test.go
wasPathOpenedWithSuffix now scans cp.Opens.Patterns using extracted concrete tails alongside concrete cp.Opens.Values matching; tests are updated to verify concrete-tail matching behavior and permissive matching for wildcard-tail patterns.
Prefix path matching with pattern scanning
pkg/rulemanager/cel/libraries/applicationprofile/open.go, open_test.go
wasPathOpenedWithPrefix now scans cp.Opens.Patterns using extracted concrete heads alongside concrete cp.Opens.Values matching; tests verify concrete-head behavior, permissive wildcard-head patterns, and helper extraction correctness across multiple pattern shapes.
Performance benchmarks
pkg/rulemanager/cel/libraries/applicationprofile/open_bench_test.go
Three benchmarks measure suffix/prefix matching allocation and latency across profile shapes (values-only, concrete patterns, wildcard patterns), and isolated patternConcreteSuffix extraction performance.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR description and changeset focus on path+opens pattern matching and dynamic open paths, while linked issue #39 addresses workflow file permissions. These are unrelated concerns; the PR does not address #39's coding requirements. The PR title and description mention Tier 1 path+opens but do not implement the workflow permissions from issue #39. Either link the correct issue or add the workflow permission changes required by #39.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding narrower-fallback pattern scanning for suffix/prefix queries in the path+opens layer to address Matthias's blocking review.
Description check ✅ Passed The PR description is thorough and directly related to the changeset, explaining the problem, solution, benchmarking evidence, tests, and component validation.
Out of Scope Changes check ✅ Passed All code changes in the PR (open.go, open_test.go, open_bench_test.go) directly implement the narrower-fallback pattern-scanning solution for suffix/prefix queries described in the PR objectives. No unrelated changes are present.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release/v0.3.26/tier-1-path-opens

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

Command failed

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


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 and usage tips.

@github-actions
Copy link
Copy Markdown

Performance Benchmark Results

Node-Agent Resource Usage
Metric BEFORE AFTER Delta
Avg CPU (cores) 0.219 0.208 -5.2%
Peak CPU (cores) 0.227 0.220 -3.1%
Avg Memory (MiB) 270.465 275.078 +1.7%
Peak Memory (MiB) 273.695 276.656 +1.1%
Dedup Effectiveness (AFTER only)
Event Type Passed Deduped Ratio
capabilities 0 0 N/A
hardlink 6000 0 0.0%
http 1708 119452 98.6%
network 901 77999 98.9%
open 30974 620776 95.2%
symlink 6000 0 0.0%
syscall 984 1900 65.9%
Event Counters
Metric BEFORE AFTER
capability_counter 8 8
dns_counter 1420 1423
exec_counter 7103 7118
network_counter 93412 93646
open_counter 773107 775187
syscall_counter 3370 3525

@entlein
Copy link
Copy Markdown
Author

entlein commented May 20, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

✅ 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.

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

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

Inline comments:
In `@pkg/rulemanager/cel/libraries/applicationprofile/open_test.go`:
- Around line 30-46: The test comments in open_test.go contain contradictory
contracts: the newer block (starting at the shown diff) states "Patterns ARE
scanned when Opens.All == true" while an earlier legacy section still asserts
"MUST NOT contribute"; remove or update the stale legacy block so the file
documents only the new contract. Locate the old paragraph that claims Patterns
"MUST NOT contribute" and either delete it or rewrite it to match the new policy
(e.g., explain the narrower fallback behavior and wildcard permissiveness),
ensuring the test comments consistently describe the behavior checked by the
tests.
🪄 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: d830f2b7-7ffb-4ee7-8489-0a21925a4bca

📥 Commits

Reviewing files that changed from the base of the PR and between 58247d7 and 3dd5932.

📒 Files selected for processing (3)
  • pkg/rulemanager/cel/libraries/applicationprofile/open.go
  • pkg/rulemanager/cel/libraries/applicationprofile/open_bench_test.go
  • pkg/rulemanager/cel/libraries/applicationprofile/open_test.go

Comment on lines +30 to +46
// These tests pin Matthias's upstream PR #811 review contract:
//
// Patterns ARE scanned when Opens.All == true, but with a NARROWER
// fallback than text-level strings.HasSuffix/HasPrefix:
//
// * Pattern with a concrete tail/head (text after/before the last/first
// wildcard segment): match via HasSuffix/HasPrefix on the concrete
// piece — every realisation has that text textually.
// * Pattern ending/starting with a wildcard segment: be PERMISSIVE
// (return true). The concrete realisations could match ANY
// suffix/prefix; refusing would silently regress rules that omit
// profileDataRequired.opens (Matthias's "we still need a narrower
// fallback here instead of ignoring Patterns entirely").
//
// Pre-PR-#811 (CR's HasSuffix-on-Patterns concern) the matcher SKIPPED
// Patterns entirely. That made wildcard-only profiles silently fail
// suffix/prefix queries — the regression Matthias's review reverses.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Resolve contradictory test contract comments.

Line 30 onward states the new “patterns are scanned” contract, but the preceding legacy block (for example Line 16) still asserts “MUST NOT contribute.” Please remove/update the stale section so the test documents one contract only.

Suggested cleanup
-// TestWasPathOpenedWithSuffix_PatternsNotScanned pins the contract from
-// the CodeRabbit PR `#43` review on open.go:79 (Major). Wildcard-shaped
-// entries in cp.Opens.Patterns MUST NOT contribute to suffix/prefix
-// answers — their literal text answers the wrong question. A retained
-// pattern "/var/log/pods/*/volumes/...." doesn't END with "foo.log"
-// even though the concrete open it stands in for might. Only concrete
-// paths in cp.Opens.Values are valid sources of suffix/prefix truth in
-// pass-through (Opens.All=true) mode.
-//
-// In projection-active mode (Opens.All=false), the rule manager
-// precomputes Opens.SuffixHits / PrefixHits from the spec, which is
-// the correct mechanism — those are exercised in
-// TestOpenWithSuffixInProfile / TestOpenWithPrefixInProfile.
+// TestWasPathOpenedWithSuffix_PatternsScannedWithConcreteTail pins the
+// post-PR-#811 contract for Opens.All=true pattern scanning.
🤖 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/rulemanager/cel/libraries/applicationprofile/open_test.go` around lines
30 - 46, The test comments in open_test.go contain contradictory contracts: the
newer block (starting at the shown diff) states "Patterns ARE scanned when
Opens.All == true" while an earlier legacy section still asserts "MUST NOT
contribute"; remove or update the stale legacy block so the file documents only
the new contract. Locate the old paragraph that claims Patterns "MUST NOT
contribute" and either delete it or rewrite it to match the new policy (e.g.,
explain the narrower fallback behavior and wildcard permissiveness), ensuring
the test comments consistently describe the behavior checked by the tests.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant