mutated × shared — the answer changes between runs
Plugin Registration / Dispatch-Order Divergence
Handlers auto-register via a decorator whose effective order depends on import/await timing -- so when two claim the same route, which one wins changes run to run.
01the recipe
In the wild
compound ofMacro & Mixin MisuseAsync IO MisuseCWE-696 Bad Operation OrdercompoundCWE-758 Undefined Behavior
example.py
# SMELL: handlers self-register at import; last writer wins, order undefined.
# (macro & mixin misuse x async I/O misuse)
HANDLERS = {}
def route(path):
def deco(fn):
HANDLERS[path] = fn # silently overwrites any prior handler
return fn
return deco
# two plugins both @route('/pay'); whichever module imports last wins,
# and lazy/async import makes that order nondeterministic.
# RIGHT: register explicitly with a priority; reject silent duplicates.
def register(path, fn, priority):
cur = HANDLERS.get(path)
if cur and cur.priority >= priority:
return
HANDLERS[path] = Handler(fn, priority) # deterministic resolutionA decorator/metaclass that registers code out of sight (macro & mixin misuse) makes the registry a function of import order; when plugins load lazily or via async readiness, that order is unspecified, so duplicate routes resolve to a different handler between runs. Relying on that order is reliance on unspecified behavior (CWE-758). Register in one synchronous phase with explicit priorities and duplicate detection.
// observed
implicit: /pay resolves to a different plugin depending on import timing right: explicit priority -> the same handler wins every run
02weakness catalog
Mapped weaknesses (CWE)
On its own, this defect is catalogued by MITRE as one or more of these weaknesses. The exploitable vulnerability usually appears only when it chains or combines with another.