# SAFE-02: Admin-only gas-station path can bypass sBTC threshold/cooldown ## Summary `pillar-safe-v2` and `jing-mm-safe` let an admin set `max-gas-amount` to any value, then call `enroll-dual-stacking` with no passkey signature and an arbitrary `gas-station-trait` contract. Because `enroll-dual-stacking` pays the gas station inside `as-contract? ((with-ft SBTC-CONTRACT "sbtc-token" (var-get max-gas-amount)))`, a malicious gas station can pull up to the configured sBTC allowance from the wallet contract without going through the sBTC threshold, pending-operation cooldown, veto flow, or token-lock check. Impact: a compromised admin key can drain sBTC immediately by first raising `max-gas-amount`, then invoking the admin-authorized dual-stacking enrollment path with an attacker-controlled gas contract. This is stronger than a normal over-threshold transfer, which at least creates a pending operation and a veto window. ## Affected Code `pillar-safe-v2.clar` - `set-max-gas-amount` is admin-only and has no cap: lines 142-146. - `enroll-dual-stacking` accepts admin authorization when `sig-auth` is `none`: lines 967-980. - The same function then pays arbitrary `gas` using the unbounded `max-gas-amount`: lines 983-986. - The function does not check `token-lock-enabled`, unlike normal transfer paths. `jing-mm-safe.clar` - `set-max-gas-amount` is admin-only and has no cap: lines 153-157. - `enroll-dual-stacking` accepts admin authorization when `sig-auth` is `none`: lines 1161-1174. - The same function then pays arbitrary `gas` using the unbounded `max-gas-amount`: lines 1177-1180. - The function does not check `token-lock-enabled`, unlike normal transfer paths. The public `gas-station-trait` only requires `pay-gas`, `pay-gas-with-pyth`, and `get-gas-amount`; it does not constrain the destination or the amount used by `pay-gas`. ## Exploit Sketch 1. Attacker compromises an admin STX key for a funded wallet. 2. Call `set-max-gas-amount` with a value equal to, or greater than, the wallet's sBTC balance. 3. Deploy or select a gas-station contract implementing `pay-gas` that transfers the granted sBTC allowance to the attacker's address and returns `(ok true)`. 4. Deploy or select a dual-stacking trait implementation whose `enroll` returns `(ok true)`. 5. Call `enroll-dual-stacking malicious-dual-stacking none (some malicious-gas)`. 6. The wallet authenticates through the admin-only branch, executes `with-ft SBTC-CONTRACT ... max-gas-amount`, calls `malicious-gas.pay-gas`, and releases sBTC without creating a pending operation. This route bypasses: - `would-exceed-sbtc-threshold` - `create-pending-operation` - `veto-operation` - `execute-after` cooldown - `token-lock-enabled` - passkey authorization ## Severity Rationale The bounty asks whether a compromised admin key alone can drain over-threshold funds and whether token-lock/cooldown protections hold. This path lets the admin key alone move sBTC through a non-transfer feature surface. The drain amount is governed by `max-gas-amount`, which the same admin can set without a floor/ceiling, passkey approval, or cooldown. ## Suggested Fix - Make `set-max-gas-amount` require passkey authorization, a cooldown, or a strict hard cap. - Do not accept arbitrary gas contracts. Use an allowlist or require a known gas-station contract. - Move gas payment behind the same `token-lock-enabled` check used by transfer paths. - For admin-only calls, either disable gas reimbursement or cap it to a small immutable value. - Consider routing sBTC gas reimbursements through the same threshold/pending-operation accounting as direct sBTC transfers.