VNX-638: Not Using Complete Mediation (cached authorization decision)
Detects authorization decisions that are memoised without expiry: Python @lru_cache/@cache, Spring @Cacheable and JavaScript memoize() on functions such as has_permission, isAdmin, canEdit or getUserRoles, so a revoked permission keeps being granted.
| Field | Value |
|---|---|
| Rule ID | VNX-638 |
| Severity | Medium |
| SARIF level | warning |
| Kind | sast |
| Languages | Java, JavaScript / TypeScript, Python |
| CWE | CWE-638 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:M/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C |
| Tags | complete-mediation authorization caching cwe-638 |
| Source | internal/sast/rules/vnx-638.rego |
Overview
VNX-638 maps to CWE-638: Not Using Complete Mediation. Complete mediation means access is checked every time the resource is used. A permission check whose answer is memoised is checked once: after an administrator revokes a permission, demotes a user or removes a role, the cached True is still returned until the process restarts.
Reported when the function’s name says it decides or loads access (has_permission, hasRole, has_access, is_admin, isAuthorized, is_allowed, is_owner, can_edit/canDelete, check_permission, getUserRoles, load_user_permissions, user_has_role, authorize, …) and it is:
- decorated with Python
@functools.lru_cacheor@functools.cache; - annotated with Spring
@Cacheable; - wrapped in
memoize,_.memoize,memoizee,moize,pMemoizeormemoizeOnein JavaScript/TypeScript (by the name it is assigned to or the function it wraps).
Names that describe a role or permission rather than decide one (get_role_display_name, getRoleLabel, get_permission_choices) and capability probes (can_parse, canHandle) are not reported.
What Gets Flagged
@lru_cache(maxsize=None) # FLAGGED
def has_permission(user_id, perm):
return Grant.objects.filter(user_id=user_id, perm=perm).exists()
@Cacheable("permissions") // FLAGGED
public boolean hasPermission(long userId, String permission) { ... }
const isAdmin = memoize(async (userId) => (await User.findById(userId)).role === 'admin'); // FLAGGED
What Is Not Flagged
- Caches with an expiry, such as cachetools
TTLCache. @cached_property, which lives as long as one object (usually one request).@CacheEvictand other eviction annotations.- Memoised functions whose names are not about access (
parse_version,formatDate). - A TTL configured on the Spring cache manager is not visible on the line and is not judged.
Remediation
Check access on every request. If the lookup is expensive, cache it for seconds rather than for the process lifetime, and evict the entry when grants change:
_perm_cache = TTLCache(maxsize=1024, ttl=30)
@cached(_perm_cache)
def has_permission(user_id, perm): ...
References
- CWE-638: Not Using Complete Mediation
- Saltzer and Schroeder, The Protection of Information in Computer Systems
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-638
// nosec vnx-638
Or record an ignore rule: vulnetix ignore add --rule vnx-638 --reason "...". See Suppressing findings.