Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/config/types.discord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,39 @@ export type DiscordExecApprovalConfig = {
cleanupAfterResolve?: boolean;
};

export type DiscordAgentComponentsRateLimitConfig = {
/** Enable or disable rate limiting. Default: true */
enabled?: boolean;
/** Maximum interactions allowed in time window. Default: 5 */
maxInteractions?: number;
/** Time window in milliseconds. Default: 10000 (10s) */
windowMs?: number;
/** Cleanup interval for stale records. Default: 60000 (1min) */
cleanupIntervalMs?: number;
/** Per-component-type limits */
componentTypeLimits?: {
button?: {
maxInteractions?: number;
windowMs?: number;
};
selectMenu?: {
maxInteractions?: number;
windowMs?: number;
};
};
/** Track per component ID vs per component type. Default: false */
perComponentId?: boolean;
/** Message shown when rate limited. Default: generic message */
rateLimitMessage?: string;
/** Log rate limit violations. Default: true */
logViolations?: boolean;
};

export type DiscordAgentComponentsConfig = {
/** Enable agent-controlled interactive components (buttons, select menus). Default: true. */
enabled?: boolean;
/** Rate limiting configuration for component interactions. Addresses CWE-770. */
rateLimit?: DiscordAgentComponentsRateLimitConfig;
};

export type DiscordAccountConfig = {
Expand Down
37 changes: 37 additions & 0 deletions src/config/zod-schema.providers-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,43 @@ export const DiscordAccountSchema = z
})
.strict()
.optional(),
agentComponents: z
.object({
enabled: z.boolean().optional(),
rateLimit: z
.object({
enabled: z.boolean().optional(),
maxInteractions: z.number().int().positive().optional(),
windowMs: z.number().int().positive().optional(),
cleanupIntervalMs: z.number().int().positive().optional(),
componentTypeLimits: z
.object({
button: z
.object({
maxInteractions: z.number().int().positive().optional(),
windowMs: z.number().int().positive().optional(),
})
.strict()
.optional(),
selectMenu: z
.object({
maxInteractions: z.number().int().positive().optional(),
windowMs: z.number().int().positive().optional(),
})
.strict()
.optional(),
})
.strict()
.optional(),
perComponentId: z.boolean().optional(),
rateLimitMessage: z.string().optional(),
logViolations: z.boolean().optional(),
})
.strict()
.optional(),
})
.strict()
.optional(),
responsePrefix: z.string().optional(),
})
.strict();
Expand Down
268 changes: 268 additions & 0 deletions src/discord/monitor/RATE_LIMITING_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
# Discord Component Interaction Rate Limiting

## Security Fix: CWE-770 - Insufficient Rate Limiting on Component Interactions

This implementation addresses the security vulnerability where Discord component interactions (buttons and select menus) were not rate-limited, potentially allowing users to flood the system with interactions and cause denial of service or resource exhaustion.

## Overview

The rate limiting system implements a **sliding window log algorithm** to provide fair and effective rate limiting that prevents both sustained abuse and burst attacks.

### Key Security Features

- **Per-user rate limiting**: Each user has their own rate limit bucket
- **Per-channel separation**: Rate limits are tracked separately per channel
- **Per-component-type separation**: Buttons and select menus have separate limits
- **Pre-authorization checks**: Rate limiting happens BEFORE authorization to prevent resource exhaustion attacks
- **Configurable limits**: Different limits can be set for different component types
- **Memory leak protection**: Automatic cleanup of stale rate limit records

## Implementation Files

### Core Components

1. **`component-rate-limiter.ts`** - Core rate limiting logic with sliding window algorithm
2. **`component-rate-limiter-manager.ts`** - Singleton manager for rate limiter instances
3. **`component-rate-limiter.test.ts`** - Comprehensive test suite
4. **Modified `agent-components.ts`** - Integration of rate limiting into component handlers
5. **Modified `provider.ts`** - Initialization and cleanup of rate limiter
6. **Updated configuration schemas** - Added rate limiting configuration options

### Configuration Schema Updates

- **`src/config/types.discord.ts`** - Added `DiscordAgentComponentsRateLimitConfig` type
- **`src/config/zod-schema.providers-core.ts`** - Added Zod schema validation

## Configuration

### Default Settings

```typescript
{
enabled: true, // Enable rate limiting
maxInteractions: 5, // Max interactions per window
windowMs: 10000, // 10-second window
cleanupIntervalMs: 60000, // 1-minute cleanup interval
perComponentId: false, // Track by component type, not individual components
rateLimitMessage: "You're interacting too quickly. Please wait a moment.",
logViolations: true // Log rate limit violations
}
```

### Custom Configuration Example

```yaml
channels:
discord:
agentComponents:
enabled: true
rateLimit:
enabled: true
maxInteractions: 3
windowMs: 15000 # 15 seconds
componentTypeLimits:
button:
maxInteractions: 5
windowMs: 10000 # 10 seconds
selectMenu:
maxInteractions: 2
windowMs: 20000 # 20 seconds
rateLimitMessage: "Please slow down your interactions."
logViolations: true
```

## How It Works

### Sliding Window Algorithm

The implementation uses a sliding window log algorithm:

1. **Track timestamps**: Each interaction's timestamp is stored
2. **Window filtering**: Only timestamps within the current window are considered
3. **Count validation**: If interaction count < limit, allow and record
4. **Denial handling**: If at limit, deny and send user-friendly message

### Rate Limit Key Structure

Rate limits are tracked using composite keys:
```
{userId}:{channelId}:{componentType}[:{componentId}]
```

- `userId`: Discord user ID
- `channelId`: Discord channel ID
- `componentType`: 'button' or 'selectMenu'
- `componentId`: Optional individual component ID (if `perComponentId: true`)

### Security Flow

```
Interaction Received
Parse Component Data
Rate Limit Check ← SECURITY CHECKPOINT
Allowed?
↙ ↘
No Yes
↓ ↓
Send Rate Authorization
Limit Msg Checks
↓ ↓
Return Process
Interaction
```

## Testing

The implementation includes comprehensive tests covering:

- Basic rate limiting functionality
- Sliding window behavior
- Separate tracking per user/channel/component type
- Component-specific limits
- Configuration handling
- Statistics and monitoring
- Reset functionality

Run tests with:
```bash
npm run test src/discord/monitor/component-rate-limiter.test.ts
```

## Monitoring and Observability

### Statistics

The rate limiter provides statistics:
```typescript
const stats = rateLimiter.getStats();
// Returns: { trackedKeys, totalRecordedInteractions, oldestRecord }
```

### Logging

Rate limit violations are logged with contextual information:
```typescript
{
userId: "123456789",
channelId: "987654321",
guildId: "555666777",
componentType: "button",
componentId: "agent_action_start",
limit: 5,
resetAfterMs: 7500
}
```

## Performance Considerations

### Memory Management

- **Automatic cleanup**: Stale records are cleaned up periodically
- **Bounded memory**: Memory usage is bounded by active users × time windows
- **Efficient lookups**: O(1) key lookups with Map-based storage

### Computational Complexity

- **Check operation**: O(n) where n = interactions in window (typically small)
- **Cleanup operation**: O(m) where m = total tracked keys (bounded)
- **Record operation**: O(1) for adding new timestamps

## Security Analysis

### Threat Mitigation

| Threat | Mitigation | Status |
|--------|------------|--------|
| Button spam DoS | Per-user sliding window limits | ✅ |
| Resource exhaustion | Pre-auth rate limiting | ✅ |
| Memory exhaustion | Automatic cleanup + bounded tracking | ✅ |
| Cross-channel abuse | Per-channel separation | ✅ |
| Information leakage | Generic error messages | ✅ |
| Timing attacks | Constant-time operations where possible | ✅ |

### Defense in Depth

```
Layer 1: Discord API Rate Limits (Platform)
Layer 2: Application Rate Limiting (This Implementation) ← NEW
Layer 3: Authorization Checks (Existing)
Layer 4: Business Logic Processing (Existing)
```

## Migration and Deployment

### Backward Compatibility

- Rate limiting is **enabled by default** but with reasonable limits
- Existing configurations without rate limiting settings will use defaults
- No breaking changes to existing component interaction handling

### Rollout Strategy

1. **Deploy with defaults**: System will automatically apply sensible rate limits
2. **Monitor violations**: Check logs for rate limiting patterns
3. **Adjust configuration**: Tune limits based on usage patterns
4. **Alert on abuse**: Set up monitoring for repeated violations

## Administrative Tools

### Reset Rate Limits

```typescript
// Reset specific user
ComponentRateLimiterManager.getInstance().resetUser(userId, channelId);

// Reset all limits (emergency)
const limiter = ComponentRateLimiterManager.getInstance();
const stats = limiter.getStats();
// Review stats before global reset
```

### Configuration Updates

Rate limiting configuration can be updated via:
1. Configuration file changes (requires restart)
2. Runtime configuration updates (if supported)

## Compliance

This implementation addresses:

- **CWE-770**: Allocation of Resources Without Limits or Throttling
- **OWASP**: Application Level DoS Prevention
- **Security Best Practices**: Fail-safe defaults, principle of least privilege

## Future Enhancements

Potential improvements:
1. **Persistent storage**: Store rate limits in Redis for multi-instance deployments
2. **Advanced algorithms**: Token bucket or leaky bucket algorithms
3. **User privilege levels**: Different limits based on user roles
4. **Geographic tracking**: Per-region rate limiting
5. **Adaptive limits**: Dynamic limits based on system load

## Troubleshooting

### Common Issues

1. **Rate limits too strict**: Users frequently hit limits
- **Solution**: Increase `maxInteractions` or `windowMs`

2. **Memory usage high**: Many tracked keys
- **Solution**: Decrease `cleanupIntervalMs` or check for user behavior patterns

3. **Legitimate users blocked**: Rate limits affecting normal usage
- **Solution**: Set different limits per component type or increase defaults

### Debug Information

Enable debug logging to see rate limit decisions:
```yaml
logging:
level: debug
```

This will show rate limit checks and violations in the logs.
Loading