# SAFE-02 — high-value pending operations never enter `spent-this-period` ## Scope Deployed contracts reviewed: - `SPV9K21TBFAK4KNRJXF5DFP8N7W46G4V9RCJDC22.pillar-safe-v2` - `SPV9K21TBFAK4KNRJXF5DFP8N7W46G4V9RCJDC22.jing-mm-safe` Source used for the line references below: [Hiro mainnet source API](https://api.mainnet.hiro.so/v2/contracts/source/SPV9K21TBFAK4KNRJXF5DFP8N7W46G4V9RCJDC22/pillar-safe-v2?proof=0), observed 2026-07-23. ## Finding (Low / Medium depending on the intended limit) The wallet maintains a per-period `spent-this-period` accumulator and uses it in `would-exceed-stx-threshold` / `would-exceed-sbtc-threshold`. Direct, below-threshold transfers update that accumulator through `add-spent-stx` / `add-spent-sbtc`, but the over-threshold branch only creates a pending operation. The later execute functions transfer the stored amount without calling either accumulator update. This means the wallet's observable period spend remains unchanged after any number of executed high-value pending transfers. A caller with the required passkey approvals can therefore execute multiple high-value operations while the spend meter still reports zero, and can continue to use the full below-threshold allowance in the same period. If `spent-this-period` is intended as a cumulative safety limit (the surrounding threshold logic strongly suggests that), the limit is not enforced for the pending path. If it is only telemetry, the implementation and public interface should say so explicitly. ## Evidence ### STX path - `would-exceed-stx-threshold` reads `spent-this-period` (lines 336–343). - `stx-transfer` creates a pending operation when the threshold is exceeded (lines 421–424), but only calls `add-spent-stx` in the immediate-transfer branch (lines 426–428). - `execute-pending-stx-transfer` transfers `op.amount` after the cooldown (lines 442–464) and never calls `add-spent-stx`. ### sBTC path - `would-exceed-sbtc-threshold` reads the same accumulator (lines 345–352). - `sip010-transfer` creates the pending operation in the over-threshold branch (lines 513–519), while `add-spent-sbtc` is only in the immediate branch (lines 522–524). - `execute-pending-sbtc-transfer` transfers the stored amount (lines 537–562) without updating the accumulator. - `sbtc-initiate-withdrawal` checks `amount + max-fee` and creates a pending operation (lines 609–616); `execute-pending-sbtc-withdrawal` later locks the stored amount and fee (lines 634–658) without `add-spent-sbtc`. The same control flow is present in `jing-mm-safe` (the deployed source has the same `add-spent-*` calls only in immediate branches and no calls in the execute-pending branches). ## Minimal state-machine reproduction Assume the default configuration (`stx-threshold = 100_000_000`, `cooldown-period = 144`) and a fresh period where `spent-this-period.stx = 0`: 1. Submit two valid passkey-authorized `stx-transfer` calls for 100,000,001 STX micro-units to two different recipients. Each call takes the over-threshold branch and creates a pending operation; neither changes `spent-this-period`. 2. After the cooldown, an authorized admin executes both pending operation IDs. 3. Query the accumulator through the contract state / subsequent threshold behavior. The period spend is still zero because neither execute path calls `add-spent-stx`. 4. A subsequent direct transfer of 100,000,000 micro-units is treated as within the threshold for the period even though the wallet has already executed 200,000,002 micro-units through pending operations. The sBTC and withdrawal variants follow the identical sequence with `spent-this-period.sbtc`. ## Impact This does not remove the per-operation passkey/cooldown requirement. It does remove the cumulative period accounting for the high-value path, so monitoring and any policy that relies on the accumulator under-report actual wallet outflow and the intended daily/period budget can be exceeded by a sequence of individually approved pending operations. ## Recommended fix On successful execution, call `add-spent-stx` / `add-spent-sbtc` with the actual amount (for withdrawal, `amount + max-fee`) before the external transfer, or reserve the amount in `create-pending-operation` and release the reservation on veto/failure. Add regression tests covering two pending operations followed by a below-threshold transfer in the same period. ## Existing-submission check The public bounty submission list was checked before filing. Existing reports cover cooldown zeroing, execute-now hash contents, memo persistence, nested-call authorization, RFQ scope, and several token-lock/threshold issues; none describes the missing `spent-this-period` update on the pending execution paths.