fix(cel): scan Opens.Patterns in was_path_opened_with_suffix/prefix (#98) - #99
Open
ConstanzeTU wants to merge 1 commit into
Open
fix(cel): scan Opens.Patterns in was_path_opened_with_suffix/prefix (#98)#99ConstanzeTU wants to merge 1 commit into
ConstanzeTU wants to merge 1 commit into
Conversation
The Opens.All branch of both helpers scanned Opens.Values only. Volatile paths are always stored as Patterns, so a correctly learned ContainerProfile records the kubelet atomic-writer ServiceAccount token open as /run/secrets/kubernetes.io/serviceaccount/⋯/token with the timestamped directory collapsed and the /token leaf preserved. R0006 gates on !cp.was_path_opened_with_suffix(containerId, '/token'), the helper answered false for that profile, and the rule fired on every token read for the life of the workload. R0008 has the same shape via /proc/⋯/environ. Fixes #98. Why scanning Patterns is safe, not a widening: A pattern's trailing segments after the last collapse token are literal. If they end with the queried suffix then every concrete path the pattern stands for ends with it too, so HasSuffix answers a real question. A pattern whose leaf is itself a wildcard ("/var/log/pods/⋯") returns false — the same answer as skipping it — so this is never worse than the previous behaviour. The prefix side is the mirror image: the segments before the first collapse token are literal. The rationale in the removed comment did not support the code it justified. It warned that HasSuffix on a pattern "returns false and produces a false negative", then avoided that by skipping Patterns — which returns false as well. The blanket skip therefore guaranteed the false negative it was meant to prevent, and did so for concrete-leaf patterns too, where HasSuffix would have been correct. This also makes the helper self-consistent. With projection active, projection_apply.go builds SuffixHits/PrefixHits with HasSuffix/HasPrefix over every raw entry INCLUDING dynamic ones, so the projected branch already answered true for "⋯/token". Only the Opens.All branch disagreed. Pinned by TestSuffix_AllBranchAgreesWithProjectedBranch. Tests: open_atomicwriter_test.go — new. Four cases fail on unmodified main (SA-token suffix, procfs environ suffix, concrete-head prefix, and the two-branch agreement check) and pass after. Three guard cases (wildcard-leaf suffix, unrelated-head prefix, concrete Values) pass both before and after, so the fix is shown not to widen matching. open_test.go — TestWasPathOpenedWithSuffix_PatternsNotScanned and TestWasPathOpenedWithPrefix_PatternsNotScanned pinned the old contract and are replaced by _ConcreteLeafPatternMatches / _ConcreteHeadPatternMatches. These keep the wildcard-leaf and past-the-collapse-token cases asserting false, and add the concrete-leaf/head cases asserting true. Issue #98 does not mention these tests; they are the reason this is a deliberate contract change rather than an oversight, and a reviewer should look here first. Regression check: ./pkg/rulemanager/cel/libraries/containerprofile/... and ./pkg/objectcache/... all pass, including the pre-existing TestOpenWithSuffixInProfile / TestOpenWithPrefixInProfile projection tests and containerprofilecache (2.459s).
ConstanzeTU
marked this pull request as ready for review
August 28, 2026 06:07
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft / stub for discussion. Fixes #98.
The failure
R0006gates on!cp.was_path_opened_with_suffix(containerId, '/token').The kubelet writes projected volumes through an atomic writer, so a ServiceAccount token is read at
…/serviceaccount/..2026_08_27_14_27_52.163845901/token. The timestamped segment is volatile,dynamicpathdetectorcollapses it, and a correctly learned profile storesBecause that entry contains a collapse token it lives in
Opens.Patterns. TheOpens.Allbranch of the helper scannedOpens.Valuesonly, answeredfalse, and R0006 fired on every token read for the life of the workload.R0008is the same mechanism via/proc/⋯/environ.Nothing in the profile or the rule is wrong — the helper simply did not consult what was recorded.
Why scanning Patterns is safe rather than a widening
A pattern's trailing segments after the last collapse token are literal. If they end with the queried suffix, every concrete path that pattern stands for ends with it too. A pattern whose leaf is itself a wildcard (
/var/log/pods/⋯) returnsfalse— the same answer as skipping it — so the change is never worse than the previous behaviour. The prefix side is the mirror image: the segments before the first collapse token are literal, so/var/⋯/loggenuinely does have prefix/var/.The removed rationale did not support the code it justified. It warned that
HasSuffixon a pattern "returns false and produces a false negative" — and then avoided that by skipping Patterns, which also returns false. The blanket skip guaranteed the false negative it was meant to prevent, and did so for concrete-leaf patterns whereHasSuffixwould have been correct.The two branches disagreed
With projection active,
projection_apply.gobuildsSuffixHits/PrefixHitswithHasSuffix/HasPrefixover every raw entry including dynamic ones. So the projected branch already answeredtruefor⋯/token; onlyOpens.Alldisagreed. Now pinned byTestSuffix_AllBranchAgreesWithProjectedBranch.Tests
open_atomicwriter_test.go(new). Verified against unmodifiedmainfirst:mainTestSuffix_AtomicWriterServiceAccountTokenTestSuffix_ProcfsEnvironTestPrefix_ConcreteHeadOfPatternTestSuffix_AllBranchAgreesWithProjectedBranchTestSuffix_WildcardLeafStillUnmatchedTestPrefix_UnrelatedHeadStillUnmatchedTestSuffix_ConcreteValueStillMatchesThe three that pass in both columns are the point: they show the fix does not widen matching.
Issue #98 does not mention it, and it is the reason this is a contract change rather than a straightforward oversight.
TestWasPathOpenedWithSuffix_PatternsNotScannedandTestWasPathOpenedWithPrefix_PatternsNotScannedexisted specifically to assert "patterns must not be scanned" (from the CodeRabbit PR #43 review onopen.go:79). They are replaced by_ConcreteLeafPatternMatches/_ConcreteHeadPatternMatches, which keep the wildcard-leaf and past-the-collapse-token cases assertingfalseand add the concrete-leaf/head cases assertingtrue.Worth noting what the old prefix test asserted: that
/var/⋯/log/foodoes not have prefix/var/. It literally does, and so does every concrete path it stands for.If the reviewers want the old contract preserved, the alternative is to leave the helper alone and have R0006/R0008 declare their suffixes so projection is always active — but that leaves two branches of one helper permanently disagreeing, which seems worse.
Regression check
./pkg/rulemanager/cel/libraries/containerprofile/...and./pkg/objectcache/...all pass, including the pre-existingTestOpenWithSuffixInProfile/TestOpenWithPrefixInProfileandcontainerprofilecache(2.459s).go build ./...clean,gofmtclean.Not covered here
No component/e2e test yet — reproducing needs a real cluster with a learned profile and a workload reading its token. Happy to add one under
tests/if you want it before this leaves draft.