# Asymmetric access check between paired operations

Compare guards on paired operations: pause vs unpause, enable vs disable, approve vs revoke, and every path setting the same bit.

_Area: Smart contract access control and control flow. Seen in 3 disclosed reports; latest 2026-08-20. Updated 2026-10-23._

## How it works
Paired operations are supposed to have deliberately different guards: pausing is cheap and broad, unpausing is narrow; enabling a privilege is checked, disabling is open. The assumption that breaks is that every code path honoring the pair applies the same split. When one path (a sibling module, a transfer-triggered auto-enable, a revoke helper) reuses a weaker or shared guard, the asymmetry silently collapses and the restricted direction becomes reachable by the broader role or by anyone.

## Where it shows up
- Pause and unpause functions sharing one validator (e.g. an emergency-admin check) instead of separate admin-only unpause [E3](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-08.md)
- Multiple entrypoints that set the same authorization bit (collateral enablement) with differing guard sets [E1](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-02.md)
- Transfer or side-effect paths that auto-set a privilege bit without the eligibility/oracle checks used on the direct path [E1](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-02.md)
- Revoke/cleanup functions whose only guard is a call on an arbitrary address that returns false for EOAs rather than reverting [E2](https://bugbountylibrary.com/r/c4%3A2022-08-olympus%3AM-12.md)
- Comments or TODOs acknowledging a missing check near the guard [E2](https://bugbountylibrary.com/r/c4%3A2022-08-olympus%3AM-12.md)

## How to review
Enumerate every function that flips a shared bit or toggles a state, and diff their guard conditions side by side. The invariant: for each paired operation, the restricted direction must be gated by a strictly narrower role/check than the permissive direction, and every path setting the same bit must apply the identical eligibility check. For guards that call into an arbitrary address, confirm non-contract callers revert rather than returning empty data treated as false [E2](https://bugbountylibrary.com/r/c4%3A2022-08-olympus%3AM-12.md). Check sibling modules (global vs reserve-level) honor the documented split [E3](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-08.md).

## Variants seen
- Shared emergency-admin validator on both pause directions, bypassing the intended asymmetry [E3](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-08.md)
- Fragmented collateral-eligibility checks across enable and transfer paths [E1](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-02.md)
- Guard relying on isActive() that silently returns false for EOAs, letting anyone zero approvals [E2](https://bugbountylibrary.com/r/c4%3A2022-08-olympus%3AM-12.md)

## Impact and severity
Paused or frozen reserves can back new borrows and dust transfers grief users [E1](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-02.md); a compromised emergency admin can unpause reserves [E3](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-08.md); anyone can zero an EOA's approved withdrawals, causing griefing/DoS [E2](https://bugbountylibrary.com/r/c4%3A2022-08-olympus%3AM-12.md). Severity rises when the weaker path is permissionless or when downstream accounting trusts the bit without rechecking.

## Evidence
- E1: https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-02.md
- E2: https://bugbountylibrary.com/r/c4%3A2022-08-olympus%3AM-12.md
- E3: https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-08.md
