Summary
Every external function in MidnightBundles accepts midnight as a caller supplied parameter, which is not needed and could be set in the constructor instead for better phishing resistance.
Vulnerability Detail
The bundler uses the supplied midnight for three operations in the same call:
- The auth gate:
IMidnight(midnight).isAuthorized(taker, msg.sender). - Approval:
_forceApproveMax(token, midnight). - External dispatch:
IMidnight(midnight).take(...)/repay/supplyCollateral/withdrawCollateral.
A phishing frontend, malicious SDK, or compromised wallet induces the victim to call the bundler with midnight = EvilMidnight, and allows token theft bypassing the checks above.
Impact
Theft of all tokens pulled in a single call, bounded by the user's signed permit amount or approval.
Code Snippet
For example:
function repayAndWithdrawCollateral(
address midnight,
Market memory market,
uint256 assets,
address onBehalf,
TokenPermit memory loanTokenPermit,
...
) external {
require(onBehalf == msg.sender || IMidnight(midnight).isAuthorized(onBehalf, msg.sender), Unauthorized());
...
_pullToken(loanToken, msg.sender, assets, loanTokenPermit);
_forceApproveMax(loanToken, midnight);
IMidnight(midnight).repay(market, units, onBehalf, address(0), "");
...
}The same pattern repeats at lines 38, 106, 170, and 243.
Tool Used
Manual Review
Recommendation
Hardcode midnight as an immutable set in the constructor and remove the parameter from every external function. This mirrors how EcrecoverRatifier already pins its MIDNIGHT:
address public immutable MIDNIGHT;
constructor(address midnight) {
MIDNIGHT = midnight;
}