1use std::collections::HashSet;
4use std::path::{Path, PathBuf};
5use std::net::{IpAddr, SocketAddr};
6
7use crate::error::{Error, Result, SecurityContext};
8use crate::security::{
9 NetworkCapability, FilesystemCapability,
10 EnvironmentCapability, ProcessCapability, TimeCapability, RandomCapability
11};
12
13pub trait CapabilityVerifier {
15 fn verify(&self, operation: &str, params: &[&str]) -> Result<()>;
17}
18
19pub struct NetworkVerifier {
21 capability: NetworkCapability,
22}
23
24impl NetworkVerifier {
25 pub fn new(capability: NetworkCapability) -> Self {
27 Self { capability }
28 }
29
30 pub fn is_host_allowed(&self, host: &str, port: u16, secure: bool) -> bool {
32 match &self.capability {
33 NetworkCapability::None => false,
34 NetworkCapability::Loopback => {
35 host == "localhost" || host == "127.0.0.1" || host == "::1"
37 },
38 NetworkCapability::AllowedHosts(hosts) => {
39 hosts.iter().any(|h| {
41 if h.host != host {
43 return false;
44 }
45
46 if let Some(port_range) = &h.ports {
48 if !port_range.contains(port) {
49 return false;
50 }
51 }
52
53 if !h.secure && secure {
55 return false;
56 }
57
58 true
59 })
60 },
61 NetworkCapability::AllowedPorts(ports) => {
62 ports.iter().any(|r| r.contains(port))
64 },
65 NetworkCapability::Full => true,
66 }
67 }
68
69 pub fn is_ip_allowed(&self, ip: IpAddr, port: u16) -> bool {
71 match &self.capability {
72 NetworkCapability::None => false,
73 NetworkCapability::Loopback => {
74 match ip {
76 IpAddr::V4(addr) => addr.is_loopback(),
77 IpAddr::V6(addr) => addr.is_loopback(),
78 }
79 },
80 NetworkCapability::AllowedHosts(_hosts) => {
81 false
83 },
84 NetworkCapability::AllowedPorts(ports) => {
85 ports.iter().any(|r| r.contains(port))
87 },
88 NetworkCapability::Full => true,
89 }
90 }
91
92 pub fn is_socket_allowed(&self, socket: SocketAddr) -> bool {
94 self.is_ip_allowed(socket.ip(), socket.port())
95 }
96}
97
98impl CapabilityVerifier for NetworkVerifier {
99 fn verify(&self, operation: &str, params: &[&str]) -> Result<()> {
100 match operation {
101 "connect" => {
102 if params.len() < 2 {
103 return Err(Error::Capability { message: "Missing host and port for connect".to_string() });
104 }
105
106 let host = params[0];
107 let port = params[1].parse::<u16>().map_err(|_| {
108 Error::Capability { message: format!("Invalid port: {}", params[1]) }
109 })?;
110
111 let secure = params.get(2).map(|s| *s == "secure").unwrap_or(false);
112
113 if !self.is_host_allowed(host, port, secure) {
114 return Err(Error::SecurityViolation {
115 violation: format!("Network access denied to {}:{}", host, port),
116 instance_id: None,
117 context: create_security_context("connect", "network.connect", &[]),
118 });
119 }
120 }
121 "bind" => {
122 if params.len() < 2 {
123 return Err(Error::Capability { message: "Missing host and port for bind".to_string() });
124 }
125
126 let host = params[0];
127 let port = params[1].parse::<u16>().map_err(|_| {
128 Error::Capability { message: format!("Invalid port: {}", params[1]) }
129 })?;
130
131 if !self.is_host_allowed(host, port, false) {
132 return Err(Error::SecurityViolation {
133 violation: format!("Network binding denied to {}:{}", host, port),
134 instance_id: None,
135 context: create_security_context("bind", "network.bind", &[]),
136 });
137 }
138 }
139 "listen" => {
140 if params.len() < 1 {
141 return Err(Error::Capability { message: "Missing port for listen".to_string() });
142 }
143
144 let port = params[0].parse::<u16>().map_err(|_| {
145 Error::Capability { message: format!("Invalid port: {}", params[0]) }
146 })?;
147
148 if !self.is_host_allowed("127.0.0.1", port, false) {
150 return Err(Error::SecurityViolation {
151 violation: format!("Network listening denied on port {}", port),
152 instance_id: None,
153 context: create_security_context("listen", "network.listen", &[]),
154 });
155 }
156 }
157 _ => {
158 return Err(Error::Capability { message: format!("Unknown network operation: {}", operation) });
159 }
160 }
161
162 Ok(())
163 }
164}
165
166pub struct FilesystemVerifier {
168 capability: FilesystemCapability,
169 normalized_readable: HashSet<PathBuf>,
170 normalized_writable: HashSet<PathBuf>,
171}
172
173impl FilesystemVerifier {
174 pub fn new(capability: FilesystemCapability) -> Self {
176 let normalized_readable = capability.readable_dirs
178 .iter()
179 .filter_map(|p| std::fs::canonicalize(p).ok())
180 .collect();
181
182 let normalized_writable = capability.writable_dirs
183 .iter()
184 .filter_map(|p| std::fs::canonicalize(p).ok())
185 .collect();
186
187 Self {
188 capability,
189 normalized_readable,
190 normalized_writable,
191 }
192 }
193
194 pub fn is_readable(&self, path: &Path) -> bool {
196 let canon_path = match std::fs::canonicalize(path) {
198 Ok(p) => p,
199 Err(_) => return false, };
201
202 for dir in &self.normalized_readable {
204 if is_path_within(dir, &canon_path) {
205 return true;
206 }
207 }
208
209 self.is_writable(path)
211 }
212
213 pub fn is_writable(&self, path: &Path) -> bool {
215 let canon_path = match std::fs::canonicalize(path) {
217 Ok(p) => p,
218 Err(_) => {
219 if let Some(parent) = path.parent() {
221 match std::fs::canonicalize(parent) {
222 Ok(p) => p,
223 Err(_) => return false, }
225 } else {
226 return false; }
228 }
229 };
230
231 for dir in &self.normalized_writable {
233 if is_path_within(dir, &canon_path) {
234 return true;
235 }
236 }
237
238 false
239 }
240
241 pub fn can_create(&self) -> bool {
243 self.capability.allow_create
244 }
245
246 pub fn can_delete(&self) -> bool {
248 self.capability.allow_delete
249 }
250
251 pub fn is_size_allowed(&self, size: u64) -> bool {
253 match self.capability.max_file_size {
254 Some(limit) => size <= limit,
255 None => true,
256 }
257 }
258}
259
260impl CapabilityVerifier for FilesystemVerifier {
261 fn verify(&self, operation: &str, params: &[&str]) -> Result<()> {
262 match operation {
263 "open" | "read" => {
264 if params.is_empty() {
265 return Err(Error::Capability { message: "Missing path for open/read".to_string() });
266 }
267
268 let path = Path::new(params[0]);
269 if !self.is_readable(path) {
270 return Err(Error::SecurityViolation {
271 violation: format!("File read access denied: {}", path.display()),
272 instance_id: None,
273 context: create_security_context("read", "filesystem.read", &[]),
274 });
275 }
276 }
277 "write" | "append" => {
278 if params.is_empty() {
279 return Err(Error::Capability { message: "Missing path for write/append".to_string() });
280 }
281
282 let path = Path::new(params[0]);
283 if !self.is_writable(path) {
284 return Err(Error::SecurityViolation {
285 violation: format!("File write access denied: {}", path.display()),
286 instance_id: None,
287 context: create_security_context("write", "filesystem.write", &[]),
288 });
289 }
290
291 if params.len() > 1 {
293 let size = params[1].parse::<u64>().map_err(|_| {
294 Error::Capability { message: format!("Invalid size: {}", params[1]) }
295 })?;
296
297 if !self.is_size_allowed(size) {
298 return Err(Error::ResourceLimit {
299 message: format!("File size limit exceeded: {}", size)
300 });
301 }
302 }
303 }
304 "create" => {
305 if params.is_empty() {
306 return Err(Error::Capability { message: "Missing path for create".to_string() });
307 }
308
309 if !self.can_create() {
310 return Err(Error::SecurityViolation {
311 violation: "File creation is not allowed".to_string(),
312 instance_id: None,
313 context: create_security_context("create", "filesystem.create", &[]),
314 });
315 }
316
317 let path = Path::new(params[0]);
318 if !self.is_writable(path) {
319 return Err(Error::SecurityViolation {
320 violation: format!("File creation access denied: {}", path.display()),
321 instance_id: None,
322 context: create_security_context("create", "filesystem.create", &[]),
323 });
324 }
325 }
326 "delete" | "remove" => {
327 if params.is_empty() {
328 return Err(Error::Capability { message: "Missing path for delete/remove".to_string() });
329 }
330
331 if !self.can_delete() {
332 return Err(Error::SecurityViolation {
333 violation: "File deletion is not allowed".to_string(),
334 instance_id: None,
335 context: create_security_context("delete", "filesystem.delete", &[]),
336 });
337 }
338
339 let path = Path::new(params[0]);
340 if !self.is_writable(path) {
341 return Err(Error::SecurityViolation {
342 violation: format!("File deletion access denied: {}", path.display()),
343 instance_id: None,
344 context: create_security_context("delete", "filesystem.delete", &[]),
345 });
346 }
347 }
348 _ => {
349 return Err(Error::Capability { message: format!("Unknown filesystem operation: {}", operation) });
350 }
351 }
352
353 Ok(())
354 }
355}
356
357fn is_path_within(dir: &Path, path: &Path) -> bool {
359 let dir_str = dir.to_string_lossy();
360 let path_str = path.to_string_lossy();
361
362 if path_str == dir_str {
364 return true;
365 }
366
367 path_str.starts_with(&format!("{}{}", dir_str, std::path::MAIN_SEPARATOR))
368}
369
370pub struct EnvironmentVerifier {
372 capability: EnvironmentCapability,
373}
374
375impl EnvironmentVerifier {
376 pub fn new(capability: EnvironmentCapability) -> Self {
378 Self { capability }
379 }
380
381 pub fn is_var_allowed(&self, var: &str) -> bool {
383 match &self.capability {
384 EnvironmentCapability::None => false,
385 EnvironmentCapability::Allowlist(allowed) => allowed.iter().any(|v| v == var),
386 EnvironmentCapability::Denylist(denied) => !denied.iter().any(|v| v == var),
387 EnvironmentCapability::Full => true,
388 }
389 }
390}
391
392impl CapabilityVerifier for EnvironmentVerifier {
393 fn verify(&self, operation: &str, params: &[&str]) -> Result<()> {
394 match operation {
395 "get" | "set" => {
396 if params.is_empty() {
397 return Err(Error::Capability { message: "Missing variable name".to_string() });
398 }
399
400 let var = params[0];
401 if !self.is_var_allowed(var) {
402 return Err(Error::SecurityViolation {
403 violation: format!("Environment variable access denied: {}", var),
404 instance_id: None,
405 context: create_security_context("get", "environment.get", &[]),
406 });
407 }
408
409 if operation == "set" && !matches!(self.capability, EnvironmentCapability::Full) {
411 return Err(Error::SecurityViolation {
412 violation: format!("Setting environment variables is not allowed: {}", var),
413 instance_id: None,
414 context: create_security_context("set", "environment.set", &[]),
415 });
416 }
417 }
418 _ => {
419 return Err(Error::Capability { message: format!("Unknown environment operation: {}", operation) });
420 }
421 }
422
423 Ok(())
424 }
425}
426
427pub struct ProcessVerifier {
429 capability: ProcessCapability,
430}
431
432impl ProcessVerifier {
433 pub fn new(capability: ProcessCapability) -> Self {
435 Self { capability }
436 }
437
438 pub fn is_command_allowed(&self, command: &str) -> bool {
440 match &self.capability {
441 ProcessCapability::None => false,
442 ProcessCapability::AllowedCommands(allowed) => {
443 allowed.iter().any(|cmd| {
445 if cmd == command {
447 return true;
448 }
449
450 if cmd.ends_with("*") {
452 let prefix = &cmd[..cmd.len() - 1];
453 return command.starts_with(prefix);
454 }
455
456 false
457 })
458 }
459 ProcessCapability::Full => true,
460 }
461 }
462}
463
464impl CapabilityVerifier for ProcessVerifier {
465 fn verify(&self, operation: &str, params: &[&str]) -> Result<()> {
466 match operation {
467 "exec" | "spawn" => {
468 if params.is_empty() {
469 return Err(Error::Capability { message: "Missing command".to_string() });
470 }
471
472 let command = params[0];
473 if !self.is_command_allowed(command) {
474 return Err(Error::SecurityViolation {
475 violation: format!("Process execution denied: {}", command),
476 instance_id: None,
477 context: create_security_context("exec", "process.exec", &[]),
478 });
479 }
480 }
481 _ => {
482 return Err(Error::Capability { message: format!("Unknown process operation: {}", operation) });
483 }
484 }
485
486 Ok(())
487 }
488}
489
490pub struct TimeVerifier {
492 capability: TimeCapability,
493}
494
495impl TimeVerifier {
496 pub fn new(capability: TimeCapability) -> Self {
498 Self { capability }
499 }
500
501 pub fn can_set_time(&self) -> bool {
503 matches!(self.capability, TimeCapability::Full)
504 }
505}
506
507impl CapabilityVerifier for TimeVerifier {
508 fn verify(&self, operation: &str, _params: &[&str]) -> Result<()> {
509 match operation {
510 "get" => {
511 Ok(())
513 }
514 "set" => {
515 if !self.can_set_time() {
516 return Err(Error::SecurityViolation {
517 violation: "Setting time is not allowed".to_string(),
518 instance_id: None,
519 context: create_security_context("set", "time.set", &[]),
520 });
521 }
522 Ok(())
523 }
524 _ => {
525 Err(Error::Capability { message: format!("Unknown time operation: {}", operation) })
526 }
527 }
528 }
529}
530
531pub struct RandomVerifier {
533 capability: RandomCapability,
534}
535
536impl RandomVerifier {
537 pub fn new(capability: RandomCapability) -> Self {
539 Self { capability }
540 }
541
542 pub fn can_secure_random(&self) -> bool {
544 matches!(self.capability, RandomCapability::Full)
545 }
546
547 pub fn can_pseudo_random(&self) -> bool {
549 !matches!(self.capability, RandomCapability::None)
550 }
551}
552
553impl CapabilityVerifier for RandomVerifier {
554 fn verify(&self, operation: &str, _params: &[&str]) -> Result<()> {
555 match operation {
556 "pseudo" => {
557 if !self.can_pseudo_random() {
558 return Err(Error::SecurityViolation {
559 violation: "Pseudo-random generation is not allowed".to_string(),
560 instance_id: None,
561 context: create_security_context("pseudo", "random.pseudo", &[]),
562 });
563 }
564 Ok(())
565 }
566 "secure" => {
567 if !self.can_secure_random() {
568 return Err(Error::SecurityViolation {
569 violation: "Secure random generation is not allowed".to_string(),
570 instance_id: None,
571 context: create_security_context("secure", "random.secure", &[]),
572 });
573 }
574 Ok(())
575 }
576 _ => {
577 Err(Error::Capability { message: format!("Unknown random operation: {}", operation) })
578 }
579 }
580 }
581}
582
583fn create_security_context(operation: &str, required_capability: &str, available_capabilities: &[&str]) -> SecurityContext {
585 SecurityContext {
586 attempted_operation: operation.to_string(),
587 required_capability: required_capability.to_string(),
588 available_capabilities: available_capabilities.iter().map(|s| s.to_string()).collect(),
589 }
590}
591
592pub struct CapabilityManager {
594 pub network: NetworkVerifier,
596
597 pub filesystem: FilesystemVerifier,
599
600 pub environment: EnvironmentVerifier,
602
603 pub process: ProcessVerifier,
605
606 pub time: TimeVerifier,
608
609 pub random: RandomVerifier,
611}
612
613impl CapabilityManager {
614 pub fn new(
616 network: NetworkCapability,
617 filesystem: FilesystemCapability,
618 environment: EnvironmentCapability,
619 process: ProcessCapability,
620 time: TimeCapability,
621 random: RandomCapability,
622 ) -> Self {
623 Self {
624 network: NetworkVerifier::new(network),
625 filesystem: FilesystemVerifier::new(filesystem),
626 environment: EnvironmentVerifier::new(environment),
627 process: ProcessVerifier::new(process),
628 time: TimeVerifier::new(time),
629 random: RandomVerifier::new(random),
630 }
631 }
632
633 pub fn verify(&self, domain: &str, operation: &str, params: &[&str]) -> Result<()> {
635 match domain {
636 "network" => self.network.verify(operation, params),
637 "filesystem" | "fs" => self.filesystem.verify(operation, params),
638 "environment" | "env" => self.environment.verify(operation, params),
639 "process" | "proc" => self.process.verify(operation, params),
640 "time" => self.time.verify(operation, params),
641 "random" | "rand" => self.random.verify(operation, params),
642 _ => Err(Error::Capability { message: format!("Unknown capability domain: {}", domain) }),
643 }
644 }
645}