An app with no storage quotas or cleanup logic accumulates cache data indefinitely. On devices with limited storage — a real constraint on mid-range and older Android handsets — this causes the OS to terminate the app, deny further writes, or prompt the user to clear data. ISO 25010:2011 performance-efficiency.resource-utilisation requires that apps bound their resource footprint. Beyond the device-health impact, unbounded storage growth retains data that should have been evicted: stale records, orphaned queue items, and superseded cache entries that inflate the attack surface for physical device compromise.
Low because storage accumulation degrades device health and UX over time rather than enabling immediate exploitation, but it becomes a reliability defect on constrained devices.
Implement a storage quota check and an LRU or age-based eviction function. Call the eviction function on app launch and after any large batch write.
import AsyncStorage from '@react-native-async-storage/async-storage';
const SOFT_LIMIT_BYTES = 50 * 1024 * 1024; // 50 MB
export async function storageUsageBytes(): Promise<number> {
const keys = await AsyncStorage.getAllKeys();
const pairs = await AsyncStorage.multiGet(keys);
return pairs.reduce((sum, [, v]) => sum + (v?.length ?? 0), 0);
}
export async function evictOldCache(maxAgeDays = 7) {
const cutoff = Date.now() - maxAgeDays * 86_400_000;
const keys = await AsyncStorage.getAllKeys();
const cacheKeys = keys.filter(k => k.startsWith('cache_'));
const stale = cacheKeys.filter(k => {
const ts = parseInt(k.split('_').pop() ?? '0', 10);
return ts < cutoff;
});
if (stale.length) await AsyncStorage.multiRemove(stale);
}
// On app launch:
const used = await storageUsageBytes();
if (used > SOFT_LIMIT_BYTES) await evictOldCache(3); // tighten window under pressure
Apply the same eviction logic to offline queue entries older than 30 days — a queued action that old is almost certainly no longer relevant.
ID: mobile-offline-storage.storage-security.storage-limits
Severity: low
What to look for: Examine storage usage and cleanup. Look for logic that monitors device storage, implements storage quotas, or cleans up old data when storage approaches limits.
Pass criteria: Count all storage cleanup or quota management functions. App has at least 1 storage management mechanism: quota checking, periodic cleanup, or LRU eviction. Storage usage should be bounded to no more than 50MB of local cache. Old data is cleaned up when nearing device storage limits.
Fail criteria: No storage management. App could accumulate unlimited data and fill the device.
Skip (N/A) when: Never — storage management is important for mobile.
Detail on fail: "No storage management found. App caches data indefinitely and could fill device storage over time."
Remediation: Implement storage quota management:
import AsyncStorage from '@react-native-async-storage/async-storage';
export async function getStorageUsage(): Promise<{
used: number;
limit: number;
}> {
try {
const allKeys = await AsyncStorage.getAllKeys();
let totalSize = 0;
const items = await AsyncStorage.multiGet(allKeys);
items.forEach(([_, value]) => {
if (value) {
totalSize += value.length;
}
});
// Estimate limit (iOS: ~100MB, Android varies)
const LIMIT = 50 * 1024 * 1024; // 50MB soft limit
return {
used: totalSize,
limit: LIMIT,
};
} catch (error) {
console.error('Failed to calculate storage usage:', error);
return { used: 0, limit: 0 };
}
}
export async function cleanupOldCache(daysToKeep = 7) {
try {
const allKeys = await AsyncStorage.getAllKeys();
const cachePrefix = 'cache_';
const cutoffTime = Date.now() - daysToKeep * 24 * 60 * 60 * 1000;
const keysToDelete = allKeys.filter(key => {
if (!key.startsWith(cachePrefix)) return false;
const timestamp = parseInt(key.split('_')[1], 10);
return timestamp < cutoffTime;
});
if (keysToDelete.length > 0) {
await AsyncStorage.multiRemove(keysToDelete);
console.log(`Cleaned up ${keysToDelete.length} old cache entries`);
}
} catch (error) {
console.error('Failed to cleanup cache:', error);
}
}
// Periodically check storage and cleanup
setInterval(async () => {
const usage = await getStorageUsage();
const usagePercent = usage.used / usage.limit;
if (usagePercent > 0.8) {
console.warn('Storage usage above 80%, cleaning up...');
await cleanupOldCache(3); // Keep only 3 days
}
}, 60 * 60 * 1000); // Check hourly