Skip to Content
Deneva MCPComponentsIp Block

IP block

Source: src/security/ip-block.service.ts

Tracks bursts of authentication failures per source IP. After 10 failures in a 1-hour rolling window, that IP is blocked for 1 hour. While blocked, all /mcp/* requests from that IP get 401 + an auth.blocked_ip audit row, even if the request carries a valid API key.

Why?

Stops a credential-stuffing attacker from cycling through stolen keys at full request rate. Works in concert with the global per-IP rate limiter — that one caps request volume, this one caps failed-auth volume.

State

Two in-memory maps:

MapKeyValue
failuresIP{ timestamps: number[] } — sorted oldest-first, trimmed when entries fall outside the 1h window
blocksIPepoch ms when the block expires

When the 10th failure lands, the IP moves from failures to blocks. While in blocks, failures is irrelevant — the block is the active state.

API

isBlocked(ip): boolean // cheap O(1) check, used by tenantAuthPlugin recordFailure(ip): boolean // returns true on the call that tripped the threshold startIpBlockCleanup(): void // call once at startup; trims expired state every 5 min _resetIpBlockState(): void // test-only

Single-process assumption

State is in-process memory. Phase 5’s deploy uses a single Node process per host (systemd unit, not PM2 cluster mode). If we ever scale horizontally, this map must move to Redis with INCR + TTL, or to a blocked_ips DB table.

Tests

tests/integration/ip-block.test.ts:

  • under threshold → not blocked
  • on 10th failure → tripped + blocked
  • IP isolation (other IPs unaffected)

The end-to-end “valid key from blocked IP also fails” test lives in the auth integration tests (Phase 1 §E5).