@@ -22,6 +22,7 @@ use {solana_remote_wallet::remote_wallet::RemoteWalletManager, std::sync::Arc};
2222pub mod clparse;
2323pub mod config; // Added
2424pub mod prelude;
25+ pub mod services;
2526pub mod utils;
2627
2728// Config struct is now in src/config.rs
@@ -117,182 +118,49 @@ fn show_devnet_logs(lines: usize, follow: bool) -> Result<(), Box<dyn std::error
117118 Ok ( ( ) )
118119}
119120
120- /// Handle the audit command separately to avoid triggering config loading and self-repair
121+ /// Handle the audit command using the dedicated audit service
121122async fn handle_audit_command ( app_matches : & clap:: ArgMatches , matches : & clap:: ArgMatches ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
122- use crate :: utils:: audit:: AuditCoordinator ;
123- use std:: fs;
124- use std:: path:: Path ;
125-
126- println ! ( "🔍 OSVM Security Audit" ) ;
127- println ! ( "======================" ) ;
123+ use crate :: services:: audit_service:: { AuditService , AuditRequest } ;
128124
129- let output_dir = matches. get_one :: < String > ( "output" ) . unwrap ( ) ;
130- let format = matches. get_one :: < String > ( "format" ) . unwrap ( ) ;
125+ let output_dir = matches. get_one :: < String > ( "output" ) . unwrap ( ) . to_string ( ) ;
126+ let format = matches. get_one :: < String > ( "format" ) . unwrap ( ) . to_string ( ) ;
131127 let verbose = matches. get_count ( "verbose" ) ;
132128 let test_mode = matches. get_flag ( "test" ) ;
133129 let ai_analysis = matches. get_flag ( "ai-analysis" ) ;
134- let gh_repo = matches. get_one :: < String > ( "gh" ) ;
135-
136- if verbose > 0 {
137- println ! ( "📁 Output directory: {}" , output_dir) ;
138- println ! ( "📄 Format: {}" , format) ;
139- if test_mode {
140- println ! ( "🧪 Test mode: generating sample audit report" ) ;
141- }
142- if ai_analysis {
143- println ! ( "🤖 AI analysis: enabled" ) ;
144- }
145- if let Some ( repo) = gh_repo {
146- println ! ( "🐙 GitHub repository: {}" , repo) ;
147- }
148- }
149-
150- // Check for OpenAI API key if AI analysis is enabled
151- let openai_api_key = if ai_analysis {
152- match std:: env:: var ( "OPENAI_API_KEY" ) {
153- Ok ( key) => {
154- if key. is_empty ( ) {
155- eprintln ! ( "❌ OPENAI_API_KEY environment variable is empty" ) ;
156- eprintln ! ( " Please set your OpenAI API key to enable AI analysis" ) ;
157- exit ( 1 ) ;
158- }
159- Some ( key)
160- }
161- Err ( _) => {
162- eprintln ! ( "❌ OPENAI_API_KEY environment variable not found" ) ;
163- eprintln ! ( " Please set your OpenAI API key to enable AI analysis" ) ;
164- eprintln ! ( " Example: export OPENAI_API_KEY='your-api-key-here'" ) ;
165- exit ( 1 ) ;
166- }
167- }
168- } else {
169- None
170- } ;
171-
172- // Create output directory
173- if let Err ( e) = fs:: create_dir_all ( output_dir) {
174- eprintln ! ( "❌ Failed to create output directory: {}" , e) ;
175- exit ( 1 ) ;
176- }
177-
178- // Create a single audit coordinator instance for all operations
179- let audit_coordinator = if let Some ( api_key) = openai_api_key. clone ( ) {
180- AuditCoordinator :: with_ai ( api_key)
181- } else {
182- AuditCoordinator :: new ( )
130+ let gh_repo = matches. get_one :: < String > ( "gh" ) . map ( |s| s. to_string ( ) ) ;
131+
132+ let request = AuditRequest {
133+ output_dir,
134+ format,
135+ verbose,
136+ test_mode,
137+ ai_analysis,
138+ gh_repo,
183139 } ;
184140
185- // Generate audit report - handle GitHub mode, test mode, or regular audit
186- let report = if let Some ( repo_spec) = gh_repo {
187- // GitHub repository audit mode
188- println ! ( "🐙 GitHub repository audit mode" ) ;
141+ // Create the audit service with or without AI
142+ let service = if ai_analysis {
143+ let api_key = std:: env:: var ( "OPENAI_API_KEY" ) . map_err ( |_| {
144+ std:: io:: Error :: new ( std:: io:: ErrorKind :: NotFound , "OPENAI_API_KEY environment variable not found" )
145+ } ) ?;
189146
190- match audit_coordinator. audit_github_repository ( repo_spec) . await {
191- Ok ( _) => {
192- println ! ( "✅ GitHub repository audit completed and pushed" ) ;
193- return Ok ( ( ) ) ; // Exit early as files are already generated and committed
194- } ,
195- Err ( e) => {
196- eprintln ! ( "❌ Failed to audit GitHub repository: {}" , e) ;
197- exit ( 1 ) ;
198- }
147+ if api_key. is_empty ( ) {
148+ return Err ( "OPENAI_API_KEY environment variable is empty" . into ( ) ) ;
199149 }
200- } else if test_mode {
201- println ! ( "🧪 Generating test audit report..." ) ;
202- audit_coordinator. create_test_audit_report ( )
150+
151+ AuditService :: with_ai ( api_key)
203152 } else {
204- // Run security audit
205- match audit_coordinator. run_security_audit ( ) . await {
206- Ok ( report) => report,
207- Err ( e) => {
208- eprintln ! ( "❌ Failed to run security audit: {}" , e) ;
209- exit ( 1 ) ;
210- }
211- }
153+ AuditService :: new ( )
212154 } ;
213155
214- println ! ( "✅ Security audit completed successfully" ) ;
215- println ! ( "📊 Security Score: {:.1}/100" , report. summary. security_score) ;
216- println ! ( "🔍 Total Findings: {}" , report. summary. total_findings) ;
217-
218- if report. summary . critical_findings > 0 {
219- println ! ( "🔴 Critical: {}" , report. summary. critical_findings) ;
220- }
221- if report. summary . high_findings > 0 {
222- println ! ( "🟠 High: {}" , report. summary. high_findings) ;
223- }
224- if report. summary . medium_findings > 0 {
225- println ! ( "🟡 Medium: {}" , report. summary. medium_findings) ;
226- }
227- if report. summary . low_findings > 0 {
228- println ! ( "🔵 Low: {}" , report. summary. low_findings) ;
229- }
230-
231- // Generate timestamp for unique filenames
232- let timestamp = chrono:: Utc :: now ( ) . format ( "%Y%m%d_%H%M%S" ) ;
233-
234- // Generate outputs based on requested format
235- let typst_path = Path :: new ( output_dir) . join ( format ! ( "osvm_audit_report_{}.typ" , timestamp) ) ;
236- let pdf_path = Path :: new ( output_dir) . join ( format ! ( "osvm_audit_report_{}.pdf" , timestamp) ) ;
237-
238- // Generate outputs based on requested format using the same coordinator
239- match format. as_str ( ) {
240- "typst" | "both" => {
241- if let Err ( e) = audit_coordinator. generate_typst_document ( & report, & typst_path) {
242- eprintln ! ( "❌ Failed to generate Typst document: {}" , e) ;
243- exit ( 1 ) ;
244- }
245- println ! ( "📄 Typst document generated: {}" , typst_path. display( ) ) ;
246-
247- if format == "both" {
248- if let Err ( e) = audit_coordinator. compile_to_pdf ( & typst_path, & pdf_path) {
249- eprintln ! ( "❌ Failed to compile PDF: {}" , e) ;
250- eprintln ! ( " Typst document is available at: {}" , typst_path. display( ) ) ;
251- } else {
252- println ! ( "📋 PDF report generated: {}" , pdf_path. display( ) ) ;
253- }
254- }
255- }
256- "pdf" => {
257- // Generate Typst document first (temporary)
258- if let Err ( e) = audit_coordinator. generate_typst_document ( & report, & typst_path) {
259- eprintln ! ( "❌ Failed to generate Typst document: {}" , e) ;
260- exit ( 1 ) ;
261- }
262-
263- if let Err ( e) = audit_coordinator. compile_to_pdf ( & typst_path, & pdf_path) {
264- eprintln ! ( "❌ Failed to compile PDF: {}" , e) ;
265- exit ( 1 ) ;
266- }
267-
268- // Remove temporary Typst file
269- let _ = fs:: remove_file ( & typst_path) ;
270- println ! ( "📋 PDF report generated: {}" , pdf_path. display( ) ) ;
271- }
272- _ => {
273- eprintln ! ( "❌ Invalid format specified: {}" , format) ;
274- eprintln ! ( " Valid formats: typst, pdf, both" ) ;
275- exit ( 1 ) ;
276- }
277- }
278-
279- if verbose > 0 {
280- println ! ( "\n 📋 Audit Summary:" ) ;
281- println ! ( " Compliance Level: {}" , report. summary. compliance_level) ;
282- println ! ( " System: {} {}" , report. system_info. os_info, report. system_info. architecture) ;
283- println ! ( " Rust Version: {}" , report. system_info. rust_version) ;
284- if let Some ( ref solana_version) = report. system_info . solana_version {
285- println ! ( " Solana Version: {}" , solana_version) ;
286- }
287- }
156+ // Execute the audit
157+ let result = service. execute_audit ( & request) . await ?;
288158
289- println ! ( "\n 💡 To view the full report, open the generated file." ) ;
290-
291- // Exit with appropriate code based on findings severity
292- if !test_mode && ( report. summary . critical_findings > 0 || report. summary . high_findings > 0 ) {
159+ // Handle exit code for CI/CD systems
160+ if !result. success {
293161 println ! ( "⚠️ Critical or high-severity findings detected. Please review and address them promptly." ) ;
294162 println ! ( "📋 This audit exits with code 1 to signal CI/CD systems about security issues." ) ;
295- exit ( 1 ) ;
163+ std :: process :: exit ( 1 ) ;
296164 }
297165
298166 Ok ( ( ) )
0 commit comments