Logic flaw in the subscription's handling functions execution
Subscription functions in Initiator can be called by both EOAs and SCAs, breaking the SubExecutor mapping invariant and preventing modification, removal, and payment initiation for subscriptions registered directly from the Initiator.
Description
Important state-changing functions in the Initiator contract can currently be called by both EOAs and SCAs. If the executor is an EOA without going through Bastion SDK, then the subscriber will be the address of the EOA account and therefore the following attack vectors are present:
-
Subscriptions registered from the
Initiatorcontract can not be modified in any way compared to those added from theSubExecutorcontract.Example: Let's say that afterward, the user wants to modify his subscription through the SubExecutor's logic by calling
modifySubscription(). What would happen then is that because in this same function, thesubscriberis themsg.sender, the subscription previously created through my EOA account will not be able to be modified, because the struct that will be returned will be the one to which the address of the SCA's SubExecutor is mapped and not the one mapped to users' EOA address. -
The
initiatePayment()function will revert every time since in theSubExecutor.processPayment()there is a check that ensures themsg.senderis the creator of the subscription which in this case will be the address of theInitiatorcontract.
require(msg.sender == sub.initiator, "Only the initiator can initiate payments");
- The
removeSubscription()function executed from an EOA can not delete a subscription created from theSubExecutorcontract.
All of these problems arise from the scenario where the user is able to call the Initiator.registerSubscription() function without first calling SubExecutor.createSubscription().
Expected functions execution flow should be:
SubExecutor.createSubscription()Initiator.registerSubscription()Initiator.initiatePayment()SubExecutor.processPayment()
function registerSubscription(address _subscriber, uint256 _amount,uint256 _validUntil, uint256 _paymentInterval, address _erc20Token)public {require(msg.sender == _subscriber, "Only the subscriber can register a subscription");ISubExecutor.SubStorage memory sub = ISubExecutor.SubStorage({amount: _amount,validUntil: _validUntil,validAfter: block.timestamp,paymentInterval: _paymentInterval,subscriber: _subscriber,initiator: address(this),erc20TokensValid: _erc20Token == address(0) ? false : true,erc20Token: _erc20Token});}
Impact
The subscription model is broken end to end for any user who interacts with Initiator directly: registered subscriptions cannot be modified, payments cannot be initiated, and removals leave stale state. Any EOA that bypasses the SDK ends up with an unusable subscription and may have funds tied up.
Recommendation
To address this vulnerability, it is crucial to have the core logic and validation in the Initiator.sol contract and restrict only the SubExecutor.sol contract to call registerSubscription(), removeSubscription() and initiatePayment() functions.
Resolution
Team Response: Acknowledged and fixed as suggested.

