amberjs 1.16.1

JavaScript/TypeScript runtime built with Rust and V8
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
// Container security scanner
// Scans container images for vulnerabilities and compliance issues

use std::collections::{BTreeMap, HashMap};
use std::fs::File;
use std::hash::Hash;
use std::io::Read;
use std::path::Path;
use tracing::info;
/// Security scanner for container images
pub struct SecurityScanner {
    /// Vulnerability database
    vulnerability_db: VulnerabilityDatabase,
    /// Scan configuration
    config: ScanConfig,
}
impl SecurityScanner {
    /// Create a new security scanner
    pub fn new(config: ScanConfig) -> Self {
        Self {
            vulnerability_db: VulnerabilityDatabase::new(),
            config,
        }
    }
    /// Scan a container image
    pub async fn scan_image(&self, image: &ContainerImage) -> Result<ScanReport, Error> {
        info!("Scanning image: {}", image.name);
        // Scan for vulnerabilities
        let vulnerabilities: _ = self.scan_vulnerabilities(image).await?;
        // Scan for compliance issues
        let compliance_issues: _ = self.scan_compliance(image).await?;
        // Scan for secrets
        let secrets: _ = self.scan_secrets(image).await?;
        // Calculate overall risk score
        let risk_score: _ =
            self.calculate_risk_score(&vulnerabilities, &compliance_issues, &secrets);
        let report: _ = ScanReport {
            image_name: image.name.clone(),
            image_digest: image.digest.clone(),
            scan_timestamp: std::time::SystemTime::now(),
            vulnerabilities: vulnerabilities.clone(),
            compliance_issues: compliance_issues.clone(),
            secrets: secrets.clone(),
            risk_score,
            recommendations: self.generate_recommendations(&vulnerabilities, &compliance_issues),
        };
        info!(
            "Completed scan for image: {}. Risk score: {}",
            image.name, risk_score
        );
        Ok(report)
    }
    /// Scan for vulnerabilities
    async fn scan_vulnerabilities(
        &self,
        image: &ContainerImage,
    ) -> Result<Vec<Vulnerability>, Error> {
        let mut vulnerabilities = Vec::new();
        // Scan each layer for vulnerabilities
        for layer in &image.layers {
            let layer_vulns: _ = self.scan_layer_vulnerabilities(layer).await?;
            vulnerabilities.extend(layer_vulns);
        }
        // Remove duplicates
        vulnerabilities.sort_by(|a, b| a.severity.cmp(&b.severity));
        vulnerabilities.dedup_by(|a, b| a.id == b.id);
        Ok(vulnerabilities)
    }
    /// Scan a single layer for vulnerabilities
    async fn scan_layer_vulnerabilities(
        &self,
        layer: &ImageLayer,
    ) -> Result<Vec<Vulnerability>, Error> {
        let mut vulnerabilities = Vec::new();
        // Check against vulnerability database
        for vuln in self.vulnerability_db.get_vulnerabilities() {
            // Check if vulnerability affects this layer
            if self.is_vulnerability_relevant(vuln, layer) {
                vulnerabilities.push(vuln.clone());
            }
        }
        Ok(vulnerabilities)
    }
    /// Check if vulnerability is relevant to layer
    fn is_vulnerability_relevant(&self, vuln: &Vulnerability, layer: &ImageLayer) -> bool {
        // Check if vulnerability affects the base image
        if layer.is_base_layer() {
            return vuln
                .affected_packages
                .iter()
                .any(|pkg| layer.packages.contains(pkg));
        }
        // Check if vulnerability affects installed packages
        vuln.affected_packages
            .iter()
            .any(|pkg| layer.packages.contains(pkg))
    }
    /// Scan for compliance issues
    async fn scan_compliance(&self, image: &ContainerImage) -> Result<Vec<ComplianceIssue>, Error> {
        let mut issues = Vec::new();
        // Check for non-root user
        if !self.has_non_root_user(image) {
            issues.push(ComplianceIssue {
                rule_id: "CIS-4.1".to_string(),
                severity: ComplianceSeverity::High,
                description: "Container is running as root user".to_string(),
                remediation: "Create a non-root user and switch to it".to_string(),
            });
        }
        // Check for read-only root filesystem
        if !self.has_read_only_root(image) {
            issues.push(ComplianceIssue {
                rule_id: "CIS-4.6".to_string(),
                severity: ComplianceSeverity::Medium,
                description: "Root filesystem is not read-only".to_string(),
                remediation: "Mount root filesystem as read-only".to_string(),
            });
        }
        // Check for capability dropping
        if !self.drops_capabilities(image) {
            issues.push(ComplianceIssue {
                rule_id: "CIS-4.4".to_string(),
                severity: ComplianceSeverity::Medium,
                description: "Linux capabilities are not dropped".to_string(),
                remediation: "Drop unnecessary Linux capabilities".to_string(),
            });
        }
        // Check for health check
        if !self.has_health_check(image) {
            issues.push(ComplianceIssue {
                rule_id: "CIS-4.8".to_string(),
                severity: ComplianceSeverity::Low,
                description: "No health check defined".to_string(),
                remediation: "Add a HEALTHCHECK instruction".to_string(),
            });
        }
        Ok(issues)
    }
    /// Scan for secrets in the image
    async fn scan_secrets(&self, image: &ContainerImage) -> Result<Vec<Secret>, Error> {
        let mut secrets = Vec::new();
        // Scan each layer for secrets
        for layer in &image.layers {
            let layer_secrets: _ = self.scan_layer_secrets(layer).await?;
            secrets.extend(layer_secrets);
        }
        Ok(secrets)
    }
    /// Scan a layer for secrets
    async fn scan_layer_secrets(&self, layer: &ImageLayer) -> Result<Vec<Secret>, Error> {
        let mut secrets = Vec::new();
        // Define secret patterns
        let secret_patterns: _ = vec![
            (
                "AWS_ACCESS_KEY_ID",
                r#"(?i)aws_access_key_id\s*[=:]\s*['\"]?[A-Z0-9]{16,}['\"]?"#,
            ),
            (
                "AWS_SECRET_ACCESS_KEY",
                r#"(?i)aws_secret_access_key\s*[=:]\s*['\"]?[A-Z0-9/+=]{40,}['\"]?"#,
            ),
            ("PRIVATE_KEY", r"-----BEGIN [A-Z ]*PRIVATE KEY-----"),
            (
                "API_KEY",
                r#"(?i)(api[_-]?key|apikey)\s*[=:]\s*['\"]?[a-zA-Z0-9]{32,}['\"]?"#,
            ),
            (
                "PASSWORD",
                r#"(?i)password\s*[=:]\s*['\"]?[a-zA-Z0-9]{8,}['\"]?"#,
            ),
        ];
        // Scan files for secrets
        for file_path in &layer.files {
            if let Ok(content) = self.read_file(file_path) {
                for (secret_type, pattern) in &secret_patterns {
                    if let Ok(regex) = regex::Regex::new(pattern) {
                        if regex.is_match(&content) {
                            secrets.push(Secret {
                                secret_type: secret_type.to_string(),
                                file_path: file_path.clone(),
                                line_number: self.find_line_number(&content, &regex),
                                remediation: format!("Remove or encrypt {}", secret_type),
                            });
                        }
                    }
                }
            }
        }
        Ok(secrets)
    }
    /// Read file content
    fn read_file(&self, path: &str) -> Result<String, std::io::Error> {
        // In a real implementation, this would read from the layer filesystem
        // For now, return an empty string
        Ok(String::new())
    }
    /// Find line number where pattern matches
    fn find_line_number(&self, content: &str, regex: &regex::Regex) -> Option<u32> {
        for (line_num, line) in content.lines().enumerate() {
            if regex.is_match(line) {
                return Some(line_num as u32 + 1);
            }
        }
        None
    }
    /// Calculate overall risk score
    fn calculate_risk_score(
        &self,
        vulnerabilities: &[Vulnerability],
        compliance_issues: &[ComplianceIssue],
        secrets: &[Secret],
    ) -> f64 {
        let mut score = 0.0;
        // Weight vulnerabilities
        for vuln in vulnerabilities {
            score += match vuln.severity {
                VulnerabilitySeverity::Critical => 10.0,
                VulnerabilitySeverity::High => 7.5,
                VulnerabilitySeverity::Medium => 5.0,
                VulnerabilitySeverity::Low => 2.5,
                VulnerabilitySeverity::Info => 1.0,
            };
        }
        // Weight compliance issues
        for issue in compliance_issues {
            score += match issue.severity {
                ComplianceSeverity::High => 5.0,
                ComplianceSeverity::Medium => 3.0,
                ComplianceSeverity::Low => 1.0,
            };
        }
        // Weight secrets
        for secret in secrets {
            score += 8.0; // Secrets are always high risk
        }
        // Normalize to 0-100 scale
        (score / 100.0_f64).min(100.0_f64)
    }
    /// Generate recommendations
    fn generate_recommendations(
        &self,
        vulnerabilities: &[Vulnerability],
        compliance_issues: &[ComplianceIssue],
    ) -> Vec<String> {
        let mut recommendations = Vec::new();
        // Critical vulnerability recommendations
        if vulnerabilities
            .iter()
            .any(|v| v.severity == VulnerabilitySeverity::Critical)
        {
            recommendations.push("Address critical vulnerabilities immediately".to_string());
        }
        // Compliance recommendations
        if compliance_issues
            .iter()
            .any(|i| i.severity == ComplianceSeverity::High)
        {
            recommendations.push("Implement high-priority compliance recommendations".to_string());
        }
        // General recommendations
        recommendations.extend(vec![
            "Use multi-stage builds to minimize attack surface".to_string(),
            "Scan images before deployment".to_string(),
            "Keep base images up to date".to_string(),
            "Use minimal base images (alpine, distroless)".to_string(),
            "Implement least privilege principle".to_string(),
        ]);
        recommendations
    }
    /// Check if image has non-root user
    fn has_non_root_user(&self, image: &ContainerImage) -> bool {
        image.layers.iter().any(|layer| {
            layer.files.contains(&"/etc/passwd".to_string())
                && layer.files.contains(&"/etc/shadow".to_string())
        })
    }
    /// Check if image has read-only root filesystem
    fn has_read_only_root(&self, image: &ContainerImage) -> bool {
        // In a real implementation, check Dockerfile for READONLY mount
        false
    }
    /// Check if image drops capabilities
    fn drops_capabilities(&self, image: &ContainerImage) -> bool {
        // In a real implementation, check Dockerfile for --cap-drop
        false
    }
    /// Check if image has health check
    fn has_health_check(&self, image: &ContainerImage) -> bool {
        image.healthcheck.is_some()
    }
}
/// Container image structure
#[derive(Debug, Clone)]
pub struct ContainerImage {
    /// Image name
    pub name: String,
    /// Image digest
    pub digest: String,
    /// Image layers
    pub layers: Vec<ImageLayer>,
    /// Healthcheck configuration
    pub healthcheck: Option<HealthCheckConfig>,
}
/// Image layer structure
#[derive(Debug, Clone)]
pub struct ImageLayer {
    /// Layer ID
    pub id: String,
    /// Layer size
    pub size: u64,
    /// Installed packages
    pub packages: Vec<String>,
    /// Files in layer
    pub files: Vec<String>,
    /// Is base layer
    pub is_base_layer: bool,
}
impl ImageLayer {
    /// Check if this is the base layer
    pub fn is_base_layer(&self) -> bool {
        self.is_base_layer
    }
}
/// Vulnerability structure
#[derive(Debug, Clone, PartialEq)]
pub struct Vulnerability {
    /// Vulnerability ID (e.g., CVE-2023-1234)
    pub id: String,
    /// Severity level
    pub severity: VulnerabilitySeverity,
    /// Description
    pub description: String,
    /// Affected packages
    pub affected_packages: Vec<String>,
    /// Fixed version
    pub fixed_version: Option<String>,
    /// CVSS score
    pub cvss_score: Option<f64>,
}
/// Vulnerability severity
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum VulnerabilitySeverity {
    Critical,
    High,
    Medium,
    Low,
    Info,
}
/// Compliance issue structure
#[derive(Debug, Clone)]
pub struct ComplianceIssue {
    /// Rule ID (e.g., CIS-4.1)
    pub rule_id: String,
    /// Severity level
    pub severity: ComplianceSeverity,
    /// Description
    pub description: String,
    /// Remediation steps
    pub remediation: String,
}
/// Compliance severity
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum ComplianceSeverity {
    High,
    Medium,
    Low,
}
/// Secret structure
#[derive(Debug, Clone)]
pub struct Secret {
    /// Secret type
    pub secret_type: String,
    /// File path where secret was found
    pub file_path: String,
    /// Line number
    pub line_number: Option<u32>,
    /// Remediation
    pub remediation: String,
}
/// Scan report structure
#[derive(Debug, Clone)]
pub struct ScanReport {
    /// Image name
    pub image_name: String,
    /// Image digest
    pub image_digest: String,
    /// Scan timestamp
    pub scan_timestamp: std::time::SystemTime,
    /// Vulnerabilities found
    pub vulnerabilities: Vec<Vulnerability>,
    /// Compliance issues
    pub compliance_issues: Vec<ComplianceIssue>,
    /// Secrets found
    pub secrets: Vec<Secret>,
    /// Risk score (0-100)
    pub risk_score: f64,
    /// Recommendations
    pub recommendations: Vec<String>,
}
impl ScanReport {
    /// Get total vulnerability count
    pub fn vulnerability_count(&self) -> usize {
        self.vulnerabilities.len()
    }
    /// Get critical vulnerability count
    pub fn critical_vulnerability_count(&self) -> usize {
        self.vulnerabilities
            .iter()
            .filter(|v| v.severity == VulnerabilitySeverity::Critical)
            .count()
    }
    /// Get high severity issue count
    pub fn high_severity_issue_count(&self) -> usize {
        let vuln_count: _ = self
            .vulnerabilities
            .iter()
            .filter(|v| v.severity == VulnerabilitySeverity::High)
            .count();
        let compliance_count: _ = self
            .compliance_issues
            .iter()
            .filter(|i| i.severity == ComplianceSeverity::High)
            .count();
        vuln_count + compliance_count
    }
    /// Check if scan passed
    pub fn passed(&self) -> bool {
        self.risk_score < 70.0
            && self.critical_vulnerability_count() == 0
            && self.secrets.is_empty()
    }
}
/// Vulnerability database
struct VulnerabilityDatabase {
    /// Known vulnerabilities
    vulnerabilities: Vec<Vulnerability>,
}
impl VulnerabilityDatabase {
    /// Create a new vulnerability database
    fn new() -> Self {
        let vulnerabilities: _ = Self::load_vulnerabilities();
        Self { vulnerabilities }
    }
    /// Load known vulnerabilities
    fn load_vulnerabilities() -> Vec<Vulnerability> {
        vec![
            Vulnerability {
                id: "CVE-2023-1234".to_string(),
                severity: VulnerabilitySeverity::High,
                description: "Buffer overflow in package X".to_string(),
                affected_packages: vec!["package-x".to_string()],
                fixed_version: Some("1.2.3".to_string()),
                cvss_score: Some(7.5),
            },
            Vulnerability {
                id: "CVE-2023-5678".to_string(),
                severity: VulnerabilitySeverity::Critical,
                description: "Remote code execution in package Y".to_string(),
                affected_packages: vec!["package-y".to_string()],
                fixed_version: Some("2.0.0".to_string()),
                cvss_score: Some(9.8),
            },
        ]
    }
    /// Get all vulnerabilities
    fn get_vulnerabilities(&self) -> &[Vulnerability] {
        &self.vulnerabilities
    }
}
/// Scan configuration
#[derive(Debug, Clone)]
pub struct ScanConfig {
    /// Enable vulnerability scanning
    pub scan_vulnerabilities: bool,
    /// Enable compliance scanning
    pub scan_compliance: bool,
    /// Enable secret scanning
    pub scan_secrets: bool,
    /// Minimum severity to report
    pub min_severity: VulnerabilitySeverity,
}
/// Health check configuration
#[derive(Debug, Clone)]
pub struct HealthCheckConfig {
    /// Test command
    pub test: Vec<String>,
    /// Interval
    pub interval: u32,
    /// Timeout
    pub timeout: u32,
    /// Retries
    pub retries: u32,
}
/// Error type
#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("Scan failed: {0}")]
    ScanFailed(String),
    #[error("Database error: {0}")]
    DatabaseError(String),
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
    #[error("Regex error: {0}")]
    Regex(#[from] regex::Error),
    #[error("Other error: {0}")]
    Other(String),
}
#[cfg(test)]
mod tests {
    use super::*;
    use std::time::SystemTime;
    #[test]
    fn test_vulnerability_severity_ordering() {
        assert!(VulnerabilitySeverity::Critical > VulnerabilitySeverity::High);
        assert!(VulnerabilitySeverity::High > VulnerabilitySeverity::Medium);
        assert!(VulnerabilitySeverity::Medium > VulnerabilitySeverity::Low);
        assert!(VulnerabilitySeverity::Low > VulnerabilitySeverity::Info);
    }
    #[test]
    fn test_compliance_severity_ordering() {
        assert!(ComplianceSeverity::High > ComplianceSeverity::Medium);
        assert!(ComplianceSeverity::Medium > ComplianceSeverity::Low);
    }
    #[test]
    fn test_scan_report_passing() {
        let report: _ = ScanReport {
            image_name: "test-image".to_string(),
            image_digest: "sha256:123".to_string(),
            scan_timestamp: std::time::SystemTime::now(),
            vulnerabilities: vec![Vulnerability {
                id: "CVE-2023-1234".to_string(),
                severity: VulnerabilitySeverity::Medium,
                description: "Test".to_string(),
                affected_packages: vec![],
                fixed_version: None,
                cvss_score: None,
            }],
            compliance_issues: vec![],
            secrets: vec![],
            risk_score: 50.0,
            recommendations: vec![],
        };
        assert!(report.passed());
    }
    #[test]
    fn test_scan_report_failing() {
        let report: _ = ScanReport {
            image_name: "test-image".to_string(),
            image_digest: "sha256:123".to_string(),
            scan_timestamp: std::time::SystemTime::now(),
            vulnerabilities: vec![Vulnerability {
                id: "CVE-2023-1234".to_string(),
                severity: VulnerabilitySeverity::Critical,
                description: "Test".to_string(),
                affected_packages: vec![],
                fixed_version: None,
                cvss_score: None,
            }],
            compliance_issues: vec![],
            secrets: vec![Secret {
                secret_type: "AWS_ACCESS_KEY_ID".to_string(),
                file_path: "/etc/secret".to_string(),
                line_number: Some(1),
                remediation: "Remove secret".to_string(),
            }],
            risk_score: 80.0,
            recommendations: vec![],
        };
        assert!(!report.passed());
    }
}