It's been another month packed with audits and unexpected findings. This time, our discoveries go beyond Solidity smart contracts, uncovering security flaws in TypeScript-based applications as well. From classic blockchain vulnerabilities to backend risks, our findings highlight the challenges developers face in securing both on-chain and off-chain components.
In this month’s article we identify issues stemming from front-running, missing access control, wrong value on revert, and JavaScript application issues like max connections DoS and restrictive API rate limiting. A detailed description of the problem and a robust solution is provided for each issue.
Issue 1: Incorrect Assembly Revert Handling **Impact:** Low **Likelihood:** High
Description Take a look at the following code snippet:
(bool success, bytes memory returnData) = target.call(data);if (!success) { assembly { revert(add(returnData, 32), returnData) } } ```
We’ve seen it thousands of times. Most security researchers would just pass it by without a second thought, but there is an issue here. When the call to `target` fails, it returns an error message stored in memory as `returnData`. The first 32 bytes encode the length of the actual error message.
`add(returnData, 32)` moves the pointer forward by 32 bytes, skipping the length field and pointing directly to the error message. However, the second parameter to `revert` is supposed to be the **length** of the message — not the pointer. This mistake causes the revert to return an incorrect error.
Recommendation To fix this, use `mload(returnData)` to read the correct length:
(bool success, bytes memory returnData) = target.call(data);
if (!success) {
assembly {
revert(add(returnData, 32), mload(returnData))
}
}
---
Issue 2: Front-Running Attack Leads to Permanent ETH Loss During Account Creation **Impact:** High **Likelihood:** High
Description This vulnerability affects a protocol that supports ERC-4337 Account Abstraction. The `createAccount` function allows users to deploy new wallets and pre-fund them with ETH.
An attacker can front-run a user’s transaction using the same `_deviceUniqueIdentifier` and `_deviceWalletOwnerKey`. This lets the attacker deploy the wallet first, causing the victim’s transaction to return the attacker’s wallet address instead of deploying a new one. As a result, the user’s ETH becomes permanently stuck in the factory contract.
Recommendation Revert if the wallet already exists and ETH was sent:
if (wallet != address(0)) {
require(msg.value == 0, "ETH will be lost if wallet already exists");
return DeviceWallet(payable(wallet));
}
Alternatively, refund the ETH to the user if the wallet already exists.
---
Issue 3: Malicious User with Registered Device Wallet Can Steal an Account Wallet **Impact:** High **Likelihood:** High
Description The protocol defines two wallet types: device and account. A device wallet can update its associated account wallet through the following function:
function updateDeviceWalletAssociatedWithAccountWallet(
address _accountWalletAddress,
address _deviceWalletAddress
) external onlyDeviceWallet {
isAccountWalletValid[_accountWalletAddress] = _deviceWalletAddress;
emit UpdatedDeviceWalletassociatedWithAccountWallet(_accountWalletAddress, _deviceWalletAddress);
}
While the `onlyDeviceWallet` modifier ensures that only valid device wallets can call this function, it **does not verify ownership**. Any valid device wallet could link itself to any account wallet, effectively hijacking it.
Recommendation Add an ownership check before updating:
require(
isAccountWalletValid[_accountWalletAddress] == address(0) ||
isAccountWalletValid[_accountWalletAddress] == msg.sender,
"Unauthorised caller or already assigned"
);
---
Issue 4: Global Connection Limit Vulnerable to DoS Attack **Impact:** High **Likelihood:** High
Description In the TypeScript middleware, the following configuration was found:
private static readonly MAX_ACTIVE_CONNECTIONS = 1000;
While limiting connections is good practice, the system tracks connections **globally** instead of per IP. This allows an attacker to flood the app with fake connections until the limit is reached — preventing legitimate users from connecting.
Recommendation Implement **per-IP** connection limits instead of global ones. Consider leveraging **Cloudflare** or a Web Application Firewall (WAF) for rate-limiting and DDoS mitigation.
---
Issue 5: Overly Restrictive API Rate Limiting Could DoS Users **Impact:** Medium **Likelihood:** Medium
Description The API’s rate limiter is configured as follows:
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
});
This configuration is too restrictive for normal application usage, especially in shared environments (e.g., offices using the same IP). Legitimate users can get blocked, causing a **self-imposed denial of service**.
Recommendation Increase the rate limit or make it more granular:
- Raise to **500–1000 requests per 10 minutes**.
- Apply endpoint-specific limits instead of a single global cap.
---
Outro We've covered five critical security vulnerabilities spanning Solidity, JavaScript, and API design. Addressing these issues — from incorrect revert handling to front-running attacks and restrictive rate limiting — is crucial for improving both **security** and **usability** in decentralized applications.
By implementing these fixes, developers can protect user funds, prevent exploits, and strengthen overall system resilience.
Stay tuned for next month's deep dive, where we'll uncover more vulnerabilities and share best practices for securing the Web3 ecosystem.
Until then, stay vigilant and keep building securely! 🚀
Don’t forget to follow **CD Security** on Twitter, as well as the author **chrisdior.eth**, for daily Web3 insights and security tips.
