Skip to content

Count runes in CEL entropy() to match reported entropy - #184

Open
mazen-salah wants to merge 2 commits into
betterleaks:mainfrom
mazen-salah:fix-cel-entropy-runes
Open

Count runes in CEL entropy() to match reported entropy#184
mazen-salah wants to merge 2 commits into
betterleaks:mainfrom
mazen-salah:fix-cel-entropy-runes

Conversation

@mazen-salah

Copy link
Copy Markdown
Contributor

The CEL entropy() helper used by rule filters (e.g. entropy(secret) <= 3.0) counted bytes, while the entropy reported on findings (detect.shannonEntropy) counts runes. For non-ASCII secrets the two disagreed, so a rule could filter on a different value than the one shown in the report.

Compute entropy over runes with the same formula; ASCII results are unchanged. Added a test pinning the rune-based behavior.

The CEL entropy() helper used by rule filters (e.g. entropy(secret) <= 3.0)
counted bytes, while the entropy reported on findings (detect.shannonEntropy)
counts runes. For non-ASCII secrets the two disagreed, so a rule could filter
on a different value than the one shown in the report. Compute over runes with
the same formula; ASCII results are unchanged.
@greptile-apps

greptile-apps Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Switches celShannonEntropy from byte-iteration to rune-iteration so the CEL entropy() filter function agrees with detect.shannonEntropy on non-ASCII input. The denominator intentionally stays as len(s) (byte count), matching the reference implementation in detect/utils.go.

  • bindings_filter.go: replaces the fixed [256]float64 array with a map[rune]int, mirroring the formula in detect/utils.go exactly.
  • entropy_test.go: adds a new test covering ASCII and multi-byte inputs, though the want reference function duplicates the implementation rather than calling detect.shannonEntropy directly.

Confidence Score: 4/5

Safe to merge; the formula change is correct and consistent with the reference implementation.

The implementation correctly mirrors detect/utils.go. The only concern is the test's want function being a verbatim copy of celShannonEntropy, which won't catch future drift from the reference.

entropy_test.go — the reference function in the test is tautological.

Important Files Changed

Filename Overview
internal/celenv/bindings_filter.go Switches entropy counting from bytes to runes; denominator intentionally kept as byte-length to match detect.shannonEntropy — formula is consistent with the reference implementation.
internal/celenv/entropy_test.go New test pins rune-based behavior but uses a want function that is a copy of the implementation, making it tautological for future divergence detection.

Reviews (1): Last reviewed commit: "count runes in celShannonEntropy to matc..." | Re-trigger Greptile

Comment thread internal/celenv/entropy_test.go Outdated
Comment on lines +12 to +32
want := func(s string) float64 {
if len(s) == 0 {
return 0
}
counts := map[rune]int{}
for _, r := range s {
counts[r]++
}
inv := 1.0 / float64(len(s))
var h float64
for _, c := range counts {
p := float64(c) * inv
h -= p * math.Log2(p)
}
return h
}

for _, s := range []string{"", "a", "aabb", "abcd1234", "日本語テスト", "🔑🔑🔒"} {
got := celShannonEntropy(s)
if exp := want(s); math.Abs(got-exp) > 1e-9 {
t.Errorf("celShannonEntropy(%q) = %v, want %v (rune-based)", s, got, exp)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Tautological reference function

The want lambda is a verbatim copy of celShannonEntropy, so the test only proves the function equals itself. If a future refactor accidentally re-diverges from detect.shannonEntropy (e.g. someone fixes the denominator to use rune count), the test will still pass even though the two would disagree again. Calling detect.shannonEntropy directly (or at least hard-coding known expected values for the non-ASCII cases) would catch that drift.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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