Skip to content

fix: size cache key buffer per rule in cached enforcers - #1756

Open
zjncs wants to merge 1 commit into
apache:masterfrom
zjncs:fix/cached-enforcer-ragged-rules-cache-key
Open

fix: size cache key buffer per rule in cached enforcers#1756
zjncs wants to merge 1 commit into
apache:masterfrom
zjncs:fix/cached-enforcer-ragged-rules-cache-key

Conversation

@zjncs

@zjncs zjncs commented Sep 4, 2026

Copy link
Copy Markdown

Description

CachedEnforcer.RemovePolicies and SyncedCachedEnforcer.checkManyAndRemoveCache (used by both AddPolicies and RemovePolicies) size the cache-key buffer from the first rule of the batch and reuse it for every rule:

irule := make([]interface{}, len(rules[0]))
for _, rule := range rules {
    for i, param := range rule {
        irule[i] = param
    }
    key, _ := e.getKey(irule...)
    ...
}

When the batch contains rules of different lengths (the [][]string API does not require uniform length, and the model accepts variable-length rules — e.g. an ABAC condition field makes rules longer than the plain RBAC ones):

  • a later rule longer than the first panics with index out of range;
  • a later rule shorter than the first keeps stale elements from the previous rule at the tail, so the computed cache key is wrong (e.g. alice$$data1$$read$$extra instead of alice$$data1$$read$$) and the real cached decision of a removed rule is never deleted.

The second case is an authorization correctness bug, not just hygiene: after RemovePolicies removes a rule from the model, Enforce keeps returning the stale cached true:

e, _ := NewCachedEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
_, _ = e.AddPolicies([][]string{{"bob", "data2", "write", "extra"}})
e.Enforce("alice", "data1", "read") // true, cached
_, _ = e.RemovePolicies([][]string{
    {"bob", "data2", "write", "extra"},
    {"alice", "data1", "read"},
})
e.Enforce("alice", "data1", "read") // still true — rule was removed but the cached decision survived

Fix: allocate the key buffer per rule (make([]interface{}, len(rule)) inside the loop) in both enforcers.

Testing

Added 4 tests (TestRemovePoliciesCacheRaggedRules{StaleEntry,LongerRule} in enforcer_cached_test.go and the TestSync... twins in enforcer_cached_synced_test.go).

Before the fix (master 34297a1):

  • StaleEntry tests FAIL: alice, data1, read: true, supposed to be false — stale cached decision
  • LongerRule tests FAIL: panic: runtime error: index out of range [3] with length 3 in checkManyAndRemoveCache/RemovePolicies

After the fix: all 4 pass. All existing cache tests (TestCache, TestSyncCache, cached g-function tests) still pass, and the full go test ./... suite passes (8 packages). go vet clean.


This PR was prepared with AI assistance (GitHub Copilot / ZCode autonomous agent). All findings were verified manually against the codebase before submission.

@zjncs
zjncs marked this pull request as ready for review September 4, 2026 12:21
Copilot AI lite review requested due to automatic review settings September 4, 2026 12:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

2 participants