F-2026-0004·missing-input-validation

Cache Clear Endpoint Accepts Arbitrary Prefix Without Validation

Fixedpentestbackendapigithub.com/bloom-art/api
TL;DR

DELETE /admin/v1/cache/:cachePrefix forwarded an attacker-controlled URL path parameter into a Redis SCAN MATCH → UNLINK loop, allowing a compromised admin key to wipe security-critical prefixes (liquidationExecution, settlementExecution, evmEventStreamDedup) and trigger duplicate liquidations and on-chain event reprocessing.

Severity
MEDIUM
Impact
HIGH
Likelihood
LOW
Method
MManual review
CAT.
Complexity
LOW
Exploitability
MEDIUM
02Section · Description

Description

The DELETE /admin/v1/cache/:cachePrefix endpoint takes a user-supplied URL parameter and performs a wildcard deletion across the entire Redis cache with zero validation or allowlisting.

At clear-cache.controller.ts#L6-L17, the controller is guarded only by AdminApiKeyGuard (role check) and passes the raw URL parameter directly to the service:

typescript
@UseGuards(AdminApiKeyGuard)
@UsePipes(new ValidationPipe({ transform: true }))
@Controller()
export class ClearCacheController {
constructor(private readonly clearCacheService: ClearCacheService) {}
@UseGuards(AdminApiKeyGuard)
@HttpCode(HttpStatus.ACCEPTED)
@Delete(buildAdminApiPath(adminApiEndpoint.cache, apiPathParam.cachePrefix))
public async deleteCacheEntries(@Param("cachePrefix") cachePrefix: string): Promise<void> {
await this.clearCacheService.deleteAllKeysStartsWith(cachePrefix);
// L16, attacker-controlled prefix, no allowlist
}
}

At clear-cache.service.ts#L17-L24, the service appends a colon and delegates to the cache manager:

typescript
public async deleteAllKeysStartsWith(prefix: string): Promise<void> {
if (!prefix) { return; }
this.logger.log(`Deleting all cache keys starting with ${prefix}`);
const count = await this.cacheManagerService.deleteStartsWith(`${prefix}:`);
// L23, passes "${prefix}:*" to Redis SCAN
this.logger.log(`Found and deleted ${count} keys starting with ${prefix}`);
}

At cache-manager.service.ts#L61-L79, the Redis SCAN iterator matches all keys with the given prefix and unlinks them one by one:

typescript
public async deleteStartsWith(prefix: string): Promise<number> {
return this.getClient().execute(async (client) => {
const stream = client.scanIterator({
MATCH: `${prefix}*`, // L65, wildcard glob match on user-controlled prefix
COUNT: 100,
});
let count = 0;
for await (const keysInBatch of stream) {
for (const key of keysInBatch) {
await client.unlink(key); // L73, deletes every matching key
count++;
}
}
return count;
});
}

The system defines 55 cache entry prefixes at base-cache.type.ts#L1-L55, all of which are vulnerable to targeted deletion. The security-critical ones include cancelPositionAntiReplay, evmEventStreamDedup, evmNonce, liquidatablePositionsByMint, liquidationExecution, settlementExecution, and unwindExecution.

Vulnerable scenario:

  1. An attacker obtains or compromises an admin API key (or a compromised insider with admin access acts maliciously).
  2. The attacker calls DELETE /admin/v1/cache/liquidationExecution. This deletes the LiquidationAntiReplayStore entries, which use executeOnlyOnce() to prevent re-executing liquidations. With the anti-replay keys cleared, the next price check cycle will re-trigger liquidation flows for positions that have already been liquidated, potentially creating double-sell orders on Polymarket's CLOB and causing financial loss.
03Section · Impact

Impact

Financial Loss. Clearing liquidationExecution or settlementExecution anti-replay stores can trigger duplicate liquidation/settlement transactions, causing double-sell orders on Polymarket that result in direct financial loss from the protocol's capital pool.

State Corruption. Clearing evmEventStreamDedup causes reprocessing of on-chain events, potentially corrupting position state by applying the same state transition twice.

04Section · Recommendation

Recommendation

Even with the admin guard, consider implementing a strict allowlist of cache prefixes that can be cleared (e.g., only non-critical caches like offerExecutionData, inferenceMarketData, leverageThreshold).

Add audit logging with alerting for all cache clear operations.

Consider requiring a confirmation token or two-step approval for destructive cache operations.

05Section · Resolution

Resolution

Fixed by code removal in commit f0379e821 (2026-04-18), the controller, service, and route constants were all deleted.

Status
Fixed
Fix commit
f0379e821
Fix date
2026-04-18
F-2026-0004

oog
zealynx

Smart Contract Security Digest

Monthly exploit breakdowns, audit checklists, and DeFi security research — straight to your inbox

© 2026 Zealynx