
Security News
RubyGems Adds Cooldown Feature to Bundler for Newly Published Gems
RubyGems and Bundler 4.0.13 introduced an opt-in cooldown feature that delays newly published gems during dependency resolution.
@frontmcp/guard
Advanced tools
Rate limiting, concurrency control, timeout, IP filtering, and traffic guard utilities for FrontMCP
Rate limiting, concurrency control, timeout enforcement, and IP filtering for FrontMCP applications. Built on the StorageAdapter interface from @frontmcp/utils, so it works with Memory, Redis, Vercel KV, and Upstash backends out of the box.
npm install @frontmcp/guard
# or
yarn add @frontmcp/guard
Peer dependency:
npm install zod@^4
import { createGuardManager } from '@frontmcp/guard';
const guard = await createGuardManager({
config: {
enabled: true,
global: { maxRequests: 100, windowMs: 60_000 },
defaultTimeout: { executeMs: 30_000 },
ipFilter: {
denyList: ['10.0.0.0/8'],
defaultAction: 'allow',
},
},
});
// Check IP filter
const ipResult = guard.checkIpFilter('203.0.113.5');
// Check rate limit
const rlResult = await guard.checkRateLimit('my-tool', undefined, {
sessionId: 'sess-123',
});
// Acquire concurrency slot
const ticket = await guard.acquireSemaphore(
'my-tool',
{ maxConcurrent: 5 },
{
sessionId: 'sess-123',
},
);
try {
// ... do work ...
} finally {
await ticket?.release();
}
| Module | Main Export | Purpose |
|---|---|---|
errors/ | GuardError + subclasses | Error hierarchy with machine-readable codes and HTTP status codes |
schemas/ | guardConfigSchema | Zod validation schemas for all configuration objects |
partition-key/ | resolvePartitionKey | Resolves request partition keys (ip, session, userId, global, custom) |
rate-limit/ | SlidingWindowRateLimiter | Sliding window counter rate limiter |
concurrency/ | DistributedSemaphore | Distributed semaphore for concurrency control |
timeout/ | withTimeout | Async execution timeout wrapper |
ip-filter/ | IpFilter | IP allow/deny list filtering with CIDR support |
manager/ | GuardManager + createGuardManager | Orchestrator combining all guard modules |
Uses the sliding window counter algorithm. Two adjacent fixed-window counters are maintained; their counts are combined with a time-weighted interpolation to approximate a true sliding window. This provides O(1) storage per key while avoiding the burst edges of simple fixed-window counters.
import { SlidingWindowRateLimiter } from '@frontmcp/guard';
const limiter = new SlidingWindowRateLimiter(storageAdapter);
const result = await limiter.check(
'user:42', // partition key
100, // max requests
60_000, // window in ms
);
if (!result.allowed) {
console.log(`Rate limited. Retry after ${result.retryAfterMs}ms`);
}
The RateLimitResult includes:
allowed -- whether the request can proceedremaining -- approximate remaining requests in this windowresetMs -- milliseconds until the current window resetsretryAfterMs -- (only when blocked) suggested retry delayThe DistributedSemaphore limits the number of concurrent executions for a given key. Each execution acquires a "ticket" via atomic incr on a counter key. Individual ticket keys are stored with a TTL for crash safety -- if a process dies without releasing, the ticket TTL expires and the counter self-corrects.
When all slots are full, callers can optionally wait in a queue with exponential backoff polling. If the storage backend supports pub/sub, slot releases trigger immediate wakeup of waiting callers.
import { DistributedSemaphore } from '@frontmcp/guard';
const semaphore = new DistributedSemaphore(storageAdapter, 300 /* ticket TTL seconds */);
const ticket = await semaphore.acquire(
'my-tool:global', // key
5, // max concurrent
10_000, // queue timeout ms (0 = no wait)
'my-tool', // entity name (for error messages)
);
if (!ticket) {
// Rejected (queueTimeoutMs was 0 and all slots full)
return;
}
try {
await doWork();
} finally {
await ticket.release();
}
Additional methods:
getActiveCount(key) -- returns the current number of active ticketsforceReset(key) -- resets the counter and removes all ticket keysThe withTimeout utility wraps an async function with a deadline using AbortController + Promise.race. Throws ExecutionTimeoutError if the function does not complete within the specified duration.
import { withTimeout } from '@frontmcp/guard';
const result = await withTimeout(
() => fetchData(),
5_000, // timeout in ms
'fetch-data', // entity name (for error messages)
);
The IpFilter class checks client IP addresses against allow and deny lists. Supports individual IP addresses and CIDR notation for both IPv4 and IPv6. IPv4-mapped IPv6 addresses (e.g., ::ffff:192.168.1.1) are also handled.
Precedence: the deny list is always checked first. If an IP matches the deny list, it is blocked regardless of the allow list. If an allow list is configured and the IP does not match any allow rule, the defaultAction determines the outcome.
All matching is performed using bigint arithmetic for correctness across the full IPv6 address space.
import { IpFilter } from '@frontmcp/guard';
const filter = new IpFilter({
allowList: ['192.168.0.0/16'],
denyList: ['192.168.1.100'],
defaultAction: 'deny',
});
const result = filter.check('192.168.2.50');
// { allowed: true, reason: 'allowlisted', matchedRule: '192.168.0.0/16' }
const blocked = filter.check('192.168.1.100');
// { allowed: false, reason: 'denylisted', matchedRule: '192.168.1.100' }
The isAllowListed(ip) method provides a quick check for whether an IP is on the allow list, which can be used to bypass rate limiting for trusted addresses.
Partition keys determine how rate limits and concurrency slots are bucketed. Built-in strategies:
| Strategy | Behavior |
|---|---|
'global' | Single shared bucket for all callers (default) |
'ip' | One bucket per client IP address |
'session' | One bucket per session ID |
'userId' | One bucket per authenticated user ID (falls back to session ID) |
You can also pass a custom function:
const config: RateLimitConfig = {
maxRequests: 100,
windowMs: 60_000,
partitionBy: (ctx) => `org:${ctx.userId?.split(':')[0]}`,
};
The PartitionKeyContext passed to custom functions contains sessionId (always present), plus optional clientIp and userId.
GuardManager is the central orchestrator. It combines rate limiting, concurrency control, IP filtering, and timeout configuration into a single interface. The createGuardManager factory handles storage initialization.
The manager supports two levels of configuration:
global, globalConcurrency)defaultRateLimit, defaultConcurrency, defaultTimeout)Per-entity configuration takes precedence over defaults.
import { createGuardManager } from '@frontmcp/guard';
const guard = await createGuardManager({
config: {
enabled: true,
storage: { provider: 'redis', host: 'localhost', port: 6379 },
keyPrefix: 'myapp:guard:',
global: { maxRequests: 1000, windowMs: 60_000, partitionBy: 'ip' },
globalConcurrency: { maxConcurrent: 50, partitionBy: 'global' },
defaultRateLimit: { maxRequests: 100, windowMs: 60_000, partitionBy: 'session' },
defaultConcurrency: { maxConcurrent: 10, queueTimeoutMs: 5_000 },
defaultTimeout: { executeMs: 30_000 },
ipFilter: {
denyList: ['10.0.0.0/8'],
allowList: ['10.0.1.0/24'],
defaultAction: 'allow',
trustProxy: true,
trustedProxyDepth: 2,
},
},
logger: console,
});
// Use the manager
const globalRl = await guard.checkGlobalRateLimit({ sessionId: 'sess-1' });
const entityRl = await guard.checkRateLimit('my-tool', undefined, { sessionId: 'sess-1' });
const ticket = await guard.acquireSemaphore('my-tool', undefined, { sessionId: 'sess-1' });
// Cleanup
await guard.destroy();
GuardConfig| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | -- | Whether the guard system is active |
storage | StorageConfig | memory | Storage backend configuration |
keyPrefix | string | 'mcp:guard:' | Prefix for all storage keys |
global | RateLimitConfig | -- | Global rate limit for ALL requests |
globalConcurrency | ConcurrencyConfig | -- | Global concurrency limit |
defaultRateLimit | RateLimitConfig | -- | Default rate limit for entities without explicit config |
defaultConcurrency | ConcurrencyConfig | -- | Default concurrency for entities without explicit config |
defaultTimeout | TimeoutConfig | -- | Default timeout for entity execution |
ipFilter | IpFilterConfig | -- | IP filtering configuration |
RateLimitConfig| Field | Type | Default | Description |
|---|---|---|---|
maxRequests | number | -- | Maximum requests allowed per window |
windowMs | number | 60000 | Time window in milliseconds |
partitionBy | PartitionKey | 'global' | Partition key strategy |
ConcurrencyConfig| Field | Type | Default | Description |
|---|---|---|---|
maxConcurrent | number | -- | Maximum concurrent executions |
queueTimeoutMs | number | 0 | Max wait time in queue (0 = reject immediately) |
partitionBy | PartitionKey | 'global' | Partition key strategy |
TimeoutConfig| Field | Type | Default | Description |
|---|---|---|---|
executeMs | number | -- | Maximum execution time in milliseconds |
IpFilterConfig| Field | Type | Default | Description |
|---|---|---|---|
allowList | string[] | -- | IP addresses or CIDR ranges to always allow |
denyList | string[] | -- | IP addresses or CIDR ranges to always block |
defaultAction | 'allow' | 'deny' | 'allow' | Action when IP matches neither list |
trustProxy | boolean | false | Trust X-Forwarded-For header |
trustedProxyDepth | number | 1 | Max proxies to trust from X-Forwarded-For |
The guard library delegates all persistence to the StorageAdapter interface from @frontmcp/utils. Choose a backend based on your deployment:
| Backend | Use Case |
|---|---|
| Memory | Development, testing, single-process deployments. Not suitable for distributed setups. |
| Redis | Production multi-instance deployments. Provides atomic operations and optional pub/sub for semaphore wakeup. |
| Vercel KV | Vercel-hosted applications. Redis-compatible API. |
| Upstash | Serverless environments. HTTP-based Redis compatible. |
If no storage config is provided, the factory falls back to in-memory storage and logs a warning.
All errors extend GuardError, which carries a machine-readable code and an HTTP statusCode.
| Error Class | Code | Status | When |
|---|---|---|---|
GuardError | (base) | -- | Base class for all guard errors |
ExecutionTimeoutError | EXECUTION_TIMEOUT | 408 | Execution exceeds configured timeout |
ConcurrencyLimitError | CONCURRENCY_LIMIT | 429 | Concurrency limit reached (no queue or queue disabled) |
QueueTimeoutError | QUEUE_TIMEOUT | 429 | Waited in concurrency queue but timed out |
IpBlockedError | IP_BLOCKED | 403 | Client IP is on the deny list |
IpNotAllowedError | IP_NOT_ALLOWED | 403 | Client IP is not on the allow list |
import { GuardError, ExecutionTimeoutError } from '@frontmcp/guard';
try {
await withTimeout(() => slowOp(), 5_000, 'slow-op');
} catch (err) {
if (err instanceof ExecutionTimeoutError) {
console.log(err.code); // 'EXECUTION_TIMEOUT'
console.log(err.statusCode); // 408
console.log(err.timeoutMs); // 5000
}
}
| Export | Module | Description |
|---|---|---|
SlidingWindowRateLimiter | rate-limit | Sliding window counter rate limiter |
DistributedSemaphore | concurrency | Distributed semaphore with ticket-based tracking |
IpFilter | ip-filter | IP allow/deny list with CIDR support |
GuardManager | manager | Central orchestrator for all guard modules |
| Export | Module | Description |
|---|---|---|
withTimeout | timeout | Wrap an async function with a deadline |
resolvePartitionKey | partition-key | Resolve a partition key string from strategy and context |
buildStorageKey | partition-key | Build a namespaced storage key |
createGuardManager | manager | Factory to create and initialize a GuardManager |
| Export | Module | Description |
|---|---|---|
GuardError | errors | Base error class |
ExecutionTimeoutError | errors | Timeout exceeded |
ConcurrencyLimitError | errors | Concurrency limit reached |
QueueTimeoutError | errors | Queue wait timed out |
IpBlockedError | errors | IP on deny list |
IpNotAllowedError | errors | IP not on allow list |
| Export | Module | Description |
|---|---|---|
partitionKeySchema | schemas | Validates partition key strategy or custom function |
rateLimitConfigSchema | schemas | Validates RateLimitConfig |
concurrencyConfigSchema | schemas | Validates ConcurrencyConfig |
timeoutConfigSchema | schemas | Validates TimeoutConfig |
ipFilterConfigSchema | schemas | Validates IpFilterConfig |
guardConfigSchema | schemas | Validates the full GuardConfig |
| Export | Module | Description |
|---|---|---|
RateLimitConfig | rate-limit | Rate limit configuration |
RateLimitResult | rate-limit | Result from a rate limit check |
ConcurrencyConfig | concurrency | Concurrency control configuration |
SemaphoreTicket | concurrency | Acquired concurrency slot handle |
TimeoutConfig | timeout | Timeout configuration |
IpFilterConfig | ip-filter | IP filter configuration |
IpFilterResult | ip-filter | Result from an IP filter check |
PartitionKeyStrategy | partition-key | Built-in partition key strategies union |
CustomPartitionKeyFn | partition-key | Custom partition key resolver function |
PartitionKeyContext | partition-key | Context passed to partition key resolvers |
PartitionKey | partition-key | Union of strategy string or custom function |
GuardConfig | manager | Full guard configuration |
GuardLogger | manager | Minimal logger interface |
CreateGuardManagerArgs | manager | Arguments for createGuardManager |
Apache-2.0
FAQs
Rate limiting, concurrency control, timeout, IP filtering, and traffic guard utilities for FrontMCP
The npm package @frontmcp/guard receives a total of 1,534 weekly downloads. As such, @frontmcp/guard popularity was classified as popular.
We found that @frontmcp/guard demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
RubyGems and Bundler 4.0.13 introduced an opt-in cooldown feature that delays newly published gems during dependency resolution.

Security News
pnpm 11.5 now recognizes npm staged publish approvals in release metadata, preventing those releases from being mistaken for lower-trust package publishes.

Security News
Federal audit finds NIST lacked a plan to clear the NVD backlog, wasted funds on duplicate work, and delayed use of CISA data.