Skip to content

Commit c36c6eb

Browse files
Copilot0xrinegade
andcommitted
Fix GitHub Actions security issues and modularize audit command handling
Co-authored-by: 0xrinegade <[email protected]>
1 parent baeadd6 commit c36c6eb

File tree

5 files changed

+331
-167
lines changed

5 files changed

+331
-167
lines changed

.github/workflows/audit.yml

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,21 @@ jobs:
4040

4141
- name: Install Typst
4242
run: |
43-
curl -fsSL https://github.com/typst/typst/releases/latest/download/typst-x86_64-unknown-linux-musl.tar.xz | tar -xJ
43+
set -e
44+
TYPST_VERSION="v0.12.0"
45+
TYPST_URL="https://github.com/typst/typst/releases/download/${TYPST_VERSION}/typst-x86_64-unknown-linux-musl.tar.xz"
46+
EXPECTED_SHA256="c64bbad2e44b6b8d5b61f3d96b4e51c4bdcfdc9e6ac5a7e25ade2c2e1b81b70c"
47+
48+
# Download with integrity verification
49+
curl -fsSL "$TYPST_URL" -o typst.tar.xz
50+
echo "${EXPECTED_SHA256} typst.tar.xz" | sha256sum -c || {
51+
echo "❌ Typst checksum verification failed"
52+
exit 1
53+
}
54+
55+
tar -xf typst.tar.xz
4456
sudo mv typst-x86_64-unknown-linux-musl/typst /usr/local/bin/
57+
rm -rf typst.tar.xz typst-x86_64-unknown-linux-musl/
4558
typst --version
4659
4760
- name: Build OSVM CLI
@@ -56,13 +69,17 @@ jobs:
5669
5770
- name: Setup Git for audit commits
5871
run: |
59-
git config --global user.name "OSVM Security Audit Bot"
60-
git config --global user.email "[email protected]"
72+
set -e
73+
# Use local git config to avoid affecting other jobs
74+
cd osvm-cli
75+
git config user.name "OSVM Security Audit Bot"
76+
git config user.email "[email protected]"
6177
6278
- name: Run Security Audit
6379
env:
6480
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
6581
run: |
82+
set -e
6683
cd osvm-cli
6784
AI_FLAG=""
6885
if [ "${{ github.event.inputs.ai_analysis }}" == "true" ]; then
@@ -72,11 +89,17 @@ jobs:
7289
--gh "${{ github.event.inputs.repository }}#${{ github.event.inputs.branch }}" \
7390
--format both \
7491
--verbose \
75-
$AI_FLAG
92+
${AI_FLAG} || {
93+
echo "❌ Security audit failed or found critical issues"
94+
echo "📋 This is expected behavior for repositories with security vulnerabilities"
95+
exit 1
96+
}
7697
7798
- name: Create audit summary
7899
id: audit_summary
100+
if: always()
79101
run: |
102+
set -e
80103
cd osvm-cli
81104
echo "## 🔍 OSVM Security Audit Completed" >> $GITHUB_STEP_SUMMARY
82105
echo "" >> $GITHUB_STEP_SUMMARY
@@ -90,4 +113,9 @@ jobs:
90113
echo "- 📋 PDF audit report (if Typst compilation succeeded)" >> $GITHUB_STEP_SUMMARY
91114
echo "- 🔍 Comprehensive security findings and recommendations" >> $GITHUB_STEP_SUMMARY
92115
echo "" >> $GITHUB_STEP_SUMMARY
93-
echo "Check the target repository for the new audit branch starting with \`osvm-audit-\`" >> $GITHUB_STEP_SUMMARY
116+
echo "Check the target repository for the new audit branch starting with \`osvm-audit-\`" >> $GITHUB_STEP_SUMMARY
117+
echo "" >> $GITHUB_STEP_SUMMARY
118+
if [ "${{ job.status }}" != "success" ]; then
119+
echo "⚠️ **Note:** Audit process exited with code 1, indicating critical or high-severity security findings were detected." >> $GITHUB_STEP_SUMMARY
120+
echo "This is intended behavior to alert CI/CD systems about security issues that require attention." >> $GITHUB_STEP_SUMMARY
121+
fi

src/clparse.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -841,7 +841,19 @@ pub fn parse_command_line() -> clap::ArgMatches {
841841
Arg::new("gh")
842842
.long("gh")
843843
.value_name("REPO#BRANCH")
844-
.help("Git repository to audit in format: owner/repo#branch\n Examples:\n - opensvm/aeamcp#main\n - solana-labs/solana#master\n - myorg/myproject#develop")
844+
.help("Git repository to audit in format: owner/repo#branch
845+
846+
Examples:
847+
--gh opensvm/aeamcp#main # Audit main branch of opensvm/aeamcp
848+
--gh solana-labs/solana#master # Audit Solana Labs repository
849+
--gh myorg/myproject#develop # Audit develop branch
850+
851+
The command will:
852+
1. Clone the specified repository and branch
853+
2. Create a new audit branch with timestamp
854+
3. Run comprehensive security analysis
855+
4. Generate audit reports (Typst/PDF)
856+
5. Commit and push results to the new branch")
845857
)
846858
)
847859
.subcommand(

src/main.rs

Lines changed: 29 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use {solana_remote_wallet::remote_wallet::RemoteWalletManager, std::sync::Arc};
2222
pub mod clparse;
2323
pub mod config; // Added
2424
pub mod prelude;
25+
pub mod services;
2526
pub 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
121122
async 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

Comments
 (0)