killmesoonbaby044 opened a new issue, #424:
URL: https://github.com/apache/casbin-pycasbin/issues/424
### Description
Calling `get_implicit_permissions_for_user()` (or its async counterpart) and
then modifying the returned list elements **silently corrupts the enforcer's
in-memory policy store**. Subsequent `enforce()` calls will produce incorrect
results without any error or warning.
### Steps to Reproduce
```python
import casbin
e = casbin.Enforcer("model.conf", "policy.csv")
# policy: p, admin, data1, read
# policy: p, alice, data2, read
# g, alice, admin
perms = e.get_implicit_permissions_for_user("alice")
# perms == [["admin", "data1", "read"], ["alice", "data2", "read"]]
# Mutate the returned list — e.g. to transform/enrich data
perms[0][2] = "write"
# The enforcer's internal store is now corrupted:
print(e.get_policy())
# [["admin", "data1", "write"], ["alice", "data2", "read"]] ← WRONG
```
### Root Cause
The call chain is:
```
get_implicit_permissions_for_user()
→ get_named_implicit_permissions_for_user()
→ get_named_permissions_for_user_in_domain() [per role]
→ get_filtered_named_policy()
→ iterates self.model["p"][ptype].policy
and appends matching rows directly (no copy)
```
The critical lines in `enforcer.py`:
```python
for role in roles:
permissions = self.get_named_permissions_for_user_in_domain(ptype, role,
...)
res.extend(permissions) # ← appends references, not copies
return res
```
`get_filtered_named_policy` yields the **same `list` objects** that are
stored in `self.model["p"][ptype].policy`. They are not copied at any point in
the chain. As a result, the returned list contains direct aliases into the
enforcer's internal storage, and any in-place modification by the caller
immediately corrupts it.
This affects all related methods that go through the same chain:
- `get_implicit_permissions_for_user()`
- `get_named_implicit_permissions_for_user()`
- `get_permissions_for_user()`
- `get_filtered_policy()`
### Expected Behavior
The returned list should be independent of the enforcer's internal state.
Modifying it must not affect the enforcer.
### Proposed Fix
The fix should be applied in `get_filtered_named_policy` in
`casbin/model/policy.py`, so all callers are protected automatically. Change
the filter loop to append copies of the matching rows instead of the rows
themselves:
```python
# Before
return [rule for rule in assertion.policy if <filter condition>]
# After
return [rule[:] for rule in assertion.policy if <filter condition>]
```
Using `rule[:]` (shallow slice copy) is sufficient because policy rows are
`list[str]` — strings are immutable, so a one-level copy fully isolates the
caller. `copy.deepcopy` is not needed and would add unnecessary overhead.
Alternatively, the copy can be applied one level up in
`get_named_implicit_permissions_for_user`:
```python
for role in roles:
permissions = self.get_named_permissions_for_user_in_domain(ptype, role,
...)
res.extend([p[:] for p in permissions]) # copy each row
```
The `policy.py` level is preferable since it fixes all callers at once.
### Workaround (until fixed)
Users can protect themselves by deep-copying the result immediately:
```python
import copy
perms = copy.deepcopy(e.get_implicit_permissions_for_user(user))
```
Or, if transforming to typed objects, by never mutating the raw rows
in-place:
```python
# Safe — constructs new objects from raw data without touching the rows
result = [MyDTO(sub=row[0], obj=row[1], act=row[2]) for row in raw]
```
### Environment
- **pycasbin version:** v2.8.0
- **Python version:** v3.10.12
- **Async enforcer:** yes
- **Async enforcer version:** v1.17.0
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]