GET /api/cache/stats?detailed=true exposes raw Redis INFO: memory, client and command metrics are unauthenticated
Labels / Complexity: Frontend · Medium Complexity — Medium
Problem
src/app/api/cache/stats/route.ts serves a GET handler with no authentication. When called as /api/cache/stats?detailed=true, it calls getRedisInfo() and returns raw INFO fields in the JSON response:
const redisMetrics = {
usedMemory: redisInfo.used_memory_human,
usedMemoryRss: redisInfo.used_memory_rss_human,
usedMemoryPeak: redisInfo.used_memory_peak_human,
connectedClients: redisInfo.connected_clients,
totalCommandsProcessed: redisInfo.total_commands_processed,
keyspaceHits: redisInfo.keyspace_hits,
keyspaceMisses: redisInfo.keyspace_misses,
uptimeInSeconds: redisInfo.uptime_in_seconds,
};
The only protected operation on this route is DELETE (wrapped in withCsrf); the GET — including the detailed path — is fully open. This discloses infrastructure internals (Redis version-dependent memory layout, client connections, command throughput, uptime) to anyone on the internet, which is exactly the reconnaissance data an attacker uses before targeting the cache layer, and it is inconsistent with the route's own CSRF protection on the write path. The detailed flag has no consumer in the UI (the only UI references are unrelated page text in src/pages/security/TransactionSecurity.tsx), so the leak exists for no product reason.
Root cause
src/app/api/cache/stats/route.ts line 13: export async function GET(request: NextRequest) with no auth or gate; the detailed=true branch (lines ~48-58) returns getRedisInfo() fields.
Why this is architecturally hard
- The fix needs a policy, not a flag. Options are dropping
detailed entirely, gating it behind an admin check, or moving stats behind an internal-only route — the contributor must pick one and match how the app authenticates admins elsewhere (there is no shared admin guard in the API routes today).
- Monitoring wants the data. A naive removal breaks whatever operational use the detailed stats serve; the contributor must reconcile observability needs with exposure, e.g. by scoping the response rather than deleting the feature.
Acceptance criteria
- An unauthenticated request to
GET /api/cache/stats?detailed=true no longer returns Redis INFO fields (memory, clients, commands, uptime).
- The non-sensitive stats summary (connected/healthy/latency) remains available or is explicitly removed with a documented replacement.
- Any remaining privileged access is authenticated and tested (a test asserts unauthenticated access is denied).
Out of scope
Building an admin dashboard for cache metrics.
Getting started
Files: src/app/api/cache/stats/route.ts, src/lib/redis.ts (getRedisInfo). Commands: npm run lint, npm test. Good first files to read: src/app/api/cache/stats/route.ts, src/lib/redis.ts.
GET /api/cache/stats?detailed=true exposes raw Redis INFO: memory, client and command metrics are unauthenticated
Labels / Complexity: Frontend · Medium Complexity — Medium
Problem
src/app/api/cache/stats/route.tsserves aGEThandler with no authentication. When called as/api/cache/stats?detailed=true, it callsgetRedisInfo()and returns rawINFOfields in the JSON response:The only protected operation on this route is
DELETE(wrapped inwithCsrf); theGET— including thedetailedpath — is fully open. This discloses infrastructure internals (Redis version-dependent memory layout, client connections, command throughput, uptime) to anyone on the internet, which is exactly the reconnaissance data an attacker uses before targeting the cache layer, and it is inconsistent with the route's own CSRF protection on the write path. Thedetailedflag has no consumer in the UI (the only UI references are unrelated page text insrc/pages/security/TransactionSecurity.tsx), so the leak exists for no product reason.Root cause
src/app/api/cache/stats/route.tsline 13:export async function GET(request: NextRequest)with no auth or gate; thedetailed=truebranch (lines ~48-58) returnsgetRedisInfo()fields.Why this is architecturally hard
detailedentirely, gating it behind an admin check, or moving stats behind an internal-only route — the contributor must pick one and match how the app authenticates admins elsewhere (there is no shared admin guard in the API routes today).Acceptance criteria
GET /api/cache/stats?detailed=trueno longer returns RedisINFOfields (memory, clients, commands, uptime).Out of scope
Building an admin dashboard for cache metrics.
Getting started
Files:
src/app/api/cache/stats/route.ts,src/lib/redis.ts(getRedisInfo). Commands:npm run lint,npm test. Good first files to read:src/app/api/cache/stats/route.ts,src/lib/redis.ts.