claude_agent/security/limits/
rlimit.rs1use crate::security::SecurityError;
4
5const KB: u64 = 1024;
6const MB: u64 = 1024 * KB;
7const GB: u64 = 1024 * MB;
8
9#[derive(Debug, Clone)]
10pub struct ResourceLimits {
11 pub cpu_time: Option<u64>,
12 pub file_size: Option<u64>,
13 pub open_files: Option<u64>,
14 pub processes: Option<u64>,
15 pub virtual_memory: Option<u64>,
16 pub data_size: Option<u64>,
17 pub stack_size: Option<u64>,
18}
19
20impl Default for ResourceLimits {
21 fn default() -> Self {
22 Self {
23 cpu_time: Some(300), file_size: Some(100 * MB), open_files: Some(256),
26 processes: Some(32),
27 virtual_memory: Some(2 * GB), data_size: Some(GB), stack_size: Some(8 * MB), }
31 }
32}
33
34impl ResourceLimits {
35 pub fn none() -> Self {
36 Self {
37 cpu_time: None,
38 file_size: None,
39 open_files: None,
40 processes: None,
41 virtual_memory: None,
42 data_size: None,
43 stack_size: None,
44 }
45 }
46
47 pub fn strict() -> Self {
48 Self {
49 cpu_time: Some(60), file_size: Some(10 * MB), open_files: Some(64),
52 processes: Some(10),
53 virtual_memory: Some(512 * MB), data_size: Some(256 * MB), stack_size: Some(MB), }
57 }
58
59 pub fn with_cpu_time(mut self, seconds: u64) -> Self {
60 self.cpu_time = Some(seconds);
61 self
62 }
63
64 pub fn with_file_size(mut self, bytes: u64) -> Self {
65 self.file_size = Some(bytes);
66 self
67 }
68
69 pub fn with_open_files(mut self, count: u64) -> Self {
70 self.open_files = Some(count);
71 self
72 }
73
74 pub fn with_processes(mut self, count: u64) -> Self {
75 self.processes = Some(count);
76 self
77 }
78
79 pub fn with_virtual_memory(mut self, bytes: u64) -> Self {
80 self.virtual_memory = Some(bytes);
81 self
82 }
83
84 #[cfg(unix)]
85 pub fn apply(&self) -> Result<(), SecurityError> {
86 use rustix::process::{Resource, Rlimit, setrlimit};
87
88 if let Some(cpu) = self.cpu_time {
89 let rlim = Rlimit {
90 current: Some(cpu),
91 maximum: Some(cpu),
92 };
93 setrlimit(Resource::Cpu, rlim)
94 .map_err(|e| SecurityError::ResourceLimit(format!("CPU: {}", e)))?;
95 }
96
97 if let Some(fsize) = self.file_size {
98 let rlim = Rlimit {
99 current: Some(fsize),
100 maximum: Some(fsize),
101 };
102 setrlimit(Resource::Fsize, rlim)
103 .map_err(|e| SecurityError::ResourceLimit(format!("FSIZE: {}", e)))?;
104 }
105
106 if let Some(nofile) = self.open_files {
107 let rlim = Rlimit {
108 current: Some(nofile),
109 maximum: Some(nofile),
110 };
111 setrlimit(Resource::Nofile, rlim)
112 .map_err(|e| SecurityError::ResourceLimit(format!("NOFILE: {}", e)))?;
113 }
114
115 #[cfg(target_os = "linux")]
116 if let Some(nproc) = self.processes {
117 let rlim = Rlimit {
118 current: Some(nproc),
119 maximum: Some(nproc),
120 };
121 setrlimit(Resource::Nproc, rlim)
122 .map_err(|e| SecurityError::ResourceLimit(format!("NPROC: {}", e)))?;
123 }
124
125 #[cfg(target_os = "linux")]
126 if let Some(vmem) = self.virtual_memory {
127 let rlim = Rlimit {
128 current: Some(vmem),
129 maximum: Some(vmem),
130 };
131 setrlimit(Resource::As, rlim)
132 .map_err(|e| SecurityError::ResourceLimit(format!("AS: {}", e)))?;
133 }
134
135 if let Some(data) = self.data_size {
136 let rlim = Rlimit {
137 current: Some(data),
138 maximum: Some(data),
139 };
140 setrlimit(Resource::Data, rlim)
141 .map_err(|e| SecurityError::ResourceLimit(format!("DATA: {}", e)))?;
142 }
143
144 if let Some(stack) = self.stack_size {
145 let rlim = Rlimit {
146 current: Some(stack),
147 maximum: Some(stack),
148 };
149 setrlimit(Resource::Stack, rlim)
150 .map_err(|e| SecurityError::ResourceLimit(format!("STACK: {}", e)))?;
151 }
152
153 Ok(())
154 }
155
156 #[cfg(not(unix))]
157 pub fn apply(&self) -> Result<(), SecurityError> {
158 Ok(())
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn test_default_limits() {
168 let limits = ResourceLimits::default();
169 assert_eq!(limits.cpu_time, Some(300));
170 assert_eq!(limits.open_files, Some(256));
171 }
172
173 #[test]
174 fn test_strict_limits() {
175 let limits = ResourceLimits::strict();
176 assert_eq!(limits.cpu_time, Some(60));
177 assert_eq!(limits.processes, Some(10));
178 }
179
180 #[test]
181 fn test_none_limits() {
182 let limits = ResourceLimits::none();
183 assert!(limits.cpu_time.is_none());
184 assert!(limits.file_size.is_none());
185 }
186
187 #[test]
188 fn test_builder() {
189 let limits = ResourceLimits::none()
190 .with_cpu_time(120)
191 .with_file_size(1024 * 1024)
192 .with_open_files(128);
193
194 assert_eq!(limits.cpu_time, Some(120));
195 assert_eq!(limits.file_size, Some(1024 * 1024));
196 assert_eq!(limits.open_files, Some(128));
197 }
198}