Skip to content

feat: Resource Limits and Rate Limiting #68

Description

@amos-arc

Resource Limits and Rate Limiting

Overview

Implement resource consumption limits to prevent abuse and ensure fair resource allocation across users.

Problem Statement

Current limitations:

  • Only timeout limit exists (60 seconds for commands)
  • No output size limits
  • No rate limiting per user
  • No concurrent operation limits
  • Memory and CPU are unrestricted

Abuse Scenarios:

  1. Infinite loop commands consuming CPU
  2. Commands generating massive output
  3. Rapid-fire requests overwhelming system
  4. Memory exhaustion via large file operations
  5. Fork bombs via spawn tool

Proposed Solution

Resource Limiter

// core/src/sandbox/resource.rs
pub struct ResourceLimiter {
    limits: ResourceLimits,
    usage: ResourceUsageTracker,
}

pub struct ResourceLimits {
    // Time limits
    pub command_timeout_seconds: u64,
    pub file_operation_timeout_seconds: u64,
    pub web_request_timeout_seconds: u64,
    
    // Size limits
    pub max_command_output_bytes: usize,
    pub max_file_read_bytes: usize,
    pub max_web_response_bytes: usize,
    
    // Rate limits
    pub max_commands_per_minute: u32,
    pub max_file_ops_per_minute: u32,
    pub max_web_requests_per_minute: u32,
    
    // Concurrency limits
    pub max_concurrent_commands: u32,
    pub max_concurrent_subagents: u32,
}

pub struct ResourceUsageTracker {
    // Per-user tracking
    user_usage: HashMap<UserId, UserUsage>,
    
    // Global tracking
    global_usage: GlobalUsage,
}

pub struct UserUsage {
    commands_this_minute: u32,
    file_ops_this_minute: u32,
    web_requests_this_minute: u32,
    active_commands: u32,
    active_subagents: u32,
    last_reset: Instant,
}

Rate Limiting Implementation

impl ResourceLimiter {
    pub fn check_rate_limit(&self, user: &UserId, operation: Operation) -> Result<(), RateLimitError> {
        let usage = self.usage.get_or_create_user(user);
        usage.reset_if_minute_elapsed();
        
        match operation {
            Operation::Command => {
                if usage.commands_this_minute >= self.limits.max_commands_per_minute {
                    return Err(RateLimitError::CommandLimitExceeded {
                        limit: self.limits.max_commands_per_minute,
                        reset_in: usage.seconds_until_reset(),
                    });
                }
            }
            Operation::FileOp => {
                if usage.file_ops_this_minute >= self.limits.max_file_ops_per_minute {
                    return Err(RateLimitError::FileOpLimitExceeded);
                }
            }
            // ... other operations
        }
        
        Ok(())
    }
    
    pub fn increment_usage(&self, user: &UserId, operation: Operation) {
        let usage = self.usage.get_or_create_user(user);
        match operation {
            Operation::Command => usage.commands_this_minute += 1,
            Operation::FileOp => usage.file_ops_this_minute += 1,
            // ... other operations
        }
    }
}

Output Truncation

impl ResourceLimiter {
    pub fn truncate_output(&self, output: &[u8], operation: Operation) -> Cow<[u8]> {
        let max_size = match operation {
            Operation::Command => self.limits.max_command_output_bytes,
            Operation::FileRead => self.limits.max_file_read_bytes,
            Operation::WebRequest => self.limits.max_web_response_bytes,
        };
        
        if output.len() <= max_size {
            Cow::Borrowed(output)
        } else {
            let truncated = &output[..max_size];
            let message = format!("\n\n[TRUNCATED: Output exceeded {} bytes]", max_size);
            Cow::Owned([truncated, message.as_bytes()].concat())
        }
    }
}

Concurrency Control

impl ResourceLimiter {
    pub async fn acquire_slot(&self, user: &UserId, operation: Operation) -> Result<ResourceSlot, ResourceError> {
        let usage = self.usage.get_or_create_user(user);
        
        match operation {
            Operation::Command => {
                if usage.active_commands >= self.limits.max_concurrent_commands {
                    return Err(ResourceError::ConcurrencyLimitExceeded);
                }
                usage.active_commands += 1;
            }
            Operation::SpawnSubagent => {
                if usage.active_subagents >= self.limits.max_concurrent_subagents {
                    return Err(ResourceError::SubagentLimitExceeded);
                }
                usage.active_subagents += 1;
            }
        }
        
        Ok(ResourceSlot { user: user.clone(), operation })
    }
}

impl Drop for ResourceSlot {
    fn drop(&mut self) {
        // Automatically release slot when dropped
    }
}

Default Limits

{
  "resource_limits": {
    "timeouts": {
      "command_seconds": 60,
      "file_operation_seconds": 30,
      "web_request_seconds": 30
    },
    "sizes": {
      "max_command_output_mb": 1,
      "max_file_read_mb": 10,
      "max_web_response_mb": 1
    },
    "rates": {
      "commands_per_minute": 30,
      "file_ops_per_minute": 60,
      "web_requests_per_minute": 30
    },
    "concurrency": {
      "max_concurrent_commands": 3,
      "max_concurrent_subagents": 5
    }
  }
}

Per-Role Limits

{
  "role_limits": {
    "guest": {
      "commands_per_minute": 10,
      "max_concurrent_commands": 1
    },
    "member": {
      "commands_per_minute": 30,
      "max_concurrent_commands": 3
    },
    "admin": {
      "commands_per_minute": 100,
      "max_concurrent_commands": 10
    }
  }
}

Implementation Tasks

  • Implement ResourceLimiter struct
  • Implement per-user usage tracking
  • Implement rate limiting logic
  • Implement output truncation
  • Implement concurrency control
  • Add per-role limit configuration
  • Integrate with all tools
  • Add metrics/monitoring hooks

Acceptance Criteria

  • Rate limits are enforced per user
  • Output is truncated at configured limits
  • Concurrent operations are limited
  • Limits vary by user role
  • Clear error messages when limits exceeded
  • Automatic cleanup of tracked usage

Related

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions