diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9cfd5de..6534861 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -27,14 +27,14 @@ jobs: uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 - name: Initialize CodeQL - uses: github/codeql-action/init@6624720a57d4c312633c7b953db2f2da5bcb4c3a # v3 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@6624720a57d4c312633c7b953db2f2da5bcb4c3a # v3 + uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@6624720a57d4c312633c7b953db2f2da5bcb4c3a # v3 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 502cfb0..8e26380 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -21,12 +21,10 @@ jobs: fetch-depth: 0 - name: TruffleHog OSS - uses: trufflesecurity/trufflehog@7ee2e0fdffec27d19ccbb8fb3dcf8a83b9d7f9e8 # main + uses: trufflesecurity/trufflehog@e9734c1ff25106f68d4266f0b09c1fcfc915dad1 # main with: path: ./ - base: ${{ github.event.repository.default_branch }} - head: HEAD - extra_args: --debug --only-verified + extra_args: --only-verified --max-depth=10 editorconfig: name: EditorConfig Check diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 692d992..6b8728f 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -53,16 +53,16 @@ jobs: echo "✅ Zero warnings" msrv: - name: Check MSRV (1.75.0) + name: Check MSRV (1.85.0) runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 - - name: Install Rust 1.75.0 + - name: Install Rust 1.85.0 uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable with: - toolchain: 1.75.0 + toolchain: 1.85.0 - name: Cache cargo registry uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 8a10401..78991f3 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -31,6 +31,6 @@ jobs: publish_results: true - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@6624720a57d4c312633c7b953db2f2da5bcb4c3a # v3 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: results.sarif diff --git a/CHANGELOG.md b/CHANGELOG.md index 07237ba..9816615 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,41 @@ # Changelog +## [1.0.1] - 2026-02-07 + +### Fixed +- **CI/CD workflows**: All GitHub Actions now passing + - Updated MSRV from 1.75.0 → 1.85.0 (required for Cargo.lock v4 format) + - Fixed invalid codeql-action SHA pins (using version tags for Scorecard compatibility) + - Fixed TruffleHog configuration (BASE/HEAD commit conflict) + - Fixed EditorConfig indentation violations +- **Code quality**: + - Resolved clippy warnings (manual_clamp, unwrap_or_default) + - Added `#[allow]` attributes for intentional vulnerabilities in examples + - Removed unused imports + - Applied rustfmt to all source files + +### Changed +- **MSRV**: Updated from 1.75.0 to 1.85.0 +- **Workflows**: codeql-action now uses version tags instead of SHA pins + +## [1.0.0] - 2026-02-07 + +### Added +- **Production-ready infrastructure**: + - Complete RSR compliance (AI.a2ml manifest, 3 SCM files) + - 11 GitHub Actions workflows (CI, security, coverage, quality) + - Comprehensive documentation (SECURITY.md, CONTRIBUTING.md, LICENSE) + - Stable JSON schema (v1.0, documented, versioned) +- **Testing**: + - 21 unit tests covering all analyzers + - 3 integration tests (X-Ray pipeline, vulnerable programs) + - 3 regression tests (echidna, eclexia, self-test baselines) + - Code coverage reporting with codecov +- **Configuration**: + - Config file support (panic-attacker.toml) + - EditorConfig for consistent formatting + - MSRV policy (1.75.0, later updated to 1.85.0) + ## [0.2.0] - 2026-02-07 ### Fixed diff --git a/Cargo.lock b/Cargo.lock index 21c6cd5..b59341a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -355,7 +355,7 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "panic-attacker" -version = "0.2.0" +version = "1.0.0" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index db14f47..f1f1b70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,9 @@ # SPDX-License-Identifier: PMPL-1.0-or-later [package] name = "panic-attacker" -version = "1.0.0" +version = "1.0.1" edition = "2021" +rust-version = "1.85.0" authors = ["Jonathan D.A. Jewell "] license = "PMPL-1.0-or-later" description = "Universal stress testing and logic-based bug signature detection" diff --git a/README.md b/README.md index e3407df..89e8582 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/hyperpolymath/panic-attacker/badge)](https://securityscorecards.dev/viewer/?uri=github.com/hyperpolymath/panic-attacker) [![codecov](https://codecov.io/gh/hyperpolymath/panic-attacker/branch/main/graph/badge.svg)](https://codecov.io/gh/hyperpolymath/panic-attacker) [![License: PMPL](https://img.shields.io/badge/License-PMPL--1.0--or--later-blue.svg)](LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.75.0-blue)](Cargo.toml) +[![MSRV](https://img.shields.io/badge/MSRV-1.85.0-blue)](Cargo.toml) Universal stress testing and logic-based bug signature detection tool. @@ -78,7 +78,7 @@ cargo install --path . ### Requirements -- Rust 1.75.0 or later +- Rust 1.85.0 or later - Cargo ## Quick Start @@ -338,4 +338,4 @@ If you use panic-attacker in your research, please cite: --- -**Status**: Active development | **Version**: 0.2.0 | **MSRV**: 1.75.0 +**Status**: Active development | **Version**: 0.2.0 | **MSRV**: 1.85.0 diff --git a/examples/vulnerable_program.rs b/examples/vulnerable_program.rs index 12f5716..1f6ab10 100644 --- a/examples/vulnerable_program.rs +++ b/examples/vulnerable_program.rs @@ -5,9 +5,12 @@ //! This program contains intentional bugs for demonstration purposes. //! DO NOT use this code in production! +#![allow(clippy::unnecessary_literal_unwrap)] +#![allow(static_mut_refs)] + use std::env; -use std::thread; use std::sync::{Arc, Mutex}; +use std::thread; use std::time::Duration; fn main() { diff --git a/src/attack/executor.rs b/src/attack/executor.rs index dc3f2db..afed7e5 100644 --- a/src/attack/executor.rs +++ b/src/attack/executor.rs @@ -23,7 +23,11 @@ impl AttackExecutor { } } - pub fn with_patterns(config: AttackConfig, language: Language, frameworks: &[Framework]) -> Self { + pub fn with_patterns( + config: AttackConfig, + language: Language, + frameworks: &[Framework], + ) -> Self { let patterns = PatternDetector::patterns_for(language, frameworks); Self { config, patterns } } diff --git a/src/attack/strategies.rs b/src/attack/strategies.rs index c86c96f..99a05db 100644 --- a/src/attack/strategies.rs +++ b/src/attack/strategies.rs @@ -15,24 +15,12 @@ pub enum AttackStrategy { impl AttackStrategy { pub fn description(&self) -> &str { match self { - AttackStrategy::CpuStress => { - "Stress test CPU with high computational load" - } - AttackStrategy::MemoryExhaustion => { - "Exhaust available memory with large allocations" - } - AttackStrategy::DiskThrashing => { - "Thrash disk I/O with many file operations" - } - AttackStrategy::NetworkFlood => { - "Flood network connections" - } - AttackStrategy::ConcurrencyStorm => { - "Create concurrency storm with many threads/tasks" - } - AttackStrategy::TimeBomb => { - "Run for extended duration to find time-dependent bugs" - } + AttackStrategy::CpuStress => "Stress test CPU with high computational load", + AttackStrategy::MemoryExhaustion => "Exhaust available memory with large allocations", + AttackStrategy::DiskThrashing => "Thrash disk I/O with many file operations", + AttackStrategy::NetworkFlood => "Flood network connections", + AttackStrategy::ConcurrencyStorm => "Create concurrency storm with many threads/tasks", + AttackStrategy::TimeBomb => "Run for extended duration to find time-dependent bugs", } } } diff --git a/src/main.rs b/src/main.rs index 98734b9..e619d05 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,7 @@ use types::*; #[derive(Parser)] #[command(name = "panic-attacker")] -#[command(version = "1.0.0")] +#[command(version = "1.0.1")] #[command(about = "Universal stress testing and logic-based bug signature detection")] #[command(long_about = None)] struct Cli { @@ -204,7 +204,10 @@ fn main() -> Result<()> { if !result.signatures_detected.is_empty() { println!("\n Bug Signatures:"); for sig in &result.signatures_detected { - println!(" - {:?} (confidence: {:.2})", sig.signature_type, sig.confidence); + println!( + " - {:?} (confidence: {:.2})", + sig.signature_type, sig.confidence + ); } } } @@ -259,7 +262,9 @@ fn main() -> Result<()> { } } - Commands::Analyze { report: report_path } => { + Commands::Analyze { + report: report_path, + } => { println!("Analyzing crash report: {}", report_path.display()); let content = std::fs::read_to_string(&report_path)?; @@ -269,7 +274,10 @@ fn main() -> Result<()> { println!("\nSignatures Detected: {}", signatures.len()); for sig in &signatures { - println!("\n {:?} (confidence: {:.2})", sig.signature_type, sig.confidence); + println!( + "\n {:?} (confidence: {:.2})", + sig.signature_type, sig.confidence + ); println!(" Evidence:"); for evidence in &sig.evidence { println!(" - {}", evidence); diff --git a/src/report/formatter.rs b/src/report/formatter.rs index aeb8430..c870e91 100644 --- a/src/report/formatter.rs +++ b/src/report/formatter.rs @@ -16,7 +16,10 @@ impl ReportFormatter { } pub fn print(&self, report: &AssaultReport) { - println!("\n{}", "=== PANIC-ATTACKER ASSAULT REPORT ===".bold().cyan()); + println!( + "\n{}", + "=== PANIC-ATTACKER ASSAULT REPORT ===".bold().cyan() + ); println!(); self.print_xray_summary(&report.xray_report); @@ -46,7 +49,10 @@ impl ReportFormatter { println!(" Unwrap calls: {}", xray.statistics.unwrap_calls); println!(" Allocation sites: {}", xray.statistics.allocation_sites); println!(" I/O operations: {}", xray.statistics.io_operations); - println!(" Threading constructs: {}", xray.statistics.threading_constructs); + println!( + " Threading constructs: {}", + xray.statistics.threading_constructs + ); println!(); if !xray.weak_points.is_empty() { @@ -124,7 +130,10 @@ impl ReportFormatter { ); if !result.crashes.is_empty() { - println!(" Crashes: {}", result.crashes.len().to_string().red().bold()); + println!( + " Crashes: {}", + result.crashes.len().to_string().red().bold() + ); for (i, crash) in result.crashes.iter().enumerate() { println!(" {}. Signal: {:?}", i + 1, crash.signal); if let Some(bt) = &crash.backtrace { @@ -134,10 +143,7 @@ impl ReportFormatter { } if result.peak_memory > 0 { - println!( - " Peak memory: {} MB", - result.peak_memory / (1024 * 1024) - ); + println!(" Peak memory: {} MB", result.peak_memory / (1024 * 1024)); } } } diff --git a/src/report/generator.rs b/src/report/generator.rs index 44998f7..47da9ad 100644 --- a/src/report/generator.rs +++ b/src/report/generator.rs @@ -17,10 +17,7 @@ impl ReportGenerator { xray_report: XRayReport, attack_results: Vec, ) -> Result { - let total_crashes = attack_results - .iter() - .map(|r| r.crashes.len()) - .sum(); + let total_crashes = attack_results.iter().map(|r| r.crashes.len()).sum(); let total_signatures = attack_results .iter() @@ -38,11 +35,7 @@ impl ReportGenerator { }) } - fn assess_results( - &self, - xray: &XRayReport, - results: &[AttackResult], - ) -> OverallAssessment { + fn assess_results(&self, xray: &XRayReport, results: &[AttackResult]) -> OverallAssessment { let mut critical_issues = Vec::new(); let mut recommendations = Vec::new(); @@ -65,7 +58,7 @@ impl ReportGenerator { * 20.0; score -= (xray.statistics.unsafe_blocks as f64) * 5.0; - score = score.max(0.0).min(100.0); + score = score.clamp(0.0, 100.0); // Identify critical issues for result in results { @@ -89,21 +82,15 @@ impl ReportGenerator { // Generate recommendations if crash_count > 0.0 { - recommendations.push( - "Add comprehensive error handling for edge cases".to_string(), - ); + recommendations.push("Add comprehensive error handling for edge cases".to_string()); } if xray.statistics.unwrap_calls > 10 { - recommendations.push( - "Replace unwrap() calls with proper error handling".to_string(), - ); + recommendations.push("Replace unwrap() calls with proper error handling".to_string()); } if xray.statistics.unsafe_blocks > 0 { - recommendations.push( - "Audit unsafe blocks for memory safety violations".to_string(), - ); + recommendations.push("Audit unsafe blocks for memory safety violations".to_string()); } if results.iter().any(|r| { @@ -111,9 +98,8 @@ impl ReportGenerator { .iter() .any(|s| matches!(s.signature_type, SignatureType::DataRace)) }) { - recommendations.push( - "Add synchronization primitives to prevent data races".to_string(), - ); + recommendations + .push("Add synchronization primitives to prevent data races".to_string()); } if results.iter().any(|r| { @@ -121,15 +107,11 @@ impl ReportGenerator { .iter() .any(|s| matches!(s.signature_type, SignatureType::Deadlock)) }) { - recommendations.push( - "Review lock ordering to prevent deadlocks".to_string(), - ); + recommendations.push("Review lock ordering to prevent deadlocks".to_string()); } if score < 50.0 { - recommendations.push( - "Consider comprehensive refactoring for robustness".to_string(), - ); + recommendations.push("Consider comprehensive refactoring for robustness".to_string()); } OverallAssessment { diff --git a/src/signatures/engine.rs b/src/signatures/engine.rs index 643c8e1..dda7663 100644 --- a/src/signatures/engine.rs +++ b/src/signatures/engine.rs @@ -121,9 +121,17 @@ impl SignatureEngine { // Find all free and use pairs for fact1 in facts { - if let Fact::Free { var: var1, location: free_loc } = fact1 { + if let Fact::Free { + var: var1, + location: free_loc, + } = fact1 + { for fact2 in facts { - if let Fact::Use { var: var2, location: use_loc } = fact2 { + if let Fact::Use { + var: var2, + location: use_loc, + } = fact2 + { if var1 == var2 && free_loc < use_loc { // Pattern matched! signatures.push(BugSignature { @@ -164,11 +172,7 @@ impl SignatureEngine { /// Free(var, loc1), /// Free(var, loc2), /// loc1 != loc2 - fn infer_double_free( - &self, - facts: &HashSet, - crash: &CrashReport, - ) -> Vec { + fn infer_double_free(&self, facts: &HashSet, crash: &CrashReport) -> Vec { let mut signatures = Vec::new(); let mut free_locations: HashMap> = HashMap::new(); @@ -177,7 +181,7 @@ impl SignatureEngine { if let Fact::Free { var, location } = fact { free_locations .entry(var.clone()) - .or_insert_with(Vec::new) + .or_default() .push(*location); } } diff --git a/src/types.rs b/src/types.rs index 45097e4..68cc2b6 100644 --- a/src/types.rs +++ b/src/types.rs @@ -262,18 +262,48 @@ pub struct AttackPattern { /// Datalog fact for signature detection #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Fact { - Alloc { var: String, location: usize }, - Free { var: String, location: usize }, - Use { var: String, location: usize }, - Lock { mutex: String, location: usize }, - Unlock { mutex: String, location: usize }, - ThreadSpawn { id: String, location: usize }, + Alloc { + var: String, + location: usize, + }, + Free { + var: String, + location: usize, + }, + Use { + var: String, + location: usize, + }, + Lock { + mutex: String, + location: usize, + }, + Unlock { + mutex: String, + location: usize, + }, + ThreadSpawn { + id: String, + location: usize, + }, #[allow(dead_code)] // Reserved for v0.5 Datalog engine - ThreadJoin { id: String, location: usize }, - Write { var: String, location: usize }, - Read { var: String, location: usize }, + ThreadJoin { + id: String, + location: usize, + }, + Write { + var: String, + location: usize, + }, + Read { + var: String, + location: usize, + }, #[allow(dead_code)] // Reserved for v0.5 Datalog engine - Ordering { before: usize, after: usize }, + Ordering { + before: usize, + after: usize, + }, } /// Datalog rule for pattern detection @@ -288,9 +318,24 @@ pub struct Rule { #[derive(Debug, Clone, PartialEq, Eq)] pub enum Predicate { - UseAfterFree { var: String, use_loc: usize, free_loc: usize }, - DoubleFree { var: String, loc1: usize, loc2: usize }, - Deadlock { m1: String, m2: String }, - DataRace { var: String, loc1: usize, loc2: usize }, + UseAfterFree { + var: String, + use_loc: usize, + free_loc: usize, + }, + DoubleFree { + var: String, + loc1: usize, + loc2: usize, + }, + Deadlock { + m1: String, + m2: String, + }, + DataRace { + var: String, + loc1: usize, + loc2: usize, + }, Fact(Fact), } diff --git a/src/xray/analyzer.rs b/src/xray/analyzer.rs index 04df53a..b1270c5 100644 --- a/src/xray/analyzer.rs +++ b/src/xray/analyzer.rs @@ -81,8 +81,7 @@ impl Analyzer { let content = match String::from_utf8(raw_bytes.clone()) { Ok(s) => s, Err(_) => { - let (cow, _, had_errors) = - encoding_rs::WINDOWS_1252.decode(&raw_bytes); + let (cow, _, had_errors) = encoding_rs::WINDOWS_1252.decode(&raw_bytes); if had_errors { if self.verbose { eprintln!( @@ -124,13 +123,23 @@ impl Analyzer { self.analyze_rust(&content, &mut file_stats, &mut file_weak_points, &rel_path)?; } Language::C | Language::Cpp => { - self.analyze_c_cpp(&content, &mut file_stats, &mut file_weak_points, &rel_path)?; + self.analyze_c_cpp( + &content, + &mut file_stats, + &mut file_weak_points, + &rel_path, + )?; } Language::Go => { self.analyze_go(&content, &mut file_stats, &mut file_weak_points, &rel_path)?; } Language::Python => { - self.analyze_python(&content, &mut file_stats, &mut file_weak_points, &rel_path)?; + self.analyze_python( + &content, + &mut file_stats, + &mut file_weak_points, + &rel_path, + )?; } _ => { self.analyze_generic(&content, &mut file_stats, &rel_path)?; @@ -307,10 +316,7 @@ impl Analyzer { category: WeakPointCategory::UnsafeCode, location: Some(file_path.to_string()), severity: Severity::High, - description: format!( - "{} unsafe blocks in {}", - stats.unsafe_blocks, file_path - ), + description: format!("{} unsafe blocks in {}", stats.unsafe_blocks, file_path), recommended_attack: vec![AttackAxis::Memory, AttackAxis::Concurrency], }); } @@ -461,9 +467,7 @@ impl Analyzer { } // Message queues - if content.contains("kafka") - || content.contains("rabbitmq") - || content.contains("nats") + if content.contains("kafka") || content.contains("rabbitmq") || content.contains("nats") { frameworks.insert(Framework::MessageQueue); } diff --git a/src/xray/mod.rs b/src/xray/mod.rs index 91fb80b..09d2847 100644 --- a/src/xray/mod.rs +++ b/src/xray/mod.rs @@ -49,7 +49,8 @@ pub fn analyze_verbose>(target: P) -> Result { for (rank, (risk, fs)) in scored.iter().take(10).enumerate() { println!( - " {}. {} (risk: {}, lines: {}, unsafe: {}, panics: {}, unwraps: {}, alloc: {}, io: {}, threads: {})", + " {}. {} (risk: {}, lines: {}, unsafe: {}, panics: {}, \ + unwraps: {}, alloc: {}, io: {}, threads: {})", rank + 1, fs.file_path, risk, diff --git a/src/xray/patterns.rs b/src/xray/patterns.rs index e68ecb7..1705fd0 100644 --- a/src/xray/patterns.rs +++ b/src/xray/patterns.rs @@ -8,10 +8,7 @@ pub struct PatternDetector; impl PatternDetector { /// Get attack patterns for a specific program type - pub fn patterns_for( - language: Language, - frameworks: &[Framework], - ) -> Vec { + pub fn patterns_for(language: Language, frameworks: &[Framework]) -> Vec { let mut patterns = Vec::new(); // Language-specific patterns @@ -94,29 +91,25 @@ impl PatternDetector { } fn go_patterns() -> Vec { - vec![ - AttackPattern { - name: "Goroutine Leak".to_string(), - description: "Spawn many concurrent operations".to_string(), - applicable_axes: vec![AttackAxis::Concurrency], - applicable_languages: vec![Language::Go], - applicable_frameworks: vec![], - command_template: "{program} --concurrent-requests 10000".to_string(), - }, - ] + vec![AttackPattern { + name: "Goroutine Leak".to_string(), + description: "Spawn many concurrent operations".to_string(), + applicable_axes: vec![AttackAxis::Concurrency], + applicable_languages: vec![Language::Go], + applicable_frameworks: vec![], + command_template: "{program} --concurrent-requests 10000".to_string(), + }] } fn python_patterns() -> Vec { - vec![ - AttackPattern { - name: "CPU Spin".to_string(), - description: "Trigger compute-heavy operations".to_string(), - applicable_axes: vec![AttackAxis::Cpu], - applicable_languages: vec![Language::Python], - applicable_frameworks: vec![], - command_template: "{program} --iterations 1000000".to_string(), - }, - ] + vec![AttackPattern { + name: "CPU Spin".to_string(), + description: "Trigger compute-heavy operations".to_string(), + applicable_axes: vec![AttackAxis::Cpu], + applicable_languages: vec![Language::Python], + applicable_frameworks: vec![], + command_template: "{program} --iterations 1000000".to_string(), + }] } fn webserver_patterns() -> Vec { @@ -127,8 +120,7 @@ impl PatternDetector { applicable_axes: vec![AttackAxis::Network, AttackAxis::Concurrency], applicable_languages: vec![], applicable_frameworks: vec![Framework::WebServer], - command_template: "wrk -t12 -c400 -d{duration}s http://localhost:8080/" - .to_string(), + command_template: "wrk -t12 -c400 -d{duration}s http://localhost:8080/".to_string(), }, AttackPattern { name: "Large POST".to_string(), @@ -144,28 +136,24 @@ impl PatternDetector { } fn database_patterns() -> Vec { - vec![ - AttackPattern { - name: "Query Storm".to_string(), - description: "Execute many concurrent queries".to_string(), - applicable_axes: vec![AttackAxis::Disk, AttackAxis::Concurrency], - applicable_languages: vec![], - applicable_frameworks: vec![Framework::Database], - command_template: "{program} --query-load 1000".to_string(), - }, - ] + vec![AttackPattern { + name: "Query Storm".to_string(), + description: "Execute many concurrent queries".to_string(), + applicable_axes: vec![AttackAxis::Disk, AttackAxis::Concurrency], + applicable_languages: vec![], + applicable_frameworks: vec![Framework::Database], + command_template: "{program} --query-load 1000".to_string(), + }] } fn concurrency_patterns() -> Vec { - vec![ - AttackPattern { - name: "Deadlock Induction".to_string(), - description: "Trigger concurrent operations that may deadlock".to_string(), - applicable_axes: vec![AttackAxis::Concurrency], - applicable_languages: vec![], - applicable_frameworks: vec![Framework::Concurrent], - command_template: "{program} --threads 100 --contention high".to_string(), - }, - ] + vec![AttackPattern { + name: "Deadlock Induction".to_string(), + description: "Trigger concurrent operations that may deadlock".to_string(), + applicable_axes: vec![AttackAxis::Concurrency], + applicable_languages: vec![], + applicable_frameworks: vec![Framework::Concurrent], + command_template: "{program} --threads 100 --contention high".to_string(), + }] } } diff --git a/tests/analyzer_tests.rs b/tests/analyzer_tests.rs index a784a74..92eb031 100644 --- a/tests/analyzer_tests.rs +++ b/tests/analyzer_tests.rs @@ -5,7 +5,6 @@ use panic_attacker::types::*; use panic_attacker::xray; use std::fs; -use std::path::Path; use tempfile::TempDir; fn create_test_file(dir: &TempDir, name: &str, content: &str) -> std::path::PathBuf { @@ -97,9 +96,10 @@ int main() { let file = create_test_file(&dir, "test.c", content); let report = xray::analyze(&file).expect("analysis should succeed"); - let unchecked = report.weak_points.iter().any(|wp| { - matches!(wp.category, WeakPointCategory::UncheckedAllocation) - }); + let unchecked = report + .weak_points + .iter() + .any(|wp| matches!(wp.category, WeakPointCategory::UncheckedAllocation)); assert!(unchecked, "Should detect unchecked malloc"); } @@ -134,9 +134,10 @@ def main(): let report = xray::analyze(&file).expect("analysis should succeed"); assert_eq!(report.language, Language::Python); - let unbounded = report.weak_points.iter().any(|wp| { - matches!(wp.category, WeakPointCategory::UnboundedLoop) - }); + let unbounded = report + .weak_points + .iter() + .any(|wp| matches!(wp.category, WeakPointCategory::UnboundedLoop)); assert!(unbounded, "Should detect unbounded loop"); } @@ -202,7 +203,10 @@ fn main() { let file = create_test_file(&dir, "test.rs", content); let report = xray::analyze(&file).expect("analysis should succeed"); - assert!(!report.file_statistics.is_empty(), "Should have file statistics"); + assert!( + !report.file_statistics.is_empty(), + "Should have file statistics" + ); let stats = &report.file_statistics[0]; assert!(stats.file_path.contains("test.rs")); assert!(stats.lines > 0); diff --git a/tests/regression_tests.rs b/tests/regression_tests.rs index 78aa3b3..1e7cfc0 100644 --- a/tests/regression_tests.rs +++ b/tests/regression_tests.rs @@ -15,8 +15,7 @@ fn test_echidna_baseline() { return; } - let report = xray::analyze(echidna_path) - .expect("echidna analysis should succeed"); + let report = xray::analyze(echidna_path).expect("echidna analysis should succeed"); // v0.2 baseline: 15 weak points (down from 271 in v0.1) assert_eq!(report.language, panic_attacker::types::Language::Rust); @@ -28,7 +27,11 @@ fn test_echidna_baseline() { // All weak points must have locations for wp in &report.weak_points { - assert!(wp.location.is_some(), "Weak point missing location: {:?}", wp); + assert!( + wp.location.is_some(), + "Weak point missing location: {:?}", + wp + ); } // Should detect multiple frameworks @@ -36,9 +39,15 @@ fn test_echidna_baseline() { // Should have file statistics assert!(!report.file_statistics.is_empty()); - assert!(report.file_statistics.len() >= 40, "Expected 40+ files with findings"); + assert!( + report.file_statistics.len() >= 40, + "Expected 40+ files with findings" + ); - println!("✅ echidna baseline validated: {} weak points", report.weak_points.len()); + println!( + "✅ echidna baseline validated: {} weak points", + report.weak_points.len() + ); } #[test] @@ -51,8 +60,7 @@ fn test_eclexia_baseline() { return; } - let report = xray::analyze(eclexia_path) - .expect("eclexia analysis should succeed"); + let report = xray::analyze(eclexia_path).expect("eclexia analysis should succeed"); // v0.2 baseline: 7 weak points assert_eq!(report.language, panic_attacker::types::Language::Rust); @@ -64,14 +72,24 @@ fn test_eclexia_baseline() { // All weak points must have locations for wp in &report.weak_points { - assert!(wp.location.is_some(), "Weak point missing location: {:?}", wp); + assert!( + wp.location.is_some(), + "Weak point missing location: {:?}", + wp + ); } // Should have file statistics assert!(!report.file_statistics.is_empty()); - assert!(report.file_statistics.len() >= 40, "Expected 40+ files with findings"); + assert!( + report.file_statistics.len() >= 40, + "Expected 40+ files with findings" + ); - println!("✅ eclexia baseline validated: {} weak points", report.weak_points.len()); + println!( + "✅ eclexia baseline validated: {} weak points", + report.weak_points.len() + ); } #[test] @@ -79,8 +97,7 @@ fn test_eclexia_baseline() { fn test_panic_attacker_on_itself() { let self_path = Path::new(env!("CARGO_MANIFEST_DIR")); - let report = xray::analyze(self_path) - .expect("self-analysis should succeed"); + let report = xray::analyze(self_path).expect("self-analysis should succeed"); assert_eq!(report.language, panic_attacker::types::Language::Rust); @@ -96,5 +113,8 @@ fn test_panic_attacker_on_itself() { assert!(wp.location.is_some()); } - println!("✅ Self-test passed: {} weak points found", report.weak_points.len()); + println!( + "✅ Self-test passed: {} weak points found", + report.weak_points.len() + ); }