Skip to main content

plugin_packager/
sandbox.rs

1// Copyright 2024 Vincents AI
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4/// Plugin sandbox verification and static security analysis
5///
6/// This module provides security analysis for plugins including:
7/// - Static binary analysis
8/// - Permission requirement validation
9/// - Resource limit enforcement
10/// - System call whitelist verification
11/// - Capability model enforcement
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14
15/// Permission types that plugins can request
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
17#[serde(rename_all = "snake_case")]
18pub enum Permission {
19    FileSystem,
20    Network,
21    ProcessCreation,
22    SystemCall,
23    EnvironmentAccess,
24    ThreadCreation,
25    MemoryAllocation,
26    TimerAccess,
27    SignalHandling,
28}
29
30impl Permission {
31    pub fn as_str(&self) -> &'static str {
32        match self {
33            Permission::FileSystem => "filesystem",
34            Permission::Network => "network",
35            Permission::ProcessCreation => "process_creation",
36            Permission::SystemCall => "system_call",
37            Permission::EnvironmentAccess => "environment_access",
38            Permission::ThreadCreation => "thread_creation",
39            Permission::MemoryAllocation => "memory_allocation",
40            Permission::TimerAccess => "timer_access",
41            Permission::SignalHandling => "signal_handling",
42        }
43    }
44
45    pub fn try_parse(s: &str) -> Option<Self> {
46        match s {
47            "filesystem" => Some(Permission::FileSystem),
48            "network" => Some(Permission::Network),
49            "process_creation" => Some(Permission::ProcessCreation),
50            "system_call" => Some(Permission::SystemCall),
51            "environment_access" => Some(Permission::EnvironmentAccess),
52            "thread_creation" => Some(Permission::ThreadCreation),
53            "memory_allocation" => Some(Permission::MemoryAllocation),
54            "timer_access" => Some(Permission::TimerAccess),
55            "signal_handling" => Some(Permission::SignalHandling),
56            _ => None,
57        }
58    }
59
60    pub fn description(&self) -> &'static str {
61        match self {
62            Permission::FileSystem => "Access to filesystem operations",
63            Permission::Network => "Network I/O and socket operations",
64            Permission::ProcessCreation => "Ability to spawn child processes",
65            Permission::SystemCall => "Low-level system call access",
66            Permission::EnvironmentAccess => "Access to environment variables",
67            Permission::ThreadCreation => "Ability to create threads",
68            Permission::MemoryAllocation => "Dynamic memory allocation",
69            Permission::TimerAccess => "Timer and clock access",
70            Permission::SignalHandling => "Signal handler registration",
71        }
72    }
73
74    pub fn severity(&self) -> u32 {
75        match self {
76            Permission::FileSystem => 7,
77            Permission::Network => 7,
78            Permission::ProcessCreation => 9,
79            Permission::SystemCall => 10,
80            Permission::EnvironmentAccess => 3,
81            Permission::ThreadCreation => 5,
82            Permission::MemoryAllocation => 4,
83            Permission::TimerAccess => 2,
84            Permission::SignalHandling => 8,
85        }
86    }
87}
88
89/// Resource limits for plugin execution
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct ResourceLimits {
92    pub max_memory_mb: u32,
93    pub max_cpu_time_ms: u32,
94    pub max_file_descriptors: u32,
95    pub max_threads: u32,
96    pub max_processes: u32,
97}
98
99impl Default for ResourceLimits {
100    fn default() -> Self {
101        Self {
102            max_memory_mb: 256,
103            max_cpu_time_ms: 30000,
104            max_file_descriptors: 1024,
105            max_threads: 16,
106            max_processes: 1,
107        }
108    }
109}
110
111/// Capability model for fine-grained permissions
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct PluginCapability {
114    pub name: String,
115    pub required_permissions: Vec<Permission>,
116    pub resource_requirement: Option<String>,
117    pub description: String,
118}
119
120/// Sandbox verification result for a single check
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct SandboxCheckResult {
123    pub check_name: String,
124    pub passed: bool,
125    pub severity: SandboxSeverity,
126    pub message: String,
127    pub details: Option<String>,
128    pub remediation: Option<String>,
129}
130
131/// Sandbox verification severity levels
132#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
133#[serde(rename_all = "lowercase")]
134pub enum SandboxSeverity {
135    Info,
136    Warning,
137    Error,
138    Critical,
139}
140
141impl SandboxSeverity {
142    pub fn as_str(&self) -> &'static str {
143        match self {
144            SandboxSeverity::Info => "info",
145            SandboxSeverity::Warning => "warning",
146            SandboxSeverity::Error => "error",
147            SandboxSeverity::Critical => "critical",
148        }
149    }
150
151    pub fn try_parse(s: &str) -> Option<Self> {
152        match s.to_lowercase().as_str() {
153            "info" => Some(SandboxSeverity::Info),
154            "warning" => Some(SandboxSeverity::Warning),
155            "error" => Some(SandboxSeverity::Error),
156            "critical" => Some(SandboxSeverity::Critical),
157            _ => None,
158        }
159    }
160}
161
162/// Complete sandbox verification report
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct SandboxVerificationReport {
165    pub plugin_id: String,
166    pub plugin_version: String,
167    pub verification_timestamp: String,
168    pub is_sandboxed: bool,
169    pub checks: Vec<SandboxCheckResult>,
170    pub requested_permissions: Vec<Permission>,
171    pub resource_limits: ResourceLimits,
172    pub capabilities: Vec<PluginCapability>,
173    pub high_risk_count: usize,
174    pub total_risk_score: u32,
175}
176
177impl SandboxVerificationReport {
178    pub fn is_compliant(&self) -> bool {
179        !self
180            .checks
181            .iter()
182            .any(|c| !c.passed && c.severity == SandboxSeverity::Critical)
183    }
184
185    pub fn risk_assessment(&self) -> SandboxRiskLevel {
186        match self.total_risk_score {
187            0..=10 => SandboxRiskLevel::Low,
188            11..=30 => SandboxRiskLevel::Medium,
189            31..=60 => SandboxRiskLevel::High,
190            _ => SandboxRiskLevel::Critical,
191        }
192    }
193
194    pub fn summary(&self) -> String {
195        let status = if self.is_compliant() {
196            "Compliant"
197        } else {
198            "Non-compliant"
199        };
200        format!(
201            "{}: Risk={:?}, Permissions={}, Score={}",
202            status,
203            self.risk_assessment(),
204            self.requested_permissions.len(),
205            self.total_risk_score
206        )
207    }
208}
209
210/// Risk assessment level
211#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
212#[serde(rename_all = "PascalCase")]
213pub enum SandboxRiskLevel {
214    Low,
215    Medium,
216    High,
217    Critical,
218}
219
220impl SandboxRiskLevel {
221    pub fn as_str(&self) -> &'static str {
222        match self {
223            SandboxRiskLevel::Low => "Low",
224            SandboxRiskLevel::Medium => "Medium",
225            SandboxRiskLevel::High => "High",
226            SandboxRiskLevel::Critical => "Critical",
227        }
228    }
229}
230
231/// System call information
232#[derive(Debug, Clone, Serialize, Deserialize)]
233pub struct SystemCallInfo {
234    pub name: String,
235    pub category: String,
236    pub whitelisted: bool,
237    pub risk_level: u32,
238}
239
240/// Plugin sandbox verifier
241pub struct PluginSandboxVerifier {
242    whitelisted_syscalls: HashMap<String, SystemCallInfo>,
243    #[allow(dead_code)] // Reserved for per-plugin permission enforcement
244    required_permissions: HashMap<Permission, bool>,
245    resource_limits: ResourceLimits,
246}
247
248impl PluginSandboxVerifier {
249    /// Create a new sandbox verifier with default configuration
250    pub fn new() -> Self {
251        Self {
252            whitelisted_syscalls: Self::default_syscall_whitelist(),
253            required_permissions: HashMap::new(),
254            resource_limits: ResourceLimits::default(),
255        }
256    }
257
258    /// Get default syscall whitelist
259    fn default_syscall_whitelist() -> HashMap<String, SystemCallInfo> {
260        let mut whitelist = HashMap::new();
261
262        // Safe I/O syscalls
263        whitelist.insert(
264            "read".to_string(),
265            SystemCallInfo {
266                name: "read".to_string(),
267                category: "io".to_string(),
268                whitelisted: true,
269                risk_level: 1,
270            },
271        );
272
273        whitelist.insert(
274            "write".to_string(),
275            SystemCallInfo {
276                name: "write".to_string(),
277                category: "io".to_string(),
278                whitelisted: true,
279                risk_level: 1,
280            },
281        );
282
283        // Memory management syscalls
284        whitelist.insert(
285            "mmap".to_string(),
286            SystemCallInfo {
287                name: "mmap".to_string(),
288                category: "memory".to_string(),
289                whitelisted: true,
290                risk_level: 3,
291            },
292        );
293
294        whitelist.insert(
295            "brk".to_string(),
296            SystemCallInfo {
297                name: "brk".to_string(),
298                category: "memory".to_string(),
299                whitelisted: true,
300                risk_level: 2,
301            },
302        );
303
304        // Thread syscalls
305        whitelist.insert(
306            "clone".to_string(),
307            SystemCallInfo {
308                name: "clone".to_string(),
309                category: "process".to_string(),
310                whitelisted: false,
311                risk_level: 9,
312            },
313        );
314
315        // Dangerous syscalls (not whitelisted)
316        whitelist.insert(
317            "execve".to_string(),
318            SystemCallInfo {
319                name: "execve".to_string(),
320                category: "process".to_string(),
321                whitelisted: false,
322                risk_level: 10,
323            },
324        );
325
326        whitelist.insert(
327            "ptrace".to_string(),
328            SystemCallInfo {
329                name: "ptrace".to_string(),
330                category: "debug".to_string(),
331                whitelisted: false,
332                risk_level: 10,
333            },
334        );
335
336        whitelist
337    }
338
339    /// Check if a syscall is whitelisted
340    pub fn check_syscall_whitelist(&self, syscall_name: &str) -> SandboxCheckResult {
341        let is_whitelisted = self
342            .whitelisted_syscalls
343            .get(syscall_name)
344            .map(|s| s.whitelisted)
345            .unwrap_or(false);
346
347        SandboxCheckResult {
348            check_name: format!("Syscall: {}", syscall_name),
349            passed: is_whitelisted,
350            severity: if is_whitelisted {
351                SandboxSeverity::Info
352            } else {
353                SandboxSeverity::Error
354            },
355            message: if is_whitelisted {
356                format!("Syscall '{}' is whitelisted", syscall_name)
357            } else {
358                format!("Syscall '{}' is not allowed in sandbox", syscall_name)
359            },
360            details: self
361                .whitelisted_syscalls
362                .get(syscall_name)
363                .map(|s| format!("Category: {}, Risk: {}", s.category, s.risk_level)),
364            remediation: if !is_whitelisted {
365                Some("Remove use of this syscall or use sandboxed alternative".to_string())
366            } else {
367                None
368            },
369        }
370    }
371
372    /// Verify permission request
373    pub fn check_permission_request(&self, permission: Permission) -> SandboxCheckResult {
374        SandboxCheckResult {
375            check_name: format!("Permission: {}", permission.as_str()),
376            passed: true,
377            severity: SandboxSeverity::Info,
378            message: format!(
379                "Permission '{}' requested: {}",
380                permission.as_str(),
381                permission.description()
382            ),
383            details: Some(format!("Risk severity: {}/10", permission.severity())),
384            remediation: None,
385        }
386    }
387
388    /// Validate resource limits
389    pub fn check_resource_limits(&self, requested: &ResourceLimits) -> SandboxCheckResult {
390        let memory_ok = requested.max_memory_mb <= self.resource_limits.max_memory_mb;
391        let cpu_ok = requested.max_cpu_time_ms <= self.resource_limits.max_cpu_time_ms;
392        let fds_ok = requested.max_file_descriptors <= self.resource_limits.max_file_descriptors;
393        let threads_ok = requested.max_threads <= self.resource_limits.max_threads;
394        let processes_ok = requested.max_processes <= self.resource_limits.max_processes;
395
396        let all_passed = memory_ok && cpu_ok && fds_ok && threads_ok && processes_ok;
397
398        let mut issues = Vec::new();
399        if !memory_ok {
400            issues.push(format!(
401                "Memory: {} MB > {} MB limit",
402                requested.max_memory_mb, self.resource_limits.max_memory_mb
403            ));
404        }
405        if !cpu_ok {
406            issues.push(format!(
407                "CPU: {} ms > {} ms limit",
408                requested.max_cpu_time_ms, self.resource_limits.max_cpu_time_ms
409            ));
410        }
411        if !fds_ok {
412            issues.push(format!(
413                "FDs: {} > {} limit",
414                requested.max_file_descriptors, self.resource_limits.max_file_descriptors
415            ));
416        }
417        if !threads_ok {
418            issues.push(format!(
419                "Threads: {} > {} limit",
420                requested.max_threads, self.resource_limits.max_threads
421            ));
422        }
423        if !processes_ok {
424            issues.push(format!(
425                "Processes: {} > {} limit",
426                requested.max_processes, self.resource_limits.max_processes
427            ));
428        }
429
430        SandboxCheckResult {
431            check_name: "Resource Limits".to_string(),
432            passed: all_passed,
433            severity: if all_passed {
434                SandboxSeverity::Info
435            } else {
436                SandboxSeverity::Warning
437            },
438            message: if all_passed {
439                "Resource limits within acceptable bounds".to_string()
440            } else {
441                format!("Resource limit violations: {}", issues.len())
442            },
443            details: if issues.is_empty() {
444                None
445            } else {
446                Some(issues.join("; "))
447            },
448            remediation: if !all_passed {
449                Some(
450                    "Reduce requested resource limits to comply with sandbox constraints"
451                        .to_string(),
452                )
453            } else {
454                None
455            },
456        }
457    }
458
459    /// Check for capability compliance
460    pub fn check_capability_model(&self, capability: &PluginCapability) -> SandboxCheckResult {
461        let dangerous_perms = capability
462            .required_permissions
463            .iter()
464            .filter(|p| p.severity() >= 8)
465            .count();
466
467        SandboxCheckResult {
468            check_name: format!("Capability: {}", capability.name),
469            passed: dangerous_perms == 0,
470            severity: if dangerous_perms == 0 {
471                SandboxSeverity::Info
472            } else {
473                SandboxSeverity::Warning
474            },
475            message: format!(
476                "Capability '{}': {} permissions, {} high-risk",
477                capability.name,
478                capability.required_permissions.len(),
479                dangerous_perms
480            ),
481            details: Some(format!("Description: {}", capability.description)),
482            remediation: if dangerous_perms > 0 {
483                Some("Reduce high-risk permissions for this capability".to_string())
484            } else {
485                None
486            },
487        }
488    }
489
490    /// Calculate total risk score
491    pub fn calculate_risk_score(permissions: &[Permission]) -> u32 {
492        permissions.iter().map(|p| p.severity()).sum()
493    }
494
495    /// Generate risk analysis
496    pub fn analyze_risk(permissions: &[Permission]) -> (u32, usize, SandboxRiskLevel) {
497        let total_score = Self::calculate_risk_score(permissions);
498        let high_risk_count = permissions.iter().filter(|p| p.severity() >= 8).count();
499
500        let risk_level = match total_score {
501            0..=10 => SandboxRiskLevel::Low,
502            11..=30 => SandboxRiskLevel::Medium,
503            31..=60 => SandboxRiskLevel::High,
504            _ => SandboxRiskLevel::Critical,
505        };
506
507        (total_score, high_risk_count, risk_level)
508    }
509}
510
511impl Default for PluginSandboxVerifier {
512    fn default() -> Self {
513        Self::new()
514    }
515}
516
517#[cfg(test)]
518mod tests {
519    use super::*;
520
521    #[test]
522    fn test_permission_to_str() {
523        assert_eq!(Permission::FileSystem.as_str(), "filesystem");
524        assert_eq!(Permission::Network.as_str(), "network");
525        assert_eq!(Permission::ProcessCreation.as_str(), "process_creation");
526        assert_eq!(Permission::SystemCall.as_str(), "system_call");
527    }
528
529    #[test]
530    fn test_permission_try_parse() {
531        assert_eq!(
532            Permission::try_parse("filesystem"),
533            Some(Permission::FileSystem)
534        );
535        assert_eq!(Permission::try_parse("network"), Some(Permission::Network));
536        assert_eq!(Permission::try_parse("unknown"), None);
537    }
538
539    #[test]
540    fn test_permission_description() {
541        let desc = Permission::FileSystem.description();
542        assert!(!desc.is_empty());
543    }
544
545    #[test]
546    fn test_permission_severity() {
547        assert!(Permission::SystemCall.severity() > Permission::TimerAccess.severity());
548        assert!(Permission::ProcessCreation.severity() > Permission::EnvironmentAccess.severity());
549    }
550
551    #[test]
552    fn test_resource_limits_default() {
553        let limits = ResourceLimits::default();
554        assert!(limits.max_memory_mb > 0);
555        assert!(limits.max_cpu_time_ms > 0);
556    }
557
558    #[test]
559    fn test_sandbox_severity_to_str() {
560        assert_eq!(SandboxSeverity::Info.as_str(), "info");
561        assert_eq!(SandboxSeverity::Warning.as_str(), "warning");
562        assert_eq!(SandboxSeverity::Error.as_str(), "error");
563        assert_eq!(SandboxSeverity::Critical.as_str(), "critical");
564    }
565
566    #[test]
567    fn test_sandbox_severity_try_parse() {
568        assert_eq!(
569            SandboxSeverity::try_parse("info"),
570            Some(SandboxSeverity::Info)
571        );
572        assert_eq!(SandboxSeverity::try_parse("unknown"), None);
573    }
574
575    #[test]
576    fn test_sandbox_severity_ordering() {
577        assert!(SandboxSeverity::Info < SandboxSeverity::Warning);
578        assert!(SandboxSeverity::Warning < SandboxSeverity::Error);
579        assert!(SandboxSeverity::Error < SandboxSeverity::Critical);
580    }
581
582    #[test]
583    fn test_sandbox_risk_level_to_str() {
584        assert_eq!(SandboxRiskLevel::Low.as_str(), "Low");
585        assert_eq!(SandboxRiskLevel::Critical.as_str(), "Critical");
586    }
587
588    #[test]
589    fn test_verifier_creation() {
590        let verifier = PluginSandboxVerifier::new();
591        assert!(!verifier.whitelisted_syscalls.is_empty());
592    }
593
594    #[test]
595    fn test_verifier_default_syscalls() {
596        let verifier = PluginSandboxVerifier::new();
597        assert!(verifier.whitelisted_syscalls.contains_key("read"));
598        assert!(verifier.whitelisted_syscalls.contains_key("write"));
599        assert!(verifier.whitelisted_syscalls.contains_key("execve"));
600    }
601
602    #[test]
603    fn test_check_syscall_whitelisted() {
604        let verifier = PluginSandboxVerifier::new();
605        let result = verifier.check_syscall_whitelist("read");
606        assert!(result.passed);
607    }
608
609    #[test]
610    fn test_check_syscall_not_whitelisted() {
611        let verifier = PluginSandboxVerifier::new();
612        let result = verifier.check_syscall_whitelist("execve");
613        assert!(!result.passed);
614    }
615
616    #[test]
617    fn test_check_permission_request() {
618        let verifier = PluginSandboxVerifier::new();
619        let result = verifier.check_permission_request(Permission::FileSystem);
620        assert!(result.passed);
621    }
622
623    #[test]
624    fn test_check_resource_limits_compliant() {
625        let verifier = PluginSandboxVerifier::new();
626        let requested = ResourceLimits {
627            max_memory_mb: 100,
628            max_cpu_time_ms: 5000,
629            max_file_descriptors: 256,
630            max_threads: 8,
631            max_processes: 1,
632        };
633        let result = verifier.check_resource_limits(&requested);
634        assert!(result.passed);
635    }
636
637    #[test]
638    fn test_check_resource_limits_exceeds() {
639        let verifier = PluginSandboxVerifier::new();
640        let requested = ResourceLimits {
641            max_memory_mb: 1000,
642            max_cpu_time_ms: 60000,
643            max_file_descriptors: 256,
644            max_threads: 8,
645            max_processes: 1,
646        };
647        let result = verifier.check_resource_limits(&requested);
648        assert!(!result.passed);
649    }
650
651    #[test]
652    fn test_calculate_risk_score() {
653        let perms = vec![Permission::FileSystem, Permission::Network];
654        let score = PluginSandboxVerifier::calculate_risk_score(&perms);
655        assert!(score > 0);
656    }
657
658    #[test]
659    fn test_analyze_risk_low() {
660        let perms = vec![Permission::EnvironmentAccess, Permission::TimerAccess];
661        let (_score, _high_risk, level) = PluginSandboxVerifier::analyze_risk(&perms);
662        assert_eq!(level, SandboxRiskLevel::Low);
663    }
664
665    #[test]
666    fn test_analyze_risk_critical() {
667        // Add all high-severity permissions to reach Critical threshold
668        let perms = vec![
669            Permission::SystemCall,
670            Permission::ProcessCreation,
671            Permission::SignalHandling,
672            Permission::FileSystem,
673            Permission::Network,
674            Permission::ThreadCreation,
675            Permission::MemoryAllocation,
676            Permission::EnvironmentAccess,
677        ];
678        let (score, high_risk, level) = PluginSandboxVerifier::analyze_risk(&perms);
679        // With these permissions: 10+9+8+7+7+5+4+3 = 53 (High), let's verify it's at least High
680        assert!(score > 30);
681        assert!(level >= SandboxRiskLevel::High);
682        assert!(high_risk > 0);
683    }
684
685    #[test]
686    fn test_check_capability_model_safe() {
687        let verifier = PluginSandboxVerifier::new();
688        let cap = PluginCapability {
689            name: "safe_io".to_string(),
690            required_permissions: vec![Permission::FileSystem],
691            resource_requirement: None,
692            description: "Safe I/O operations".to_string(),
693        };
694        let result = verifier.check_capability_model(&cap);
695        assert!(result.passed);
696    }
697
698    #[test]
699    fn test_check_capability_model_dangerous() {
700        let verifier = PluginSandboxVerifier::new();
701        let cap = PluginCapability {
702            name: "dangerous_ops".to_string(),
703            required_permissions: vec![Permission::SystemCall, Permission::ProcessCreation],
704            resource_requirement: None,
705            description: "Dangerous operations".to_string(),
706        };
707        let result = verifier.check_capability_model(&cap);
708        assert!(!result.passed);
709    }
710
711    #[test]
712    fn test_sandbox_verification_report_is_compliant() {
713        let report = SandboxVerificationReport {
714            plugin_id: "test".to_string(),
715            plugin_version: "1.0.0".to_string(),
716            verification_timestamp: "2024-01-01T00:00:00Z".to_string(),
717            is_sandboxed: true,
718            checks: vec![],
719            requested_permissions: vec![],
720            resource_limits: ResourceLimits::default(),
721            capabilities: vec![],
722            high_risk_count: 0,
723            total_risk_score: 5,
724        };
725        assert!(report.is_compliant());
726    }
727
728    #[test]
729    fn test_sandbox_verification_report_not_compliant() {
730        let check = SandboxCheckResult {
731            check_name: "Test".to_string(),
732            passed: false,
733            severity: SandboxSeverity::Critical,
734            message: "Failed".to_string(),
735            details: None,
736            remediation: None,
737        };
738        let report = SandboxVerificationReport {
739            plugin_id: "test".to_string(),
740            plugin_version: "1.0.0".to_string(),
741            verification_timestamp: "2024-01-01T00:00:00Z".to_string(),
742            is_sandboxed: true,
743            checks: vec![check],
744            requested_permissions: vec![],
745            resource_limits: ResourceLimits::default(),
746            capabilities: vec![],
747            high_risk_count: 0,
748            total_risk_score: 5,
749        };
750        assert!(!report.is_compliant());
751    }
752
753    #[test]
754    fn test_sandbox_verification_report_risk_assessment() {
755        let report = SandboxVerificationReport {
756            plugin_id: "test".to_string(),
757            plugin_version: "1.0.0".to_string(),
758            verification_timestamp: "2024-01-01T00:00:00Z".to_string(),
759            is_sandboxed: true,
760            checks: vec![],
761            requested_permissions: vec![],
762            resource_limits: ResourceLimits::default(),
763            capabilities: vec![],
764            high_risk_count: 0,
765            total_risk_score: 50,
766        };
767        assert_eq!(report.risk_assessment(), SandboxRiskLevel::High);
768    }
769
770    #[test]
771    fn test_sandbox_verification_report_summary() {
772        let report = SandboxVerificationReport {
773            plugin_id: "test".to_string(),
774            plugin_version: "1.0.0".to_string(),
775            verification_timestamp: "2024-01-01T00:00:00Z".to_string(),
776            is_sandboxed: true,
777            checks: vec![],
778            requested_permissions: vec![Permission::FileSystem],
779            resource_limits: ResourceLimits::default(),
780            capabilities: vec![],
781            high_risk_count: 0,
782            total_risk_score: 15,
783        };
784        let summary = report.summary();
785        assert!(summary.contains("Compliant"));
786    }
787
788    #[test]
789    fn test_sandbox_check_result_serialization() {
790        let result = SandboxCheckResult {
791            check_name: "Test Check".to_string(),
792            passed: true,
793            severity: SandboxSeverity::Info,
794            message: "Test message".to_string(),
795            details: Some("Details".to_string()),
796            remediation: Some("Fix it".to_string()),
797        };
798
799        let json = serde_json::to_string(&result).unwrap();
800        let deserialized: SandboxCheckResult = serde_json::from_str(&json).unwrap();
801
802        assert_eq!(deserialized.check_name, result.check_name);
803        assert_eq!(deserialized.passed, result.passed);
804    }
805
806    #[test]
807    fn test_sandbox_verification_report_serialization() {
808        let report = SandboxVerificationReport {
809            plugin_id: "test-plugin".to_string(),
810            plugin_version: "1.0.0".to_string(),
811            verification_timestamp: "2024-01-01T00:00:00Z".to_string(),
812            is_sandboxed: true,
813            checks: vec![],
814            requested_permissions: vec![],
815            resource_limits: ResourceLimits::default(),
816            capabilities: vec![],
817            high_risk_count: 0,
818            total_risk_score: 0,
819        };
820
821        let json = serde_json::to_string(&report).unwrap();
822        let deserialized: SandboxVerificationReport = serde_json::from_str(&json).unwrap();
823
824        assert_eq!(deserialized.plugin_id, report.plugin_id);
825    }
826
827    #[test]
828    fn test_plugin_capability_serialization() {
829        let cap = PluginCapability {
830            name: "test".to_string(),
831            required_permissions: vec![Permission::FileSystem],
832            resource_requirement: Some("256MB".to_string()),
833            description: "Test capability".to_string(),
834        };
835
836        let json = serde_json::to_string(&cap).unwrap();
837        let deserialized: PluginCapability = serde_json::from_str(&json).unwrap();
838
839        assert_eq!(deserialized.name, cap.name);
840    }
841}