Skip to content

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.

Updated 02 Nov 2026Checked against cli@de9b52a
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_cache or @functools.cache;
  • annotated with Spring @Cacheable;
  • wrapped in memoize, _.memoize, memoizee, moize, pMemoize or memoizeOne in 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).
  • @CacheEvict and 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

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.

Edit this pageSources: cli/internal/sast/rules/vnx-638.rego, cli/internal/sast/docs/vnx-638.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.