use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use rand::Rng;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;
use std::process::Command;
use std::time::Duration;
use tokio::time::sleep;
use crate::services::ai_service::AiService;
use crate::utils::audit_modular::{FindingIdAllocator, ModularAuditCoordinator};
use crate::utils::audit_templates::{EnhancedAIErrorHandler, TemplateReportGenerator};
use crate::utils::diagnostics::{
DiagnosticCoordinator, DiagnosticResults, IssueCategory, IssueSeverity,
};
use crate::utils::git_manager::GitRepositoryManager;
fn safe_regex_new(pattern: &str) -> Option<regex::Regex> {
match regex::Regex::new(pattern) {
Ok(r) => Some(r),
Err(e) => {
log::warn!("Failed to compile regex pattern '{}': {}", pattern, e);
None
}
}
}
pub struct OpenAIClient {
api_key: String,
client: reqwest::Client,
max_retries: u32,
base_delay: Duration,
}
#[derive(Debug, Clone)]
pub struct AICircuitBreaker {
failure_count: std::sync::Arc<std::sync::atomic::AtomicU32>,
last_failure_time: std::sync::Arc<std::sync::Mutex<Option<std::time::Instant>>>,
failure_threshold: u32,
recovery_timeout: Duration,
is_open: std::sync::Arc<std::sync::atomic::AtomicBool>,
}
impl AICircuitBreaker {
pub fn new(failure_threshold: u32, recovery_timeout: Duration) -> Self {
Self {
failure_count: std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0)),
last_failure_time: std::sync::Arc::new(std::sync::Mutex::new(None)),
failure_threshold,
recovery_timeout,
is_open: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
}
}
pub fn is_open(&self) -> bool {
if self.is_open.load(std::sync::atomic::Ordering::Acquire) {
if let Ok(last_failure) = self.last_failure_time.lock() {
if let Some(last_time) = *last_failure {
if last_time.elapsed() > self.recovery_timeout {
log::info!("AI circuit breaker attempting recovery after timeout");
self.reset();
return false;
}
}
}
true
} else {
false
}
}
pub fn record_success(&self) {
self.failure_count
.store(0, std::sync::atomic::Ordering::Release);
if self.is_open.load(std::sync::atomic::Ordering::Acquire) {
log::info!("AI circuit breaker closing - operation successful");
self.is_open
.store(false, std::sync::atomic::Ordering::Release);
}
}
pub fn record_failure(&self) {
let current_failures = self
.failure_count
.fetch_add(1, std::sync::atomic::Ordering::AcqRel)
+ 1;
if let Ok(mut last_failure) = self.last_failure_time.lock() {
*last_failure = Some(std::time::Instant::now());
}
if current_failures >= self.failure_threshold
&& !self.is_open.load(std::sync::atomic::Ordering::Acquire)
{
log::warn!(
"AI circuit breaker opening after {} failures",
current_failures
);
self.is_open
.store(true, std::sync::atomic::Ordering::Release);
}
}
pub fn reset(&self) {
self.failure_count
.store(0, std::sync::atomic::Ordering::Release);
self.is_open
.store(false, std::sync::atomic::Ordering::Release);
if let Ok(mut last_failure) = self.last_failure_time.lock() {
*last_failure = None;
}
log::info!("AI circuit breaker reset");
}
pub fn get_failure_count(&self) -> u32 {
self.failure_count
.load(std::sync::atomic::Ordering::Acquire)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AnalysisVector {
StateTransition,
EconomicExploit,
AccessControl,
MathematicalIntegrity,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodeSnippet {
pub file_path: String,
pub start_line: usize,
pub end_line: usize,
pub content: String,
pub context: Option<String>, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeepLogicAnalysis {
pub title: String,
pub confidence_score: f32,
pub severity: AuditSeverity,
pub category: String,
pub analysis_vector: AnalysisVector,
pub ai_powered_analysis: String,
pub risk_scenario: String,
pub problematic_code: CodeSnippet,
pub suggested_fix: CodeSnippet,
pub explanation_of_fix: String,
pub additional_considerations: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AIAnalysis {
pub enhanced_description: String,
pub risk_assessment: String,
pub mitigation_strategy: String,
pub confidence_score: f32,
pub additional_cwe_ids: Vec<String>,
pub deeplogic_analysis: Option<DeepLogicAnalysis>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AIEnhancedFinding {
pub original_finding: AuditFinding,
pub ai_analysis: Option<AIAnalysis>,
}
impl OpenAIClient {
pub fn new(api_key: String) -> Self {
Self {
api_key,
client: reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.build()
.unwrap_or_else(|_| reqwest::Client::new()),
max_retries: 3,
base_delay: Duration::from_millis(1000),
}
}
pub async fn analyze_finding(&self, finding: &AuditFinding) -> Result<AIAnalysis> {
for attempt in 0..=self.max_retries {
match self.try_analyze_finding(finding).await {
Ok(analysis) => return Ok(analysis),
Err(e) if attempt < self.max_retries => {
let (should_retry, base_delay) = EnhancedAIErrorHandler::get_retry_strategy(&e);
if should_retry {
let backoff_factor = 2_u64.pow(attempt as u32);
let jitter = rand::random::<f64>() * 0.5 + 0.5; let delay = std::time::Duration::from_millis(
(base_delay.as_millis() as f64 * backoff_factor as f64 * jitter) as u64,
);
log::warn!(
"AI analysis attempt {}/{} failed, retrying in {}ms: {}",
attempt + 1,
self.max_retries,
delay.as_millis(),
e
);
sleep(delay).await;
} else {
log::error!("AI analysis failed with non-retryable error: {}", e);
return Err(e);
}
}
Err(e) => {
if let Some(fallback) =
EnhancedAIErrorHandler::handle_ai_error(&e, "finding analysis")
{
log::warn!("Using fallback analysis: {}", fallback);
return Ok(AIAnalysis {
enhanced_description:
EnhancedAIErrorHandler::generate_fallback_analysis(
&finding.title,
&finding.category,
),
risk_assessment: "AI analysis unavailable - manual review required"
.to_string(),
mitigation_strategy: finding.recommendation.clone(),
confidence_score: 0.0,
additional_cwe_ids: vec![],
deeplogic_analysis: None,
});
}
return Err(e);
}
}
}
Err(anyhow::anyhow!(
"AI analysis failed after {} retries",
self.max_retries
))
}
async fn try_analyze_finding(&self, finding: &AuditFinding) -> Result<AIAnalysis> {
let prompt = format!(
"Analyze this security finding and provide enhanced insights:\n\n\
Title: {}\n\
Description: {}\n\
Severity: {:?}\n\
Category: {}\n\
Current Recommendation: {}\n\n\
Please provide:\n\
1. Enhanced description with technical details\n\
2. Comprehensive risk assessment\n\
3. Specific mitigation strategy\n\
4. Confidence score (0.0-1.0)\n\
5. Additional relevant CWE IDs if applicable\n\n\
Format your response as JSON with these exact fields:\n\
- enhanced_description\n\
- risk_assessment\n\
- mitigation_strategy\n\
- confidence_score\n\
- additional_cwe_ids (array of strings)",
finding.title,
finding.description,
finding.severity,
finding.category,
finding.recommendation
);
let request_body = serde_json::json!({
"model": "gpt-4.1",
"messages": [
{
"role": "system",
"content": "You are a cybersecurity expert specializing in Rust and blockchain security. Analyze security findings and provide detailed technical insights in JSON format."
},
{
"role": "user",
"content": prompt
}
],
"max_tokens": 1000,
"temperature": 0.3
});
let response = self
.client
.post("https://api.openai.com/v1/chat/completions")
.header("Authorization", format!("Bearer {}", self.api_key))
.header("Content-Type", "application/json")
.json(&request_body)
.send()
.await
.context("Failed to send request to OpenAI API")?;
if !response.status().is_success() {
let error_text = response.text().await.unwrap_or_default();
anyhow::bail!("OpenAI API request failed: {}", error_text);
}
let response_json: serde_json::Value = response
.json()
.await
.context("Failed to parse OpenAI response")?;
let content = response_json
.get("choices")
.and_then(|choices| choices.get(0))
.and_then(|choice| choice.get("message"))
.and_then(|message| message.get("content"))
.and_then(|content| content.as_str())
.ok_or_else(|| anyhow::anyhow!("Invalid OpenAI response format"))?;
let mut ai_analysis: AIAnalysis =
serde_json::from_str(content).context("Failed to parse AI analysis JSON")?;
ai_analysis.deeplogic_analysis = None;
Ok(ai_analysis)
}
pub async fn analyze_code(
&self,
code_content: &str,
file_path: &str,
) -> Result<Vec<AuditFinding>> {
for attempt in 0..=self.max_retries {
match self.try_analyze_code(code_content, file_path).await {
Ok(findings) => return Ok(findings),
Err(e) if attempt < self.max_retries => {
let (should_retry, delay) = EnhancedAIErrorHandler::get_retry_strategy(&e);
if should_retry {
log::warn!(
"AI code analysis attempt {}/{} failed, retrying in {}ms: {}",
attempt + 1,
self.max_retries,
delay.as_millis(),
e
);
sleep(delay).await;
} else {
log::error!("AI code analysis failed with non-retryable error: {}", e);
return Ok(Vec::new()); }
}
Err(e) => {
EnhancedAIErrorHandler::handle_ai_error(&e, "code analysis");
log::warn!(
"AI code analysis completely failed for {}, continuing without AI findings",
file_path
);
return Ok(Vec::new());
}
}
}
log::warn!(
"AI code analysis failed after {} retries for {}",
self.max_retries,
file_path
);
Ok(Vec::new())
}
async fn try_analyze_code(
&self,
code_content: &str,
file_path: &str,
) -> Result<Vec<AuditFinding>> {
let prompt = format!(
"Analyze this Rust code file for security vulnerabilities:\n\n\
File: {}\n\
Code:\n```rust\n{}\n```\n\n\
Please identify potential security issues and provide findings in JSON format.\n\
For each finding, include: id, title, description, severity, category, impact, recommendation.\n\
Severity should be one of: Critical, High, Medium, Low, Info.\n\
Category should be one of: Security, Dependencies, Configuration, Network, Performance.\n\
Respond with a JSON array of findings.",
file_path,
code_content
);
let request_body = serde_json::json!({
"model": "gpt-4.1",
"messages": [
{
"role": "system",
"content": "You are a security expert specializing in Rust code analysis. Identify security vulnerabilities and respond with JSON array of findings."
},
{
"role": "user",
"content": prompt
}
],
"max_tokens": 2000,
"temperature": 0.2
});
let response = self
.client
.post("https://api.openai.com/v1/chat/completions")
.header("Authorization", format!("Bearer {}", self.api_key))
.header("Content-Type", "application/json")
.json(&request_body)
.send()
.await
.context("Failed to send code analysis request to OpenAI API")?;
if !response.status().is_success() {
return Ok(Vec::new()); }
let response_json: serde_json::Value = response
.json()
.await
.context("Failed to parse OpenAI response")?;
let content = response_json
.get("choices")
.and_then(|choices| choices.get(0))
.and_then(|choice| choice.get("message"))
.and_then(|message| message.get("content"))
.and_then(|content| content.as_str())
.unwrap_or("[]");
let ai_findings: Vec<serde_json::Value> = serde_json::from_str(content).unwrap_or_default();
let mut findings = Vec::new();
for (i, finding_json) in ai_findings.iter().enumerate() {
if let Ok(finding) = self.parse_ai_finding(finding_json, i + 1000, file_path) {
findings.push(finding);
}
}
Ok(findings)
}
fn parse_ai_finding(
&self,
finding_json: &serde_json::Value,
id_offset: usize,
file_path: &str,
) -> Result<AuditFinding> {
let title = finding_json
.get("title")
.and_then(|v| v.as_str())
.unwrap_or("AI-detected security issue")
.to_string();
let description = finding_json
.get("description")
.and_then(|v| v.as_str())
.unwrap_or("Security issue identified by AI analysis")
.to_string();
let severity_str = finding_json
.get("severity")
.and_then(|v| v.as_str())
.unwrap_or("Medium");
let severity = match severity_str {
"Critical" => AuditSeverity::Critical,
"High" => AuditSeverity::High,
"Medium" => AuditSeverity::Medium,
"Low" => AuditSeverity::Low,
_ => AuditSeverity::Info,
};
let category = finding_json
.get("category")
.and_then(|v| v.as_str())
.unwrap_or("Security")
.to_string();
let impact = finding_json
.get("impact")
.and_then(|v| v.as_str())
.unwrap_or("Potential security vulnerability")
.to_string();
let recommendation = finding_json
.get("recommendation")
.and_then(|v| v.as_str())
.unwrap_or("Review and address the identified security concern")
.to_string();
Ok(AuditFinding {
id: format!("AI-{:03}", id_offset),
title,
description,
severity,
category,
cwe_id: Some("CWE-AI".to_string()),
cvss_score: Some(5.0),
impact,
recommendation,
code_location: Some(file_path.to_string()),
references: vec!["AI-generated finding".to_string()],
})
}
pub async fn analyze_deeplogic(
&self,
finding: &AuditFinding,
problematic_code: &crate::utils::audit::CodeSnippet,
) -> Result<crate::utils::audit::DeepLogicAnalysis> {
use crate::utils::audit::{AnalysisVector, CodeSnippet, DeepLogicAnalysis};
use crate::utils::code_extractor::CodeExtractor;
for attempt in 0..=self.max_retries {
match self.try_analyze_deeplogic(finding, problematic_code).await {
Ok(analysis) => return Ok(analysis),
Err(e) if attempt < self.max_retries => {
let (should_retry, delay) = EnhancedAIErrorHandler::get_retry_strategy(&e);
if should_retry {
log::warn!(
"DeepLogic analysis attempt {}/{} failed, retrying in {}ms: {}",
attempt + 1,
self.max_retries,
delay.as_millis(),
e
);
sleep(delay).await;
} else {
log::error!("DeepLogic analysis failed with non-retryable error: {}", e);
return Err(e);
}
}
Err(e) => {
log::error!("DeepLogic analysis failed after all retries: {}", e);
return Err(e);
}
}
}
Err(anyhow::anyhow!(
"DeepLogic analysis failed after {} retries",
self.max_retries
))
}
async fn try_analyze_deeplogic(
&self,
finding: &AuditFinding,
problematic_code: &crate::utils::audit::CodeSnippet,
) -> Result<crate::utils::audit::DeepLogicAnalysis> {
use crate::utils::audit::{AnalysisVector, CodeSnippet, DeepLogicAnalysis};
use crate::utils::code_extractor::CodeExtractor;
let prompt = format!(
"Perform DeepLogic AI Analysis for this Solana security vulnerability:\n\n\
VULNERABILITY DETAILS:\n\
Title: {}\n\
Description: {}\n\
Severity: {:?}\n\
Category: {}\n\
File: {}\n\n\
PROBLEMATIC CODE:\n\
```rust\n{}\n```\n\n\
ANALYSIS TASK:\n\
Analyze this vulnerability from multiple vectors:\n\
1. State Transition Analysis - How state changes might be exploited\n\
2. Economic Exploit Simulation - Financial attack scenarios\n\
3. Access Control & Authorization - Permission bypass vulnerabilities\n\
4. Mathematical Integrity - Calculation and overflow issues\n\n\
Provide a comprehensive analysis including:\n\
- AI-Powered Analysis explaining the logical vulnerability\n\
- Risk Scenario with specific attack vectors\n\
- Suggested Fix with corrected Rust code\n\
- Explanation of the fix and why it addresses the issue\n\
- Additional considerations for security\n\n\
Format your response as JSON with these exact fields:\n\
- analysis_vector (one of: StateTransition, EconomicExploit, AccessControl, MathematicalIntegrity)\n\
- ai_powered_analysis (detailed technical explanation)\n\
- risk_scenario (specific attack scenario)\n\
- suggested_fix_code (corrected Rust code)\n\
- explanation_of_fix (why this fix works)\n\
- additional_considerations (array of strings with extra security advice)\n\
- confidence_score (0.0-1.0)",
finding.title,
finding.description,
finding.severity,
finding.category,
problematic_code.file_path,
problematic_code.content
);
let request_body = serde_json::json!({
"model": "gpt-4.1",
"messages": [
{
"role": "system",
"content": "You are a Solana security expert specializing in DeepLogic vulnerability analysis. Analyze complex logical flaws in Rust blockchain code and provide detailed remediation with code snippets. Focus on state transitions, economic exploits, access control bypasses, and mathematical integrity issues."
},
{
"role": "user",
"content": prompt
}
],
"max_tokens": 2000,
"temperature": 0.2
});
let response = self
.client
.post("https://api.openai.com/v1/chat/completions")
.header("Authorization", format!("Bearer {}", self.api_key))
.header("Content-Type", "application/json")
.json(&request_body)
.send()
.await
.context("Failed to send DeepLogic analysis request to OpenAI API")?;
if !response.status().is_success() {
let error_text = response.text().await.unwrap_or_default();
anyhow::bail!("OpenAI API request failed: {}", error_text);
}
let response_json: serde_json::Value = response
.json()
.await
.context("Failed to parse OpenAI response")?;
let content = response_json
.get("choices")
.and_then(|choices| choices.get(0))
.and_then(|choice| choice.get("message"))
.and_then(|message| message.get("content"))
.and_then(|content| content.as_str())
.ok_or_else(|| anyhow::anyhow!("Invalid OpenAI response format"))?;
let deeplogic_json: serde_json::Value =
serde_json::from_str(content).context("Failed to parse DeepLogic analysis JSON")?;
let analysis_vector_str = deeplogic_json
.get("analysis_vector")
.and_then(|v| v.as_str())
.unwrap_or("AccessControl");
let analysis_vector = match analysis_vector_str {
"StateTransition" => AnalysisVector::StateTransition,
"EconomicExploit" => AnalysisVector::EconomicExploit,
"AccessControl" => AnalysisVector::AccessControl,
"MathematicalIntegrity" => AnalysisVector::MathematicalIntegrity,
_ => AnalysisVector::AccessControl,
};
let ai_powered_analysis = deeplogic_json
.get("ai_powered_analysis")
.and_then(|v| v.as_str())
.unwrap_or("AI analysis of logical vulnerability")
.to_string();
let risk_scenario = deeplogic_json
.get("risk_scenario")
.and_then(|v| v.as_str())
.unwrap_or("Potential security risk scenario")
.to_string();
let suggested_fix_code = deeplogic_json
.get("suggested_fix_code")
.and_then(|v| v.as_str())
.unwrap_or(&problematic_code.content)
.to_string();
let explanation_of_fix = deeplogic_json
.get("explanation_of_fix")
.and_then(|v| v.as_str())
.unwrap_or("Fix explanation")
.to_string();
let additional_considerations: Vec<String> = deeplogic_json
.get("additional_considerations")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|item| item.as_str().map(|s| s.to_string()))
.collect()
})
.unwrap_or_else(|| vec!["Additional security review recommended".to_string()]);
let confidence_score = deeplogic_json
.get("confidence_score")
.and_then(|v| v.as_f64())
.unwrap_or(0.7) as f32;
let suggested_fix = CodeSnippet {
file_path: problematic_code.file_path.clone(),
start_line: problematic_code.start_line,
end_line: problematic_code.end_line,
content: suggested_fix_code,
context: Some("AI-generated fix for logical vulnerability".to_string()),
};
Ok(DeepLogicAnalysis {
title: format!("DeepLogic: {}", finding.title),
confidence_score,
severity: finding.severity.clone(),
category: format!("DeepLogic - {}", finding.category),
analysis_vector,
ai_powered_analysis,
risk_scenario,
problematic_code: problematic_code.clone(),
suggested_fix,
explanation_of_fix,
additional_considerations,
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum AuditSeverity {
Critical,
High,
Medium,
Low,
Info,
}
impl From<IssueSeverity> for AuditSeverity {
fn from(severity: IssueSeverity) -> Self {
match severity {
IssueSeverity::Critical => AuditSeverity::Critical,
IssueSeverity::Error => AuditSeverity::High,
IssueSeverity::Warning => AuditSeverity::Medium,
IssueSeverity::Info => AuditSeverity::Info,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditFinding {
pub id: String,
pub title: String,
pub description: String,
pub severity: AuditSeverity,
pub category: String,
pub cwe_id: Option<String>,
pub cvss_score: Option<f32>,
pub impact: String,
pub recommendation: String,
pub code_location: Option<String>,
pub references: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FindingLocation {
pub file_path: String,
pub line_number: usize,
pub code_context: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GroupedFinding {
pub id: String,
pub title: String,
pub description: String,
pub severity: AuditSeverity,
pub category: String,
pub cwe_id: Option<String>,
pub cvss_score: Option<f32>,
pub impact: String,
pub recommendation: String,
pub references: Vec<String>,
pub locations: Vec<FindingLocation>,
pub instance_count: usize,
pub ai_analysis: Option<AIAnalysis>,
}
impl GroupedFinding {
pub fn from_finding(finding: &AuditFinding) -> Self {
let location = if let Some(file_path) = &finding.code_location {
vec![FindingLocation {
file_path: file_path.clone(),
line_number: 1, code_context: None,
}]
} else {
vec![]
};
Self {
id: finding.id.clone(),
title: finding.title.clone(),
description: finding.description.clone(),
severity: finding.severity.clone(),
category: finding.category.clone(),
cwe_id: finding.cwe_id.clone(),
cvss_score: finding.cvss_score,
impact: finding.impact.clone(),
recommendation: finding.recommendation.clone(),
references: finding.references.clone(),
locations: location,
instance_count: 1,
ai_analysis: None,
}
}
pub fn add_instance(&mut self, finding: &AuditFinding) {
if let Some(file_path) = &finding.code_location {
self.locations.push(FindingLocation {
file_path: file_path.clone(),
line_number: 1, code_context: None,
});
}
self.instance_count += 1;
}
pub fn grouping_key(finding: &AuditFinding) -> String {
format!("{}::{}", finding.title, finding.category)
}
pub fn matches_finding(&self, finding: &AuditFinding) -> bool {
self.title == finding.title && self.category == finding.category
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditSummary {
pub total_findings: usize,
pub critical_findings: usize,
pub high_findings: usize,
pub medium_findings: usize,
pub low_findings: usize,
pub info_findings: usize,
pub security_score: f32, pub compliance_level: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditReport {
pub timestamp: DateTime<Utc>,
pub version: String,
pub summary: AuditSummary,
pub findings: Vec<AuditFinding>,
pub system_info: SystemInfo,
pub recommendations: Vec<String>,
pub compliance_notes: Vec<String>,
#[serde(default)]
pub deeplogic_findings: Vec<DeepLogicAnalysis>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemInfo {
pub rust_version: String,
pub solana_version: Option<String>,
pub os_info: String,
pub architecture: String,
pub dependencies: HashMap<String, String>,
}
pub struct AuditCoordinator {
ai_client: Option<OpenAIClient>,
internal_ai_service: Option<AiService>,
modular_coordinator: ModularAuditCoordinator,
template_generator: TemplateReportGenerator,
ai_disabled: bool,
ai_circuit_breaker: AICircuitBreaker,
}
impl Default for AuditCoordinator {
fn default() -> Self {
Self::new()
}
}
impl AuditCoordinator {
pub fn new() -> Self {
Self {
ai_client: None,
internal_ai_service: None,
modular_coordinator: ModularAuditCoordinator::new(),
template_generator: TemplateReportGenerator::new().unwrap_or_else(|e| {
log::warn!("Failed to initialize template generator: {}", e);
panic!("Template generator is required for audit functionality");
}),
ai_disabled: false,
ai_circuit_breaker: AICircuitBreaker::new(3, Duration::from_secs(300)), }
}
pub fn with_internal_ai() -> Self {
Self {
ai_client: None,
internal_ai_service: Some(AiService::new()),
modular_coordinator: ModularAuditCoordinator::new(),
template_generator: TemplateReportGenerator::new().unwrap_or_else(|e| {
log::warn!("Failed to initialize template generator: {}", e);
panic!("Template generator is required for audit functionality");
}),
ai_disabled: false,
ai_circuit_breaker: AICircuitBreaker::new(3, Duration::from_secs(300)), }
}
pub fn with_ai(api_key: String) -> Self {
Self {
ai_client: Some(OpenAIClient::new(api_key)),
internal_ai_service: None,
modular_coordinator: ModularAuditCoordinator::new(),
template_generator: TemplateReportGenerator::new().unwrap_or_else(|e| {
log::warn!("Failed to initialize template generator: {}", e);
panic!("Template generator is required for audit functionality");
}),
ai_disabled: false,
ai_circuit_breaker: AICircuitBreaker::new(3, Duration::from_secs(300)), }
}
pub fn with_custom_ai(api_url: String) -> Self {
Self {
ai_client: None,
internal_ai_service: Some(AiService::with_api_url(Some(api_url))),
modular_coordinator: ModularAuditCoordinator::new(),
template_generator: TemplateReportGenerator::new().unwrap_or_else(|e| {
log::warn!("Failed to initialize template generator: {}", e);
panic!("Template generator is required for audit functionality");
}),
ai_disabled: false,
ai_circuit_breaker: AICircuitBreaker::new(3, Duration::from_secs(300)), }
}
pub fn with_optional_ai(api_key: Option<String>) -> Self {
match api_key {
Some(key) if !key.trim().is_empty() => {
println!("🤖 AI analysis enabled with provided OpenAI API key");
Self::with_ai(key)
}
_ => {
println!("🤖 AI analysis enabled with internal OSVM AI service");
Self::with_internal_ai()
}
}
}
pub fn is_workspace(&self) -> bool {
match cargo_metadata::MetadataCommand::new().exec() {
Ok(metadata) => {
!metadata.workspace_members.is_empty() && metadata.workspace_members.len() > 1
}
Err(_) => {
std::path::Path::new("Cargo.toml").exists()
&& std::fs::read_to_string("Cargo.toml")
.map(|content| content.contains("[workspace]"))
.unwrap_or(false)
}
}
}
pub fn get_workspace_crates(&self) -> Result<Vec<std::path::PathBuf>> {
let mut crates = Vec::new();
match cargo_metadata::MetadataCommand::new().exec() {
Ok(metadata) => {
if metadata.workspace_members.len() > 1 {
for package_id in &metadata.workspace_members {
if let Some(package) =
metadata.packages.iter().find(|p| p.id == *package_id)
{
let manifest_dir = package.manifest_path.parent().ok_or_else(|| {
anyhow::anyhow!("Invalid manifest path: {}", package.manifest_path)
})?;
crates.push(manifest_dir.into());
}
}
} else {
crates.push(std::path::PathBuf::from("."));
}
}
Err(e) => {
log::warn!(
"Failed to use cargo_metadata, falling back to manual parsing: {}",
e
);
if self.is_workspace_fallback() {
crates = self.get_workspace_crates_fallback()?;
} else {
crates.push(std::path::PathBuf::from("."));
}
}
}
if crates.is_empty() {
crates.push(std::path::PathBuf::from("."));
}
Ok(crates)
}
fn is_workspace_fallback(&self) -> bool {
std::path::Path::new("Cargo.toml").exists()
&& std::fs::read_to_string("Cargo.toml")
.map(|content| content.contains("[workspace]"))
.unwrap_or(false)
}
fn get_workspace_crates_fallback(&self) -> Result<Vec<std::path::PathBuf>> {
let mut crates = Vec::new();
let cargo_toml = std::fs::read_to_string("Cargo.toml")?;
let mut in_workspace = false;
let mut in_members = false;
for line in cargo_toml.lines() {
let line = line.trim();
if line == "[workspace]" {
in_workspace = true;
continue;
}
if in_workspace && line.starts_with("members") {
in_members = true;
continue;
}
if in_members && line.starts_with('[') && !line.starts_with("members") {
break;
}
if in_members && line.contains("\"") {
if let Some(member) = line.split('"').nth(1) {
let crate_path = std::path::PathBuf::from(member);
if crate_path.join("Cargo.toml").exists() {
crates.push(crate_path);
}
}
}
}
Ok(crates)
}
#[cfg(test)]
pub fn ai_client(&self) -> &Option<OpenAIClient> {
&self.ai_client
}
#[cfg(test)]
pub async fn enhance_findings_with_ai(
&self,
_ai_client: &OpenAIClient, findings: Vec<AuditFinding>,
) -> Vec<AuditFinding> {
self.enhance_findings_with_ai_internal(findings).await
}
pub fn disable_ai(&mut self) {
self.ai_disabled = true;
self.ai_circuit_breaker.record_failure();
log::warn!("AI analysis disabled due to persistent errors");
}
pub fn should_use_ai(&self) -> bool {
(self.ai_client.is_some() || self.internal_ai_service.is_some())
&& !self.ai_disabled
&& !self.ai_circuit_breaker.is_open()
}
pub fn reset_ai_circuit_breaker(&mut self) {
self.ai_circuit_breaker.reset();
if self.ai_client.is_some() || self.internal_ai_service.is_some() {
self.ai_disabled = false;
log::info!("AI analysis re-enabled after circuit breaker reset");
}
}
pub fn get_ai_status(&self) -> (bool, u32, bool) {
(
self.ai_disabled,
self.ai_circuit_breaker.get_failure_count(),
self.ai_circuit_breaker.is_open(),
)
}
fn group_findings(&self, findings: Vec<AuditFinding>) -> Vec<GroupedFinding> {
let mut groups: HashMap<String, GroupedFinding> = HashMap::new();
for finding in findings {
let grouping_key = GroupedFinding::grouping_key(&finding);
match groups.get_mut(&grouping_key) {
Some(group) => {
group.add_instance(&finding);
}
None => {
let group = GroupedFinding::from_finding(&finding);
groups.insert(grouping_key, group);
}
}
}
let mut grouped_findings: Vec<GroupedFinding> = groups.into_values().collect();
grouped_findings.sort_by(|a, b| {
let severity_order = |severity: &AuditSeverity| match severity {
AuditSeverity::Critical => 0,
AuditSeverity::High => 1,
AuditSeverity::Medium => 2,
AuditSeverity::Low => 3,
AuditSeverity::Info => 4,
};
let severity_cmp = severity_order(&a.severity).cmp(&severity_order(&b.severity));
if severity_cmp != std::cmp::Ordering::Equal {
return severity_cmp;
}
b.instance_count.cmp(&a.instance_count)
});
grouped_findings
}
fn ungrouped_findings_from_groups(&self, groups: &[GroupedFinding]) -> Vec<AuditFinding> {
let mut findings = Vec::new();
for group in groups {
for (index, location) in group.locations.iter().enumerate() {
let finding_id = if index == 0 {
group.id.clone()
} else {
format!("{}-{}", group.id, index + 1)
};
findings.push(AuditFinding {
id: finding_id,
title: group.title.clone(),
description: if group.instance_count > 1 {
format!(
"{} (Instance {} of {})",
group.description,
index + 1,
group.instance_count
)
} else {
group.description.clone()
},
severity: group.severity.clone(),
category: group.category.clone(),
cwe_id: group.cwe_id.clone(),
cvss_score: group.cvss_score,
impact: group.impact.clone(),
recommendation: group.recommendation.clone(),
code_location: Some(format!("{}:{}", location.file_path, location.line_number)),
references: group.references.clone(),
});
}
}
findings
}
async fn enhance_grouped_findings_with_ai(
&self,
mut groups: Vec<GroupedFinding>,
) -> Vec<GroupedFinding> {
let mut ai_failures = 0;
let mut ai_successes = 0;
let mut deeplogic_analyses = 0;
for group in &mut groups {
if group.severity == AuditSeverity::Critical || group.severity == AuditSeverity::High {
if self.ai_circuit_breaker.is_open() {
log::warn!(
"AI circuit breaker is open, skipping AI enhancement for group {}",
group.id
);
continue;
}
let representative_finding = AuditFinding {
id: group.id.clone(),
title: group.title.clone(),
description: format!(
"{} (Found in {} locations with {} instances)",
group.description,
group.locations.len(),
group.instance_count
),
severity: group.severity.clone(),
category: group.category.clone(),
cwe_id: group.cwe_id.clone(),
cvss_score: group.cvss_score,
impact: group.impact.clone(),
recommendation: group.recommendation.clone(),
code_location: group.locations.first().map(|loc| loc.file_path.clone()),
references: group.references.clone(),
};
match self.query_ai_for_finding(&representative_finding).await {
Ok(mut ai_analysis) => {
println!(
"🤖 AI analysis completed for group: {} ({} instances)",
group.title, group.instance_count
);
if self.should_perform_deeplogic_analysis(&representative_finding) {
println!("🧠 Performing DeepLogic AI analysis for: {}", group.title);
if let Ok(deeplogic_analysis) = self
.perform_deeplogic_analysis(&representative_finding)
.await
{
ai_analysis.deeplogic_analysis = Some(deeplogic_analysis);
deeplogic_analyses += 1;
println!("✨ DeepLogic analysis completed for: {}", group.title);
} else {
log::warn!("DeepLogic analysis failed for group: {}", group.id);
}
}
group.ai_analysis = Some(ai_analysis);
ai_successes += 1;
self.ai_circuit_breaker.record_success();
}
Err(e) => {
self.ai_circuit_breaker.record_failure();
if let Some(fallback_msg) = EnhancedAIErrorHandler::handle_ai_error(
&e,
&format!("grouped finding {}", group.id),
) {
log::warn!(
"AI enhancement failed for group {}: {}",
group.id,
fallback_msg
);
}
ai_failures += 1;
if EnhancedAIErrorHandler::should_disable_ai(&e) {
log::error!("Disabling AI analysis due to: {}", e);
break;
}
}
}
}
}
if ai_failures > 0 || ai_successes > 0 {
log::info!(
"AI enhancement completed for grouped findings: {} successes, {} failures, {} DeepLogic analyses",
ai_successes,
ai_failures,
deeplogic_analyses
);
if deeplogic_analyses > 0 {
println!(
"🧠 DeepLogic AI Analysis completed for {} critical vulnerabilities",
deeplogic_analyses
);
}
}
groups
}
pub async fn run_security_audit(&self) -> Result<AuditReport> {
println!("🔍 Starting comprehensive security audit with enhanced modular system...");
if self.should_use_ai() {
println!("🤖 AI-powered analysis enabled");
} else if self.ai_disabled {
println!("🤖 AI analysis disabled due to previous errors");
} else if self.ai_circuit_breaker.is_open() {
let (_, failure_count, _) = self.get_ai_status();
println!(
"🤖 AI analysis temporarily disabled (circuit breaker open after {} failures)",
failure_count
);
} else {
println!("🔧 Running audit without AI analysis");
println!("💡 Use --ai-analysis flag to enable AI-powered analysis");
}
let diagnostic_coordinator = DiagnosticCoordinator::new();
let diagnostic_results = match diagnostic_coordinator.run_detailed_diagnostics().await {
Ok(results) => results,
Err(e) => {
println!("⚠️ Some diagnostic checks failed: {}", e);
println!("📝 Proceeding with modular audit system...");
return self.run_modular_audit_only().await;
}
};
let mut findings = self.convert_diagnostics_to_findings(&diagnostic_results);
findings.extend(self.run_modular_security_checks().await?);
println!("📊 Grouping findings for enhanced reporting...");
let mut grouped_findings = self.group_findings(findings);
let total_instances: usize = grouped_findings.iter().map(|g| g.instance_count).sum();
let total_groups = grouped_findings.len();
println!(
"📋 Grouped {} findings into {} groups",
total_instances, total_groups
);
if self.should_use_ai() {
println!("🤖 Running optimized AI-powered analysis on grouped findings...");
if let Some(ref ai_client) = self.ai_client {
let ai_findings = self.perform_ai_code_analysis(ai_client).await;
if !ai_findings.is_empty() {
println!(
"🔍 Integrating {} AI-discovered findings...",
ai_findings.len()
);
let ai_groups = self.group_findings(ai_findings);
grouped_findings.extend(ai_groups);
}
}
grouped_findings = self
.enhance_grouped_findings_with_ai(grouped_findings)
.await;
}
let findings = self.ungrouped_findings_from_groups(&grouped_findings);
let deeplogic_findings = self.extract_deeplogic_findings(&grouped_findings);
let system_info = self.collect_system_info().await?;
let summary = self.calculate_audit_summary(&findings);
let recommendations = self.generate_security_recommendations(&findings);
let compliance_notes = self.generate_compliance_notes(&findings);
let audit_report = AuditReport {
timestamp: Utc::now(),
version: env!("CARGO_PKG_VERSION").to_string(),
summary,
findings,
system_info,
recommendations,
compliance_notes,
deeplogic_findings,
};
println!("✅ Enhanced security audit completed");
Ok(audit_report)
}
pub async fn run_modular_audit_only(&self) -> Result<AuditReport> {
println!("🔧 Running modular security audit system...");
let mut findings = Vec::new();
findings.extend(self.run_modular_security_checks().await?);
findings.push(AuditFinding {
id: FindingIdAllocator::next_id(),
title: "Diagnostic system unavailable".to_string(),
description: "Full system diagnostics could not be completed. This may indicate configuration issues.".to_string(),
severity: AuditSeverity::Medium,
category: "System".to_string(),
cwe_id: Some("CWE-754".to_string()),
cvss_score: Some(4.0),
impact: "Limited visibility into system security posture".to_string(),
recommendation: "Review system configuration and ensure all dependencies are properly installed".to_string(),
code_location: None,
references: vec!["https://cwe.mitre.org/data/definitions/754.html".to_string()],
});
println!("📊 Grouping findings for enhanced reporting...");
let grouped_findings = self.group_findings(findings);
let total_instances: usize = grouped_findings.iter().map(|g| g.instance_count).sum();
let total_groups = grouped_findings.len();
println!(
"📋 Grouped {} findings into {} groups",
total_instances, total_groups
);
let findings = self.ungrouped_findings_from_groups(&grouped_findings);
let system_info = self.collect_system_info().await?;
let summary = self.calculate_audit_summary(&findings);
let recommendations = self.generate_security_recommendations(&findings);
let compliance_notes = self.generate_compliance_notes(&findings);
Ok(AuditReport {
timestamp: Utc::now(),
version: env!("CARGO_PKG_VERSION").to_string(),
summary,
findings,
system_info,
recommendations,
compliance_notes,
deeplogic_findings: vec![], })
}
async fn run_modular_security_checks(&self) -> Result<Vec<AuditFinding>> {
let mut all_findings = Vec::new();
println!("🔍 Running modular security checks...");
let crates = self.get_workspace_crates()?;
if crates.len() > 1 {
println!("📦 Detected workspace with {} crates", crates.len());
}
for crate_path in &crates {
let src_dir = crate_path.join("src");
let crate_name = crate_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("root");
if src_dir.exists() {
println!("🔍 Scanning crate: {}", crate_name);
self.scan_rust_files_recursive(&src_dir, &mut all_findings, crate_name)
.await?;
}
}
for crate_path in &crates {
let current_dir = std::env::current_dir()?;
if let Err(e) = std::env::set_current_dir(crate_path) {
log::warn!(
"Failed to change directory to {}: {}",
crate_path.display(),
e
);
continue;
}
all_findings.extend(self.check_dependency_security_enhanced()?);
all_findings.extend(self.check_configuration_security_enhanced()?);
if let Err(e) = std::env::set_current_dir(¤t_dir) {
log::warn!("Failed to restore directory: {}", e);
}
}
println!(
"✅ Modular security checks completed - {} findings",
all_findings.len()
);
Ok(all_findings)
}
fn scan_rust_files_recursive<'a>(
&'a self,
dir: &'a std::path::Path,
findings: &'a mut Vec<AuditFinding>,
crate_name: &'a str,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + 'a>> {
Box::pin(async move {
let mut entries = tokio::fs::read_dir(dir).await?;
while let Some(entry) = entries.next_entry().await? {
let path = entry.path();
if path.is_dir() {
if let Some(dir_name) = path.file_name().and_then(|n| n.to_str()) {
if !matches!(dir_name, "target" | ".git" | "node_modules" | ".cargo") {
self.scan_rust_files_recursive(&path, findings, crate_name)
.await?;
}
}
} else if let Some(ext) = path.extension() {
if ext == "rs" {
if let Ok(content) = tokio::fs::read_to_string(&path).await {
let file_path = format!("{}/{}", crate_name, path.display());
match self.modular_coordinator.audit_file(&content, &file_path) {
Ok(mut file_findings) => {
for finding in &mut file_findings {
finding.code_location = Some(format!(
"{}:{}",
crate_name,
finding
.code_location
.as_ref()
.unwrap_or(&"unknown".to_string())
));
}
println!(
" 📄 Analyzed {} - {} findings",
file_path,
file_findings.len()
);
findings.extend(file_findings);
}
Err(e) => {
log::warn!("Failed to analyze file {}: {}", file_path, e);
}
}
}
}
}
}
Ok(())
})
}
async fn create_fallback_audit_report(&self) -> Result<AuditReport> {
let mut findings = Vec::new();
findings.extend(self.perform_additional_security_checks());
findings.push(AuditFinding {
id: "OSVM-999".to_string(),
title: "Diagnostic system unavailable".to_string(),
description: "Full system diagnostics could not be completed. This may indicate configuration issues.".to_string(),
severity: AuditSeverity::Medium,
category: "System".to_string(),
cwe_id: Some("CWE-754".to_string()),
cvss_score: Some(4.0),
impact: "Limited visibility into system security posture".to_string(),
recommendation: "Review system configuration and ensure all dependencies are properly installed".to_string(),
code_location: None,
references: vec!["https://cwe.mitre.org/data/definitions/754.html".to_string()],
});
let system_info = self.collect_system_info().await?;
let summary = self.calculate_audit_summary(&findings);
let recommendations = self.generate_security_recommendations(&findings);
let compliance_notes = self.generate_compliance_notes(&findings);
Ok(AuditReport {
timestamp: Utc::now(),
version: env!("CARGO_PKG_VERSION").to_string(),
summary,
findings,
system_info,
recommendations,
compliance_notes,
deeplogic_findings: vec![], })
}
fn convert_diagnostics_to_findings(
&self,
diagnostics: &DiagnosticResults,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
let mut finding_id = 1;
for issue in &diagnostics.system_health.issues {
let severity = AuditSeverity::from(issue.severity.clone());
let category = match issue.category {
IssueCategory::Security => "Security",
IssueCategory::SystemDependencies => "Dependencies",
IssueCategory::UserConfiguration => "Configuration",
IssueCategory::NetworkConnectivity => "Network",
IssueCategory::Performance => "Performance",
};
let (cwe_id, cvss_score, impact) =
self.analyze_security_impact(&issue.title, &issue.description, &severity);
findings.push(AuditFinding {
id: format!("OSVM-{:03}", finding_id),
title: issue.title.clone(),
description: issue.description.clone(),
severity,
category: category.to_string(),
cwe_id,
cvss_score,
impact,
recommendation: issue
.suggested_fix
.clone()
.unwrap_or_else(|| "Manual review required".to_string()),
code_location: None,
references: self.get_security_references(category),
});
finding_id += 1;
}
findings.extend(self.perform_additional_security_checks());
findings
}
fn analyze_security_impact(
&self,
title: &str,
_description: &str,
severity: &AuditSeverity,
) -> (Option<String>, Option<f32>, String) {
let (cwe_id, impact) = if title.contains("permissions") || title.contains("keypair") {
(
Some("CWE-276".to_string()),
"Unauthorized access to sensitive cryptographic material",
)
} else if title.contains("network") || title.contains("connectivity") {
(
Some("CWE-300".to_string()),
"Potential network-based attacks or service disruption",
)
} else if title.contains("dependency") || title.contains("outdated") {
(
Some("CWE-937".to_string()),
"Known vulnerabilities in dependencies",
)
} else if title.contains("configuration") {
(
Some("CWE-16".to_string()),
"Misconfiguration leading to security weaknesses",
)
} else {
(None, "General security concern requiring assessment")
};
let cvss_score = match severity {
AuditSeverity::Critical => Some(9.0),
AuditSeverity::High => Some(7.5),
AuditSeverity::Medium => Some(5.0),
AuditSeverity::Low => Some(3.0),
AuditSeverity::Info => Some(1.0),
};
(cwe_id, cvss_score, impact.to_string())
}
fn get_security_references(&self, category: &str) -> Vec<String> {
match category {
"Security" => vec![
"https://owasp.org/Top10/".to_string(),
"https://cwe.mitre.org/".to_string(),
"https://docs.solana.com/developing/programming-model/security".to_string(),
],
"Dependencies" => vec![
"https://rustsec.org/".to_string(),
"https://docs.rs/cargo-audit/".to_string(),
],
"Configuration" => {
vec!["https://docs.solana.com/running-validator/validator-start".to_string()]
}
_ => vec![],
}
}
async fn perform_ai_code_analysis(&self, ai_client: &OpenAIClient) -> Vec<AuditFinding> {
let mut ai_findings = Vec::new();
let mut files_analyzed = 0;
let mut files_failed = 0;
if let Ok(entries) = std::fs::read_dir("src") {
for entry in entries.flatten() {
if let Some(ext) = entry.path().extension() {
if ext == "rs" {
if let Ok(content) = std::fs::read_to_string(&entry.path()) {
if content.len() < 80000 {
match ai_client
.analyze_code(&content, &entry.path().display().to_string())
.await
{
Ok(findings) => {
ai_findings.extend(findings);
files_analyzed += 1;
}
Err(e) => {
EnhancedAIErrorHandler::handle_ai_error(
&e,
&format!("file {}", entry.path().display()),
);
files_failed += 1;
if EnhancedAIErrorHandler::should_disable_ai(&e)
|| files_failed > 5
{
log::warn!("Stopping AI code analysis due to repeated failures");
break;
}
}
}
} else {
log::info!(
"Skipping large file for AI analysis: {}",
entry.path().display()
);
}
}
}
}
}
}
if files_analyzed > 0 || files_failed > 0 {
log::info!(
"AI code analysis completed: {} files analyzed, {} files failed",
files_analyzed,
files_failed
);
}
ai_findings
}
async fn query_ai_for_finding(&self, finding: &AuditFinding) -> Result<AIAnalysis> {
if let Some(ref ai_client) = self.ai_client {
ai_client.analyze_finding(finding).await
} else if let Some(ref internal_ai) = self.internal_ai_service {
let prompt = format!(
"Analyze this security finding and provide enhanced insights:\n\n\
Title: {}\n\
Description: {}\n\
Severity: {:?}\n\
Category: {}\n\
Current Recommendation: {}\n\n\
Please provide:\n\
1. Enhanced description with technical details\n\
2. Comprehensive risk assessment\n\
3. Specific mitigation strategy\n\
4. Confidence score (0.0-1.0)\n\
5. Additional relevant CWE IDs if applicable",
finding.title,
finding.description,
finding.severity,
finding.category,
finding.recommendation
);
match internal_ai.query(&prompt).await {
Ok(response) => {
Ok(AIAnalysis {
enhanced_description: response.clone(),
risk_assessment: format!("AI analysis: {}", response),
mitigation_strategy: finding.recommendation.clone(),
confidence_score: 0.8, additional_cwe_ids: vec![],
deeplogic_analysis: None,
})
}
Err(e) => {
anyhow::bail!("Internal AI service error: {}", e)
}
}
} else {
anyhow::bail!("No AI service available")
}
}
async fn enhance_findings_with_ai_internal(
&self,
findings: Vec<AuditFinding>,
) -> Vec<AuditFinding> {
let mut enhanced_findings = Vec::new();
let mut ai_failures = 0;
let mut ai_successes = 0;
for finding in findings {
if finding.severity == AuditSeverity::Critical
|| finding.severity == AuditSeverity::High
{
if self.ai_circuit_breaker.is_open() {
log::warn!(
"AI circuit breaker is open, skipping AI enhancement for finding {}",
finding.id
);
enhanced_findings.push(finding);
continue;
}
match self.query_ai_for_finding(&finding).await {
Ok(ai_analysis) => {
let mut enhanced_finding = finding.clone();
enhanced_finding.description = format!(
"{}\n\nAI Analysis: {}",
enhanced_finding.description, ai_analysis.enhanced_description
);
enhanced_finding.recommendation = format!(
"{}\n\nAI Recommendation: {}",
enhanced_finding.recommendation, ai_analysis.mitigation_strategy
);
enhanced_findings.push(enhanced_finding);
ai_successes += 1;
self.ai_circuit_breaker.record_success();
}
Err(e) => {
self.ai_circuit_breaker.record_failure();
if let Some(fallback_msg) = EnhancedAIErrorHandler::handle_ai_error(
&e,
&format!("finding {}", finding.id),
) {
log::warn!(
"AI enhancement failed for finding {}: {}",
finding.id,
fallback_msg
);
}
ai_failures += 1;
if EnhancedAIErrorHandler::should_disable_ai(&e) {
log::error!("Disabling AI analysis due to: {}", e);
enhanced_findings.push(finding);
break;
}
enhanced_findings.push(finding);
}
}
} else {
enhanced_findings.push(finding);
}
}
if ai_failures > 0 || ai_successes > 0 {
log::info!(
"AI enhancement completed: {} successes, {} failures",
ai_successes,
ai_failures
);
if ai_failures > ai_successes {
println!(
"⚠️ AI analysis partially unavailable ({} failures, {} successes)",
ai_failures, ai_successes
);
println!(" Manual review is recommended for enhanced security insights");
} else if ai_failures > 0 {
println!(
"ℹ️ AI analysis completed with some failures ({} failures, {} successes)",
ai_failures, ai_successes
);
} else {
println!(
"✅ AI analysis completed successfully for {} findings",
ai_successes
);
}
}
enhanced_findings
}
fn perform_additional_security_checks(&self) -> Vec<AuditFinding> {
let mut findings = Vec::new();
let mut finding_id = 100;
if let Ok(entries) = std::fs::read_dir("src") {
for entry in entries.flatten() {
if let Some(_ext) = entry.path().extension() {
if entry.path().extension().unwrap() == "rs" {
if let Ok(content) = std::fs::read_to_string(&entry.path()) {
let file_path = entry.path().display().to_string();
if self.contains_potential_secrets(&content) {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", finding_id),
title: "Potential hardcoded secrets detected".to_string(),
description: format!("File {} may contain hardcoded secrets", file_path),
severity: AuditSeverity::High,
category: "Security".to_string(),
cwe_id: Some("CWE-798".to_string()),
cvss_score: Some(8.0),
impact: "Exposed secrets could lead to unauthorized access".to_string(),
recommendation: "Review and remove any hardcoded secrets, use environment variables or secure key management".to_string(),
code_location: Some(file_path.clone()),
references: vec!["https://cwe.mitre.org/data/definitions/798.html".to_string()],
});
finding_id += 1;
}
findings.extend(self.check_unsafe_code(
&content,
&file_path,
&mut finding_id,
));
findings.extend(self.check_unwrap_usage(
&content,
&file_path,
&mut finding_id,
));
findings.extend(self.check_command_injection(
&content,
&file_path,
&mut finding_id,
));
findings.extend(self.check_path_traversal(
&content,
&file_path,
&mut finding_id,
));
findings.extend(self.check_insecure_network(
&content,
&file_path,
&mut finding_id,
));
findings.extend(self.check_error_handling(
&content,
&file_path,
&mut finding_id,
));
findings.extend(self.check_cryptographic_issues(
&content,
&file_path,
&mut finding_id,
));
findings.extend(self.check_input_validation(
&content,
&file_path,
&mut finding_id,
));
findings.extend(self.check_solana_security(
&content,
&file_path,
&mut finding_id,
));
findings.extend(self.check_positive_security_practices(
&content,
&file_path,
&mut finding_id,
));
}
}
}
}
}
findings.extend(self.check_dependency_security(&mut finding_id));
findings.extend(self.check_configuration_security(&mut finding_id));
findings.extend(self.check_advanced_security_patterns(&mut finding_id));
findings.extend(self.check_security_best_practices(&mut finding_id));
findings.extend(self.check_security_excellence_indicators(&mut finding_id));
findings
}
fn contains_potential_secrets(&self, content: &str) -> bool {
let secret_patterns = [
r#"(?i)password\s*=\s*['"'][^'"']+['"']"#,
r#"(?i)secret\s*=\s*['"'][^'"']+['"']"#,
r#"(?i)key\s*=\s*['"'][^'"']+['"']"#,
r#"(?i)token\s*=\s*['"'][^'"']+['"']"#,
r#"(?i)api_key\s*=\s*['"'][^'"']+['"']"#,
r#"(?i)private_key\s*=\s*['"'][^'"']+['"']"#,
r#"['"'][0-9a-fA-F]{32,}['"']"#, r#"['"'][A-Za-z0-9+/]{20,}={0,2}['"']"#, ];
for pattern in &secret_patterns {
if let Some(regex) = safe_regex_new(pattern) {
if regex.is_match(content) {
return true;
}
}
}
false
}
fn check_unsafe_code(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("unsafe") {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Unsafe code block detected".to_string(),
description: format!("File {} contains unsafe code blocks that bypass Rust's memory safety guarantees", file_path),
severity: AuditSeverity::Medium,
category: "Security".to_string(),
cwe_id: Some("CWE-119".to_string()),
cvss_score: Some(5.5),
impact: "Potential memory safety violations and buffer overflows".to_string(),
recommendation: "Review unsafe code blocks carefully, ensure proper bounds checking and memory management".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html".to_string(),
"https://cwe.mitre.org/data/definitions/119.html".to_string(),
],
});
*finding_id += 1;
}
findings
}
fn check_unwrap_usage(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
let unwrap_count =
content.matches(".unwrap()").count() + content.matches(".expect(").count();
if unwrap_count > 5 {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Excessive unwrap/expect usage".to_string(),
description: format!("File {} contains {} instances of unwrap/expect which can cause panics", file_path, unwrap_count),
severity: AuditSeverity::Medium,
category: "Security".to_string(),
cwe_id: Some("CWE-248".to_string()),
cvss_score: Some(4.0),
impact: "Application crashes due to unhandled panics, potential denial of service".to_string(),
recommendation: "Replace unwrap/expect with proper error handling using match or if let patterns".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://doc.rust-lang.org/book/ch09-00-error-handling.html".to_string(),
"https://cwe.mitre.org/data/definitions/248.html".to_string(),
],
});
*finding_id += 1;
}
findings
}
fn check_command_injection(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
let command_patterns = [
r#"Command::new\([^)]*format!"#,
r#"Command::new\([^)]*\+\s*"#,
r#"std::process::Command::new\([^)]*format!"#,
r#"shell\("#,
r#"exec\("#,
r#"system\("#,
];
for pattern in &command_patterns {
if let Some(regex) = safe_regex_new(pattern) {
if regex.is_match(content) {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Potential command injection vulnerability".to_string(),
description: format!("File {} contains command execution with potentially unsafe input", file_path),
severity: AuditSeverity::High,
category: "Security".to_string(),
cwe_id: Some("CWE-78".to_string()),
cvss_score: Some(7.5),
impact: "Arbitrary command execution on the host system".to_string(),
recommendation: "Validate and sanitize all input before using in commands, use parameterized commands".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://cwe.mitre.org/data/definitions/78.html".to_string(),
"https://owasp.org/Top10/A03_2021-Injection/".to_string(),
],
});
*finding_id += 1;
break; }
}
}
findings
}
fn check_path_traversal(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
let path_patterns = [
r#"\.\./"#,
r#"\.\.\\"#,
r#"Path::new\([^)]*format!"#,
r#"PathBuf::from\([^)]*format!"#,
r#"File::open\([^)]*format!"#,
r#"std::fs::read\([^)]*format!"#,
];
for pattern in &path_patterns {
if let Some(regex) = safe_regex_new(pattern) {
if regex.is_match(content) {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Potential path traversal vulnerability".to_string(),
description: format!(
"File {} contains file operations with potentially unsafe paths",
file_path
),
severity: AuditSeverity::High,
category: "Security".to_string(),
cwe_id: Some("CWE-22".to_string()),
cvss_score: Some(7.0),
impact: "Unauthorized access to files outside intended directory".to_string(),
recommendation:
"Validate and canonicalize file paths, use safe path construction methods"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://cwe.mitre.org/data/definitions/22.html".to_string(),
"https://owasp.org/Top10/A01_2021-Broken_Access_Control/".to_string(),
],
});
*finding_id += 1;
break; }
}
}
findings
}
fn check_insecure_network(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("http://")
&& !content.contains("localhost")
&& !content.contains("127.0.0.1")
{
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Insecure HTTP usage detected".to_string(),
description: format!(
"File {} uses HTTP instead of HTTPS for network communications",
file_path
),
severity: AuditSeverity::Medium,
category: "Security".to_string(),
cwe_id: Some("CWE-319".to_string()),
cvss_score: Some(5.0),
impact: "Data transmitted in plain text, susceptible to interception".to_string(),
recommendation: "Use HTTPS for all external network communications".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://cwe.mitre.org/data/definitions/319.html".to_string(),
"https://owasp.org/Top10/A02_2021-Cryptographic_Failures/".to_string(),
],
});
*finding_id += 1;
}
let tls_bypass_patterns = [
r#"danger_accept_invalid_certs\(true\)"#,
r#"verify\(false\)"#,
r#"accept_invalid_hostnames\(true\)"#,
r#"verify_mode\(SslVerifyMode::NONE\)"#,
];
for pattern in &tls_bypass_patterns {
if let Some(regex) = safe_regex_new(pattern) {
if regex.is_match(content) {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "TLS certificate verification bypass".to_string(),
description: format!(
"File {} disables TLS certificate verification",
file_path
),
severity: AuditSeverity::High,
category: "Security".to_string(),
cwe_id: Some("CWE-295".to_string()),
cvss_score: Some(7.5),
impact: "Man-in-the-middle attacks, compromised secure communications"
.to_string(),
recommendation:
"Enable proper TLS certificate verification for all connections"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://cwe.mitre.org/data/definitions/295.html".to_string()
],
});
*finding_id += 1;
break;
}
}
}
findings
}
fn check_error_handling(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
let sensitive_patterns = [
r#"println!\([^)]*password"#,
r#"println!\([^)]*secret"#,
r#"println!\([^)]*key"#,
r#"eprintln!\([^)]*password"#,
r#"error!\([^)]*password"#,
r#"format!\([^)]*password"#,
];
for pattern in &sensitive_patterns {
if regex::Regex::new(pattern).unwrap().is_match(content) {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Sensitive information in error messages".to_string(),
description: format!(
"File {} may expose sensitive information in error messages",
file_path
),
severity: AuditSeverity::Medium,
category: "Security".to_string(),
cwe_id: Some("CWE-209".to_string()),
cvss_score: Some(4.5),
impact: "Information disclosure through error messages".to_string(),
recommendation:
"Sanitize error messages to avoid exposing sensitive information"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://cwe.mitre.org/data/definitions/209.html".to_string()],
});
*finding_id += 1;
break;
}
}
findings
}
fn check_cryptographic_issues(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
let weak_rng_patterns = [r#"thread_rng\(\)"#, r#"rand::random"#, r#"SmallRng"#];
for pattern in &weak_rng_patterns {
if regex::Regex::new(pattern).unwrap().is_match(content) {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Potentially weak random number generation".to_string(),
description: format!("File {} uses random number generation that may not be cryptographically secure", file_path),
severity: AuditSeverity::Medium,
category: "Security".to_string(),
cwe_id: Some("CWE-338".to_string()),
cvss_score: Some(5.0),
impact: "Predictable random values could compromise cryptographic operations".to_string(),
recommendation: "Use cryptographically secure random number generators for security-sensitive operations".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://cwe.mitre.org/data/definitions/338.html".to_string(),
],
});
*finding_id += 1;
break;
}
}
if content.contains("vec![0") || content.contains("&[0") {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Potential hardcoded initialization vector or salt".to_string(),
description: format!(
"File {} may contain hardcoded cryptographic parameters",
file_path
),
severity: AuditSeverity::Medium,
category: "Security".to_string(),
cwe_id: Some("CWE-330".to_string()),
cvss_score: Some(4.5),
impact: "Weak cryptographic operations due to predictable parameters".to_string(),
recommendation:
"Generate random initialization vectors and salts for each operation"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://cwe.mitre.org/data/definitions/330.html".to_string()],
});
*finding_id += 1;
}
findings
}
fn check_input_validation(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
let parsing_patterns = [
r#"\.parse\(\)\.unwrap\(\)"#,
r#"\.parse\(\)\.expect\("#,
r#"from_str\([^)]*\)\.unwrap\(\)"#,
r#"serde_json::from_str\([^)]*\)\.unwrap\(\)"#,
];
for pattern in &parsing_patterns {
if regex::Regex::new(pattern).unwrap().is_match(content) {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Unsafe input parsing without validation".to_string(),
description: format!(
"File {} parses input without proper validation or error handling",
file_path
),
severity: AuditSeverity::Medium,
category: "Security".to_string(),
cwe_id: Some("CWE-20".to_string()),
cvss_score: Some(5.0),
impact: "Application crashes or unexpected behavior from malformed input"
.to_string(),
recommendation: "Validate all input and handle parsing errors gracefully"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://cwe.mitre.org/data/definitions/20.html".to_string()],
});
*finding_id += 1;
break;
}
}
findings
}
fn check_solana_security(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
findings.extend(self.check_solana_program_security(content, file_path, finding_id));
findings.extend(self.check_solana_network_security(content, file_path, finding_id));
findings.extend(self.check_solana_account_validation(content, file_path, finding_id));
findings.extend(self.check_solana_antipatterns(content, file_path, finding_id));
findings.extend(self.check_solana_crypto_security(content, file_path, finding_id));
findings.extend(self.check_solana_cpi_security(content, file_path, finding_id));
findings.extend(self.check_solana_token_security(content, file_path, finding_id));
findings.extend(self.check_solana_anchor_security(content, file_path, finding_id));
findings.extend(self.check_solana_runtime_security(content, file_path, finding_id));
findings.extend(self.check_solana_pda_advanced_security(content, file_path, finding_id));
findings.extend(self.check_solana_transaction_security(content, file_path, finding_id));
findings.extend(self.check_solana_oracle_security(content, file_path, finding_id));
findings.extend(self.check_solana_governance_security(content, file_path, finding_id));
findings.extend(self.check_solana_deployment_security(content, file_path, finding_id));
findings.extend(self.check_account_data_matching(content, file_path, finding_id));
findings.extend(self.check_account_data_reallocation(content, file_path, finding_id));
findings.extend(self.check_account_reloading(content, file_path, finding_id));
findings.extend(self.check_arbitrary_cpi(content, file_path, finding_id));
findings.extend(self.check_authority_transfer(content, file_path, finding_id));
findings.extend(self.check_bump_seed_canonicalization(content, file_path, finding_id));
findings.extend(self.check_closing_accounts_enhanced(content, file_path, finding_id));
findings.extend(self.check_duplicate_mutable_accounts(content, file_path, finding_id));
findings.extend(self.check_frontrunning_vulnerabilities(content, file_path, finding_id));
findings.extend(self.check_insecure_initialization(content, file_path, finding_id));
findings.extend(self.check_precision_loss(content, file_path, finding_id));
findings.extend(self.check_overflow_underflow_enhanced(content, file_path, finding_id));
findings.extend(self.check_pda_sharing_vulnerabilities(content, file_path, finding_id));
findings
.extend(self.check_remaining_accounts_vulnerabilities(content, file_path, finding_id));
findings.extend(self.check_rust_specific_errors(content, file_path, finding_id));
findings.extend(self.check_type_cosplay_vulnerabilities(content, file_path, finding_id));
findings.extend(self.check_financial_math_precision(content, file_path, finding_id));
findings
}
fn check_solana_program_security(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
let signer_patterns = [
r#"AccountInfo.*without.*is_signer"#,
r#"AccountMeta::new\("#,
r#"instruction.*without.*signer.*check"#,
];
for pattern in &signer_patterns {
if regex::Regex::new(pattern).unwrap().is_match(content) {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Missing signer validation in Solana program".to_string(),
description: format!(
"File {} may lack proper signer validation for sensitive operations",
file_path
),
severity: AuditSeverity::Critical,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-862".to_string()),
cvss_score: Some(9.0),
impact: "Unauthorized users could execute privileged operations".to_string(),
recommendation:
"Always validate that required accounts are signers using is_signer checks"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string(),
"https://solana.com/developers/guides/getstarted/intro-to-anchor"
.to_string(),
],
});
*finding_id += 1;
break;
}
}
if (content.contains("find_program_address") || content.contains("create_program_address"))
&& !content.contains("assert_eq!")
&& !content.contains("require!")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Potential PDA verification bypass".to_string(),
description: format!(
"File {} uses PDA operations without proper verification",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-345".to_string()),
cvss_score: Some(8.0),
impact: "Attackers could provide arbitrary accounts instead of valid PDAs"
.to_string(),
recommendation:
"Always verify PDA derivation matches expected seeds and program ID"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://solanacookbook.com/references/programs.html#how-to-create-a-pda"
.to_string(),
],
});
*finding_id += 1;
}
if content.contains("AccountInfo") && !content.contains("owner") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Missing account ownership validation".to_string(),
description: format!(
"File {} handles accounts without verifying ownership",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-284".to_string()),
cvss_score: Some(7.5),
impact: "Programs could operate on accounts owned by malicious programs"
.to_string(),
recommendation: "Always verify account ownership before performing operations"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
if content.contains("create_account") && !content.contains("rent.minimum_balance") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Missing rent exemption check".to_string(),
description: format!(
"File {} creates accounts without ensuring rent exemption",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-400".to_string()),
cvss_score: Some(4.0),
impact: "Accounts could be closed due to insufficient rent, causing data loss"
.to_string(),
recommendation: "Ensure accounts are rent-exempt when creating them".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://docs.solana.com/developing/programming-model/accounts#rent"
.to_string(),
],
});
*finding_id += 1;
}
if content.contains("instruction")
&& !content.contains("nonce")
&& !content.contains("timestamp")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Potential instruction replay vulnerability".to_string(),
description: format!(
"File {} may be vulnerable to instruction replay attacks",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-294".to_string()),
cvss_score: Some(5.5),
impact: "Attackers could replay previous instructions to manipulate program state"
.to_string(),
recommendation: "Implement nonce or timestamp-based replay protection".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
findings
}
fn check_solana_network_security(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
let insecure_rpc_patterns = [
r#"http://.*solana"#,
r#"mainnet-beta\.solana\.com"#,
r#"devnet\.solana\.com"#,
r#"testnet\.solana\.com"#,
];
for pattern in &insecure_rpc_patterns {
if regex::Regex::new(pattern).unwrap().is_match(content) {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Insecure Solana RPC endpoint usage".to_string(),
description: format!(
"File {} uses insecure or public RPC endpoints",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-319".to_string()),
cvss_score: Some(5.0),
impact: "Rate limiting, censorship, or man-in-the-middle attacks on RPC calls"
.to_string(),
recommendation:
"Use HTTPS RPC endpoints and consider private/dedicated RPC providers"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://docs.solana.com/cluster/rpc-endpoints".to_string()],
});
*finding_id += 1;
break;
}
}
let program_id_patterns = [
r#"11111111111111111111111111111111"#,
r#"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"#,
r#"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"#,
];
for pattern in &program_id_patterns {
if content.contains(pattern) {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Hardcoded Solana program ID detected".to_string(),
description: format!("File {} contains hardcoded Solana program IDs", file_path),
severity: AuditSeverity::Low,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-798".to_string()),
cvss_score: Some(3.0),
impact: "Reduced flexibility and potential issues when deploying to different clusters".to_string(),
recommendation: "Use configuration files or environment variables for program IDs".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://docs.solana.com/developing/programming-model/accounts".to_string(),
],
});
*finding_id += 1;
break;
}
}
if content.contains("swap") || content.contains("dex") {
if !content.contains("slippage") && !content.contains("deadline") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Missing MEV protection in trading operations".to_string(),
description: format!(
"File {} performs trading operations without MEV protection",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-841".to_string()),
cvss_score: Some(4.5),
impact: "Transactions vulnerable to front-running and sandwich attacks"
.to_string(),
recommendation: "Implement slippage protection and transaction deadlines"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://docs.solana.com/developing/programming-model/transactions"
.to_string(),
],
});
*finding_id += 1;
}
}
findings
}
fn check_solana_account_validation(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("data.len()") || content.contains("account.data") {
if !content.contains("expected_size") && !content.contains("minimum_size") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Missing account size validation".to_string(),
description: format!(
"File {} accesses account data without size validation",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-119".to_string()),
cvss_score: Some(7.0),
impact: "Buffer overflow or underflow when accessing account data".to_string(),
recommendation: "Always validate account data size before access".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
}
if content.contains("close") && content.contains("account") {
if !content.contains("assert") && !content.contains("require") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Unsafe account closing operation".to_string(),
description: format!(
"File {} closes accounts without proper validation",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-404".to_string()),
cvss_score: Some(6.5),
impact: "Accounts could be closed prematurely, causing loss of funds or data"
.to_string(),
recommendation: "Validate account state and ownership before closing"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
}
let sysvar_patterns = [
r#"SysvarC1ock11111111111111111111111111111111"#,
r#"SysvarRent111111111111111111111111111111111"#,
r#"SysvarS1otHashes111111111111111111111111111"#,
];
for pattern in &sysvar_patterns {
if content.contains(pattern) && !content.contains("sysvar::") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Direct sysvar account access".to_string(),
description: format!("File {} accesses sysvar accounts directly", file_path),
severity: AuditSeverity::Low,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-668".to_string()),
cvss_score: Some(2.5),
impact: "Potential inconsistency or incorrect sysvar data access".to_string(),
recommendation: "Use the sysvar crate for safe sysvar access".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://docs.solana.com/developing/runtime-facilities/sysvars".to_string(),
],
});
*finding_id += 1;
break;
}
}
findings
}
fn check_solana_antipatterns(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("#[derive(Accounts)]") {
if !content.contains("#[account(") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Missing Anchor account constraints".to_string(),
description: format!(
"File {} uses Anchor without proper account constraints",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-20".to_string()),
cvss_score: Some(5.0),
impact: "Insufficient validation of account inputs".to_string(),
recommendation:
"Use Anchor constraints like #[account(mut)], #[account(init)], etc."
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_references/account-constraints.html"
.to_string(),
],
});
*finding_id += 1;
}
}
if content.contains("ProgramError") || content.contains("anchor_lang::error") {
if !content.contains("Result<") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Improper Solana error handling".to_string(),
description: format!(
"File {} handles Solana errors without proper Result types",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-754".to_string()),
cvss_score: Some(4.0),
impact: "Program crashes or unexpected behavior on error conditions"
.to_string(),
recommendation: "Use Result<T, E> types for proper error propagation"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/errors.html".to_string()
],
});
*finding_id += 1;
}
}
if content.contains("Program") && content.contains("State") {
if !content.contains("validate") && !content.contains("check") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Missing program state validation".to_string(),
description: format!(
"File {} modifies program state without validation",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-20".to_string()),
cvss_score: Some(6.0),
impact: "Program state could be corrupted or manipulated".to_string(),
recommendation: "Implement proper state validation before modifications"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
}
findings
}
fn check_solana_crypto_security(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("Keypair::new()") && !content.contains("random") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Potentially weak Solana keypair generation".to_string(),
description: format!(
"File {} generates keypairs without explicit randomness",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-330".to_string()),
cvss_score: Some(5.5),
impact: "Predictable keypairs could be exploited by attackers".to_string(),
recommendation: "Ensure secure random number generation for keypair creation"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://docs.solana.com/developing/clients/rust-api#keypair".to_string(),
],
});
*finding_id += 1;
}
if content.contains("verify") && content.contains("signature") {
if content.contains("false") || content.contains("skip") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Signature verification bypass detected".to_string(),
description: format!("File {} may bypass signature verification", file_path),
severity: AuditSeverity::Critical,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-347".to_string()),
cvss_score: Some(9.5),
impact: "Unauthorized transactions could be executed without proper signatures".to_string(),
recommendation: "Never bypass signature verification in production code".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://docs.solana.com/developing/programming-model/transactions#signatures".to_string(),
],
});
*finding_id += 1;
}
}
if content.contains("spl_token") || content.contains("Token") {
if !content.contains("mint") && !content.contains("authority") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "SPL token operations without authority checks".to_string(),
description: format!(
"File {} performs SPL token operations without authority validation",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-862".to_string()),
cvss_score: Some(8.0),
impact: "Unauthorized token operations could lead to fund theft".to_string(),
recommendation: "Always verify token authorities before performing operations"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://spl.solana.com/token".to_string()],
});
*finding_id += 1;
}
}
findings
}
fn check_solana_cpi_security(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("invoke") || content.contains("invoke_signed") {
if !content.contains("assert!") && !content.contains("require!") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Unsafe Cross Program Invocation (CPI) detected".to_string(),
description: format!(
"File {} performs CPI without proper validation",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-20".to_string()),
cvss_score: Some(7.5),
impact: "Malicious programs could be invoked, leading to privilege escalation"
.to_string(),
recommendation:
"Validate target program IDs and account ownership before CPI calls"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://solanacookbook.com/references/programs.html#how-to-make-cpi"
.to_string(),
],
});
*finding_id += 1;
}
}
if content.contains("invoke_signed") && content.contains("is_signer") {
if content.contains("false") || content.contains("0") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "CPI account privilege escalation risk".to_string(),
description: format!(
"File {} may escalate account privileges through CPI",
file_path
),
severity: AuditSeverity::Critical,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-269".to_string()),
cvss_score: Some(9.0),
impact: "Accounts could gain unauthorized signer privileges through CPI"
.to_string(),
recommendation:
"Never promote non-signer accounts to signer status in CPI calls"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://github.com/coral-xyz/sealevel-attacks".to_string()],
});
*finding_id += 1;
}
}
if content.contains("invoke") && content.contains("account.reload()").eq(&false) {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Missing account reload after CPI".to_string(),
description: format!("File {} doesn't reload accounts after CPI calls", file_path),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-362".to_string()),
cvss_score: Some(5.0),
impact: "Stale account data could lead to incorrect program logic".to_string(),
recommendation: "Reload account data after CPI calls that modify accounts"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
findings
}
fn check_solana_token_security(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("mint_to") || content.contains("burn") {
if !content.contains("mint_authority") && !content.contains("freeze_authority") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Missing token mint authority validation".to_string(),
description: format!(
"File {} performs token mint/burn without authority checks",
file_path
),
severity: AuditSeverity::Critical,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-862".to_string()),
cvss_score: Some(9.5),
impact: "Unauthorized token minting or burning could occur".to_string(),
recommendation:
"Always validate mint and freeze authorities before token operations"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://spl.solana.com/token".to_string()],
});
*finding_id += 1;
}
}
if content.contains("decimals") && content.contains("amount") {
if !content.contains("checked_mul") && !content.contains("checked_div") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Unsafe token decimal calculations".to_string(),
description: format!(
"File {} performs token decimal calculations without overflow checks",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-190".to_string()),
cvss_score: Some(7.0),
impact: "Integer overflow in token amount calculations".to_string(),
recommendation:
"Use checked arithmetic operations for token amount calculations"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://spl.solana.com/token".to_string()],
});
*finding_id += 1;
}
}
if content.contains("associated_token") || content.contains("get_associated_token_address")
{
if !content.contains("owner") || !content.contains("mint") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Insufficient associated token account validation".to_string(),
description: format!(
"File {} uses associated token accounts without proper validation",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-345".to_string()),
cvss_score: Some(7.5),
impact: "Wrong token accounts could be used, leading to fund loss".to_string(),
recommendation: "Validate associated token account owner and mint before use"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://spl.solana.com/associated-token-account".to_string()],
});
*finding_id += 1;
}
}
if content.contains("token_extension") || content.contains("Token2022") {
if !content.contains("extension_type") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Token extension security not validated".to_string(),
description: format!(
"File {} uses token extensions without proper validation",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-20".to_string()),
cvss_score: Some(6.0),
impact: "Token extension features could be bypassed or misused".to_string(),
recommendation: "Validate token extension types and configurations".to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://spl.solana.com/token-2022".to_string()],
});
*finding_id += 1;
}
}
findings
}
fn check_solana_anchor_security(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("#[derive(Accounts)]") && content.contains("discriminator") {
if !content.contains("check") && !content.contains("assert") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Unsafe account discriminator handling".to_string(),
description: format!(
"File {} handles discriminators without proper validation",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-20".to_string()),
cvss_score: Some(7.0),
impact: "Wrong account types could be accepted, leading to type confusion"
.to_string(),
recommendation: "Always validate account discriminators match expected types"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
}
if content.contains("emit!") {
if content.contains("private") || content.contains("secret") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Sensitive data in event emission".to_string(),
description: format!("File {} may emit sensitive data in events", file_path),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-532".to_string()),
cvss_score: Some(5.0),
impact: "Sensitive information could be exposed in transaction logs"
.to_string(),
recommendation: "Avoid emitting sensitive data in events, logs are public"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_references/events.html".to_string()
],
});
*finding_id += 1;
}
}
if content.contains("#[account(") {
let constraint_patterns = ["init", "mut", "close", "has_one", "constraint"];
let mut has_constraints = false;
for pattern in &constraint_patterns {
if content.contains(pattern) {
has_constraints = true;
break;
}
}
if !has_constraints {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Missing Anchor account constraints".to_string(),
description: format!(
"File {} uses #[account()] without proper constraints",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-20".to_string()),
cvss_score: Some(5.5),
impact: "Insufficient account validation could allow unauthorized access"
.to_string(),
recommendation: "Use appropriate Anchor constraints (init, mut, has_one, etc.)"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_references/account-constraints.html"
.to_string(),
],
});
*finding_id += 1;
}
}
if content.contains("upgrade") && content.contains("program") {
if !content.contains("upgrade_authority") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Missing program upgrade authority validation".to_string(),
description: format!(
"File {} handles program upgrades without authority checks",
file_path
),
severity: AuditSeverity::Critical,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-862".to_string()),
cvss_score: Some(9.0),
impact: "Unauthorized program upgrades could compromise the entire system"
.to_string(),
recommendation:
"Always validate upgrade authority before allowing program changes"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://docs.solana.com/developing/deploying".to_string()],
});
*finding_id += 1;
}
}
findings
}
fn check_solana_runtime_security(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("loop") || content.contains("while") {
if !content.contains("compute_budget") && !content.contains("break") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Potential compute unit exhaustion".to_string(),
description: format!("File {} contains unbounded loops without compute limits", file_path),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-834".to_string()),
cvss_score: Some(7.0),
impact: "Programs could exceed compute limits and fail or be exploited for DoS".to_string(),
recommendation: "Implement bounded loops and consider compute budget instructions".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://docs.solana.com/developing/programming-model/runtime#compute-budget".to_string(),
],
});
*finding_id += 1;
}
}
if content.contains("recursive") || content.contains("fn ") && content.contains("self") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Potential stack overflow in recursive function".to_string(),
description: format!(
"File {} contains recursive patterns that could cause stack overflow",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-674".to_string()),
cvss_score: Some(5.5),
impact: "Stack overflow could cause program termination or unpredictable behavior"
.to_string(),
recommendation: "Limit recursion depth or use iterative approaches".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://docs.solana.com/developing/programming-model/runtime#stack-frame"
.to_string(),
],
});
*finding_id += 1;
}
if content.contains("instruction") && content.len() > 10000 {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Potentially oversized instruction".to_string(),
description: format!("File {} contains very large instruction implementations", file_path),
severity: AuditSeverity::Low,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-400".to_string()),
cvss_score: Some(3.0),
impact: "Large instructions may hit size limits or consume excessive compute".to_string(),
recommendation: "Break down large instructions into smaller, focused operations".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://docs.solana.com/developing/programming-model/runtime#instruction-processing".to_string(),
],
});
*finding_id += 1;
}
if content.contains("Vec::new") || content.contains("HashMap::new") {
if !content.contains("with_capacity") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Inefficient heap allocation pattern".to_string(),
description: format!(
"File {} uses dynamic allocation without capacity hints",
file_path
),
severity: AuditSeverity::Low,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-400".to_string()),
cvss_score: Some(2.5),
impact: "Inefficient memory usage could lead to performance degradation"
.to_string(),
recommendation: "Pre-allocate collections with known capacity when possible"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://docs.solana.com/developing/programming-model/runtime#heap-size"
.to_string(),
],
});
*finding_id += 1;
}
}
findings
}
fn check_solana_pda_advanced_security(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("find_program_address") {
if !content.contains("bump") || !content.contains("canonical") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "PDA collision vulnerability".to_string(),
description: format!(
"File {} doesn't use canonical bumps for PDA generation",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-345".to_string()),
cvss_score: Some(6.0),
impact: "Non-canonical PDAs could lead to account collisions".to_string(),
recommendation: "Always use canonical bump seeds for PDA generation"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://solanacookbook.com/references/programs.html#canonical-bump"
.to_string(),
],
});
*finding_id += 1;
}
}
if content.contains("seeds") {
if !content.contains("validate") && !content.contains("check") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Insufficient PDA seed validation".to_string(),
description: format!("File {} uses PDA seeds without proper validation", file_path),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-20".to_string()),
cvss_score: Some(7.5),
impact: "Malicious seeds could generate unauthorized PDAs".to_string(),
recommendation: "Validate all PDA seeds against expected values and constraints".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string(),
],
});
*finding_id += 1;
}
}
if content.contains("seeds") && (content.contains("user") || content.contains("mint")) {
if content.contains("0") || content.contains("1") || content.contains("static") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Low entropy PDA seeds detected".to_string(),
description: format!(
"File {} uses predictable seeds for PDA generation",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-330".to_string()),
cvss_score: Some(5.0),
impact: "Predictable PDAs could be precomputed by attackers".to_string(),
recommendation: "Include user-specific or high-entropy data in PDA seeds"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://solanacookbook.com/references/programs.html#pda-seeds".to_string(),
],
});
*finding_id += 1;
}
}
findings
}
fn check_solana_transaction_security(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("VersionedTransaction") || content.contains("v0") {
if !content.contains("lookup_table") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Versioned transaction without lookup table validation".to_string(),
description: format!("File {} uses versioned transactions without proper lookup table checks", file_path),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-20".to_string()),
cvss_score: Some(5.5),
impact: "Malicious lookup tables could redirect account references".to_string(),
recommendation: "Validate lookup table contents and ownership".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://docs.solana.com/developing/lookup-tables".to_string(),
],
});
*finding_id += 1;
}
}
if content.contains("Transaction") && content.contains("instructions") {
if !content.contains("MAX_TRANSACTION_SIZE") && !content.contains("size") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Transaction size limits not validated".to_string(),
description: format!("File {} builds transactions without size validation", file_path),
severity: AuditSeverity::Low,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-400".to_string()),
cvss_score: Some(3.5),
impact: "Oversized transactions could fail or be rejected".to_string(),
recommendation: "Validate transaction size against network limits".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://docs.solana.com/developing/programming-model/transactions#transaction-size".to_string(),
],
});
*finding_id += 1;
}
}
if content.contains("priority_fee") || content.contains("compute_budget") {
if !content.contains("max_fee") && !content.contains("limit") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Unlimited priority fee vulnerability".to_string(),
description: format!("File {} sets priority fees without limits", file_path),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-400".to_string()),
cvss_score: Some(4.5),
impact: "Excessive priority fees could drain user funds".to_string(),
recommendation: "Implement maximum priority fee limits and user confirmation".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://docs.solana.com/developing/programming-model/runtime#prioritization-fees".to_string(),
],
});
*finding_id += 1;
}
}
findings
}
fn check_solana_oracle_security(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("price") && (content.contains("oracle") || content.contains("feed")) {
if !content.contains("twap")
&& !content.contains("median")
&& !content.contains("validate")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Oracle price manipulation vulnerability".to_string(),
description: format!("File {} uses oracle prices without manipulation protection", file_path),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-20".to_string()),
cvss_score: Some(8.0),
impact: "Price manipulation could lead to arbitrage attacks or fund theft".to_string(),
recommendation: "Use TWAP, multiple oracles, or price validation mechanisms".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices".to_string(),
],
});
*finding_id += 1;
}
}
if content.contains("oracle") || content.contains("price_feed") {
if !content.contains("timestamp") && !content.contains("staleness") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Stale oracle data vulnerability".to_string(),
description: format!(
"File {} uses oracle data without staleness checks",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-672".to_string()),
cvss_score: Some(6.0),
impact: "Stale price data could lead to incorrect valuations".to_string(),
recommendation: "Implement staleness checks based on timestamp validation"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://docs.switchboard.xyz/solana/feeds".to_string()],
});
*finding_id += 1;
}
}
if content.contains("oracle")
&& !content.contains("authority")
&& !content.contains("source")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Oracle source not validated".to_string(),
description: format!(
"File {} uses oracle data without source validation",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-346".to_string()),
cvss_score: Some(5.5),
impact: "Malicious or unauthorized oracle sources could provide false data"
.to_string(),
recommendation: "Validate oracle authority and data source authenticity"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://docs.pyth.network/documentation/pythnet-price-feeds/publisher-keys"
.to_string(),
],
});
*finding_id += 1;
}
findings
}
fn check_solana_governance_security(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("vote") || content.contains("proposal") {
if !content.contains("voting_power") && !content.contains("threshold") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Governance voting security not validated".to_string(),
description: format!(
"File {} implements voting without proper power validation",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-284".to_string()),
cvss_score: Some(7.5),
impact: "Unauthorized votes or vote manipulation could compromise governance"
.to_string(),
recommendation: "Implement voting power validation and threshold checks"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://github.com/solana-labs/governance-ui".to_string()],
});
*finding_id += 1;
}
}
if content.contains("timelock") || content.contains("delay") {
if content.contains("skip") || content.contains("bypass") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Timelock bypass vulnerability detected".to_string(),
description: format!("File {} may allow timelock bypass", file_path),
severity: AuditSeverity::Critical,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-863".to_string()),
cvss_score: Some(9.0),
impact: "Critical operations could bypass intended delays".to_string(),
recommendation: "Remove timelock bypass mechanisms and enforce all delays"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://github.com/solana-labs/governance".to_string()],
});
*finding_id += 1;
}
}
if content.contains("execute") && content.contains("proposal") {
if !content.contains("executed") && !content.contains("state") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Proposal double execution vulnerability".to_string(),
description: format!("File {} may allow proposal double execution", file_path),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-362".to_string()),
cvss_score: Some(7.0),
impact: "Proposals could be executed multiple times".to_string(),
recommendation: "Track proposal execution state to prevent double execution"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://github.com/solana-labs/governance".to_string()],
});
*finding_id += 1;
}
}
findings
}
fn check_solana_deployment_security(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("BPFLoader") || content.contains("deploy") {
if !content.contains("immutable") && !content.contains("upgrade_authority") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Program immutability not verified".to_string(),
description: format!(
"File {} deploys programs without immutability checks",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-668".to_string()),
cvss_score: Some(5.0),
impact: "Programs could be unexpectedly upgraded or modified".to_string(),
recommendation: "Verify program immutability status or upgrade authority"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://docs.solana.com/developing/deploying#immutable-programs"
.to_string(),
],
});
*finding_id += 1;
}
}
if content.contains("verify") && content.contains("false") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Program verification bypass detected".to_string(),
description: format!("File {} bypasses program verification", file_path),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-347".to_string()),
cvss_score: Some(7.5),
impact: "Unverified programs could contain malicious code".to_string(),
recommendation: "Always enable program verification in production".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://docs.solana.com/developing/on-chain-programs/examples#program-verification".to_string(),
],
});
*finding_id += 1;
}
if content.contains("load") && content.contains("program") {
if !content.contains("safety") && !content.contains("verify") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Unsafe program loading detected".to_string(),
description: format!("File {} loads programs without safety checks", file_path),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-20".to_string()),
cvss_score: Some(6.0),
impact: "Malicious programs could be loaded and executed".to_string(),
recommendation: "Implement program safety validation before loading".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://docs.solana.com/developing/programming-model/runtime#program-loading".to_string(),
],
});
*finding_id += 1;
}
}
findings
}
fn check_account_data_matching(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("ctx.accounts")
&& !content.contains("key()")
&& !content.contains("has_one")
&& !content.contains("constraint")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Missing account key validation".to_string(),
description: format!(
"File {} accesses accounts without validating keys match expected values",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-345".to_string()),
cvss_score: Some(7.5),
impact: "Malicious accounts could be processed instead of expected accounts".to_string(),
recommendation: "Use ctx.accounts.account.key() checks or Anchor's has_one/constraint attributes".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string(),
],
});
*finding_id += 1;
}
if (content.contains("config_data") || content.contains("admin_settings"))
&& !content.contains("admin.key()")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Config data modification without admin validation".to_string(),
description: format!(
"File {} modifies config data without validating admin authority",
file_path
),
severity: AuditSeverity::Critical,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-862".to_string()),
cvss_score: Some(9.0),
impact: "Unauthorized users could modify critical configuration data".to_string(),
recommendation:
"Validate admin.key() matches config_data.admin before modifications"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
findings
}
fn check_account_data_reallocation(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("realloc") && content.contains("false") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Unsafe account reallocation".to_string(),
description: format!(
"File {} uses realloc with zero_init=false, potentially exposing stale data",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-200".to_string()),
cvss_score: Some(7.0),
impact: "Stale data could be exposed when account size decreases".to_string(),
recommendation: "Use realloc with zero_init=true when decreasing account size"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
if content.contains("realloc")
&& !content.contains("required_data_len")
&& !content.contains("size")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Realloc without proper size calculation".to_string(),
description: format!(
"File {} uses realloc without calculating required data length",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-682".to_string()),
cvss_score: Some(5.5),
impact: "Inefficient compute unit usage or incorrect memory allocation".to_string(),
recommendation: "Calculate required_data_len before realloc operations".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
findings
}
fn check_account_reloading(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("cpi::")
&& content.contains("ctx.accounts")
&& !content.contains("reload()")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Missing account reload after CPI".to_string(),
description: format!(
"File {} performs CPI but doesn't reload accounts, potentially using stale data",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-362".to_string()),
cvss_score: Some(7.0),
impact: "Using stale account data after CPI can lead to incorrect program behavior".to_string(),
recommendation: "Use ctx.accounts.account.reload() after CPI calls".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string(),
],
});
*finding_id += 1;
}
if content.contains("rewards")
&& content.contains("staking_account")
&& content.contains("msg!")
&& !content.contains("reload")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Staking rewards accessed without account reload".to_string(),
description: format!(
"File {} accesses staking rewards after potential CPI without reload",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-362".to_string()),
cvss_score: Some(7.5),
impact: "Incorrect reward calculations due to stale account data".to_string(),
recommendation: "Reload staking_account after reward distribution CPI".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
findings
}
fn check_arbitrary_cpi(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("invoke(")
&& !content.contains("program.key()")
&& !content.contains("::ID")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Arbitrary CPI without program verification".to_string(),
description: format!(
"File {} performs CPI without verifying target program identity",
file_path
),
severity: AuditSeverity::Critical,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-345".to_string()),
cvss_score: Some(9.0),
impact: "Malicious programs could be invoked, compromising security".to_string(),
recommendation:
"Verify target program ID before CPI: if program.key() != &expected_program::ID"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
if content.contains("ledger_program") && !content.contains("IncorrectProgramId") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Ledger program CPI without verification".to_string(),
description: format!(
"File {} uses ledger program CPI without program ID verification",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-345".to_string()),
cvss_score: Some(8.0),
impact: "Malicious ledger program could be invoked".to_string(),
recommendation: "Verify ledger_program.key() == &custom_ledger_program::ID"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
findings
}
fn check_authority_transfer(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("authority")
&& content.contains("=")
&& !content.contains("pending_authority")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Direct authority transfer without nomination".to_string(),
description: format!(
"File {} transfers authority directly without two-step nominate-accept process",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-266".to_string()),
cvss_score: Some(7.5),
impact: "Authority could be transferred to wrong address, causing lockout"
.to_string(),
recommendation: "Implement two-step authority transfer: nominate then accept"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
if content.contains("require_keys_eq!")
&& content.contains("current_admin")
&& !content.contains("new_authority")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Authority verification without acceptance step".to_string(),
description: format!(
"File {} verifies current admin but lacks new authority acceptance",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-284".to_string()),
cvss_score: Some(6.0),
impact: "Incomplete authority transfer process".to_string(),
recommendation: "Implement pending_authority nomination and acceptance verification".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string(),
],
});
*finding_id += 1;
}
findings
}
fn check_bump_seed_canonicalization(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("create_program_address") && !content.contains("find_program_address") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Non-canonical PDA creation".to_string(),
description: format!(
"File {} uses create_program_address instead of find_program_address",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-345".to_string()),
cvss_score: Some(7.5),
impact: "Non-canonical bump seeds allow PDA manipulation attacks".to_string(),
recommendation: "Use find_program_address for canonical bump generation"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
if content.contains("find_program_address")
&& content.contains("bump")
&& !content.contains("profile_pda.bump")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Bump seed not stored in account".to_string(),
description: format!(
"File {} generates bump but doesn't store it in account for validation",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-345".to_string()),
cvss_score: Some(5.5),
impact: "PDA validation may be incomplete without stored bump".to_string(),
recommendation: "Store bump in account: profile_pda.bump = bump".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
findings
}
fn check_closing_accounts_enhanced(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("**account.lamports.borrow_mut() = 0")
&& !content.contains("CLOSED_ACCOUNT_DISCRIMINATOR")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Incomplete account closure".to_string(),
description: format!(
"File {} only zeros lamports without proper data cleanup",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-404".to_string()),
cvss_score: Some(7.0),
impact: "Account data remains accessible, enabling reinitialization attacks"
.to_string(),
recommendation:
"Zero data, set CLOSED_ACCOUNT_DISCRIMINATOR, then transfer lamports"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
if content.contains("close")
&& content.contains("account")
&& !content.contains("#[account(close = destination)]")
&& !content.contains("try_borrow_mut_data")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Manual account closure without Anchor helper".to_string(),
description: format!(
"File {} manually closes accounts instead of using Anchor's close attribute",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-404".to_string()),
cvss_score: Some(5.5),
impact: "Manual closure may miss security steps".to_string(),
recommendation: "Use #[account(close = destination)] for safe account closure"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
findings
}
fn check_duplicate_mutable_accounts(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("reward_account.balance")
&& content.contains("bonus_account.balance")
&& !content.contains("reward_account.key() == bonus_account.key()")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Missing duplicate account check".to_string(),
description: format!(
"File {} operates on multiple accounts without checking for duplicates",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-841".to_string()),
cvss_score: Some(7.0),
impact: "Same account passed multiple times could cause double spending"
.to_string(),
recommendation:
"Check account distinctness: reward_account.key() != bonus_account.key()"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
if content.contains("#[derive(Accounts)]")
&& content.contains("mut")
&& !content.contains("constraint")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Missing constraints for mutable accounts".to_string(),
description: format!(
"File {} has mutable accounts without distinctness constraints",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-841".to_string()),
cvss_score: Some(6.0),
impact: "Duplicate mutable accounts could cause unintended state changes"
.to_string(),
recommendation: "Add #[account(constraint = account1.key() != account2.key())]"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
findings
}
fn check_frontrunning_vulnerabilities(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("purchase")
&& !content.contains("expected_price")
&& !content.contains("assert!")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Purchase operation vulnerable to frontrunning".to_string(),
description: format!(
"File {} performs purchase without price validation against expected value",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-362".to_string()),
cvss_score: Some(7.5),
impact: "Transaction could be frontrun with price manipulation".to_string(),
recommendation:
"Include expected price validation: assert!(sale_price <= expected_price)"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
if (content.contains("swap") || content.contains("dex"))
&& !content.contains("slippage")
&& !content.contains("min_out")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Swap operation without slippage protection".to_string(),
description: format!(
"File {} performs swaps without slippage or minimum output protection",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-362".to_string()),
cvss_score: Some(7.0),
impact: "Swaps vulnerable to frontrunning and sandwich attacks".to_string(),
recommendation: "Implement slippage protection and minimum output validation"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
findings
}
fn check_insecure_initialization(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("central_state.authority")
&& !content.contains("upgrade_authority_address")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Initialization without upgrade authority validation".to_string(),
description: format!(
"File {} initializes state without verifying upgrade authority",
file_path
),
severity: AuditSeverity::Critical,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-862".to_string()),
cvss_score: Some(9.0),
impact: "Unauthorized initialization could compromise the entire program"
.to_string(),
recommendation: "Restrict initialization to program upgrade authority".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
if content.contains("program_data") && !content.contains("constraint") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Program data used without validation".to_string(),
description: format!(
"File {} uses program_data without proper constraint validation",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-345".to_string()),
cvss_score: Some(7.5),
impact: "Invalid program data could be used for initialization".to_string(),
recommendation:
"Add constraint validation for program_data.upgrade_authority_address"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
findings
}
fn check_precision_loss(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("/ c")
&& content.contains("* b")
&& content
.lines()
.any(|line| line.contains("/ c") && line.contains("* b"))
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Multiplication after division causing precision loss".to_string(),
description: format!(
"File {} performs division before multiplication, causing precision loss",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-682".to_string()),
cvss_score: Some(7.0),
impact: "Precision loss in calculations could lead to incorrect token amounts"
.to_string(),
recommendation: "Perform multiplication before division: (a * b) / c".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
if content.contains("saturating_mul")
|| content.contains("saturating_add")
|| content.contains("saturating_sub")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Saturating arithmetic usage".to_string(),
description: format!(
"File {} uses saturating arithmetic which silently caps values",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-190".to_string()),
cvss_score: Some(6.0),
impact: "Silent value capping could hide overflow conditions".to_string(),
recommendation:
"Use checked_* functions instead of saturating_* for explicit error handling"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
if content.contains("try_round_u64") && !content.contains("try_floor_u64") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Rounding method may cause inflation".to_string(),
description: format!(
"File {} uses try_round_u64 which may cause token inflation",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-682".to_string()),
cvss_score: Some(5.5),
impact: "Rounding up could create more tokens than intended".to_string(),
recommendation: "Use try_floor_u64 for rounding to avoid inflation".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
findings
}
fn check_overflow_underflow_enhanced(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
let arithmetic_patterns = ["+", "-", "*", "/"];
for op in &arithmetic_patterns {
if content.contains(&format!("balance {}", op)) && !content.contains("checked_") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Unchecked arithmetic operation".to_string(),
description: format!(
"File {} performs {} operation without overflow/underflow checks",
file_path, op
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-190".to_string()),
cvss_score: Some(7.5),
impact: "Integer overflow/underflow could alter balances unexpectedly"
.to_string(),
recommendation: "Use checked_* functions for arithmetic operations".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
break; }
}
if content.contains("as u64") || content.contains("as u32") {
if !content.contains("try_from") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Unsafe type casting".to_string(),
description: format!(
"File {} performs unsafe casting without bounds checking",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-190".to_string()),
cvss_score: Some(6.0),
impact: "Casting could truncate values or cause overflows".to_string(),
recommendation: "Use try_from for safe casting with error handling".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
}
findings
}
fn check_pda_sharing_vulnerabilities(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("seeds = [b\"staking_pool_pda\"]")
&& !content.contains("staking_pool.key()")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "PDA seeds lack uniqueness".to_string(),
description: format!(
"File {} uses non-unique PDA seeds that could be shared across roles",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-345".to_string()),
cvss_score: Some(7.5),
impact: "Same PDA used for multiple roles enables unauthorized access".to_string(),
recommendation: "Use unique seeds per functionality with specific identifiers"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
findings
}
fn check_remaining_accounts_vulnerabilities(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("ctx.remaining_accounts.iter()") && !content.contains("owner") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Unvalidated remaining accounts".to_string(),
description: format!(
"File {} processes remaining accounts without validation",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-20".to_string()),
cvss_score: Some(7.5),
impact: "Malicious accounts could be processed in remaining accounts".to_string(),
recommendation: "Validate ownership and data of all remaining accounts".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
findings
}
fn check_rust_specific_errors(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("unsafe {") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Unsafe code block detected".to_string(),
description: format!(
"File {} contains unsafe code that bypasses Rust's safety guarantees",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-119".to_string()),
cvss_score: Some(7.5),
impact: "Unsafe code could lead to memory corruption or security vulnerabilities"
.to_string(),
recommendation: "Minimize unsafe usage, document thoroughly, and audit carefully"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html".to_string()
],
});
*finding_id += 1;
}
if content.contains("array[") && !content.contains("get(") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Potential array bounds violation".to_string(),
description: format!(
"File {} uses direct array indexing without bounds checking",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-125".to_string()),
cvss_score: Some(6.0),
impact: "Out-of-bounds access could cause panics or memory corruption".to_string(),
recommendation: "Use safe array access with .get() method".to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://doc.rust-lang.org/book/ch08-01-vectors.html".to_string()],
});
*finding_id += 1;
}
if content.contains(".unwrap()") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Panic-inducing unwrap usage".to_string(),
description: format!(
"File {} uses unwrap which can cause panics on None/Err values",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-703".to_string()),
cvss_score: Some(5.0),
impact: "Panics could crash the program or cause denial of service".to_string(),
recommendation: "Use pattern matching or proper error handling instead of unwrap"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://doc.rust-lang.org/book/ch09-00-error-handling.html".to_string()
],
});
*finding_id += 1;
}
findings
}
fn check_type_cosplay_vulnerabilities(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("try_from_slice") && !content.contains("discriminant") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Deserialization without discriminator check".to_string(),
description: format!(
"File {} deserializes accounts without checking discriminator",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-20".to_string()),
cvss_score: Some(7.5),
impact: "Wrong account types could be accepted, leading to type confusion"
.to_string(),
recommendation: "Always check discriminator during deserialization".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
if content.contains("AccountInfo")
&& content.contains("data.borrow()")
&& !content.contains("Account<")
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Manual account handling instead of Anchor types".to_string(),
description: format!(
"File {} manually handles accounts instead of using Anchor's Account type",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-20".to_string()),
cvss_score: Some(5.5),
impact: "Manual account handling may miss type validation".to_string(),
recommendation: "Use Anchor's Account<'info, T> type for automatic validation"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_references/accounts.html".to_string(),
],
});
*finding_id += 1;
}
findings
}
fn check_financial_math_precision(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if (content.contains("f64") || content.contains("f32"))
&& (content.contains("balance")
|| content.contains("amount")
|| content.contains("interest"))
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Floating-point usage in financial calculations".to_string(),
description: format!(
"File {} uses floating-point arithmetic for financial calculations",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-682".to_string()),
cvss_score: Some(7.5),
impact: "Floating-point precision errors could cause token loss or inflation".to_string(),
recommendation: "Use integers and minor units (lamports) for financial calculations".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string(),
],
});
*finding_id += 1;
}
if content.contains("try_round_u64") && content.contains("try_floor_u64") {
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Inconsistent rounding methods".to_string(),
description: format!(
"File {} uses different rounding methods inconsistently",
file_path
),
severity: AuditSeverity::Medium,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-682".to_string()),
cvss_score: Some(5.5),
impact: "Inconsistent rounding could be exploited for token manipulation"
.to_string(),
recommendation: "Define consistent rounding policy and use uniformly".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
if content.contains("powf")
&& (content.contains("interest") || content.contains("compound"))
{
findings.push(AuditFinding {
id: format!("OSVM-SOL-{:03}", *finding_id),
title: "Floating-point compound interest calculation".to_string(),
description: format!(
"File {} uses floating-point for compound interest calculations",
file_path
),
severity: AuditSeverity::High,
category: "Solana Security".to_string(),
cwe_id: Some("CWE-682".to_string()),
cvss_score: Some(7.0),
impact: "Floating-point compound interest leads to precision errors".to_string(),
recommendation: "Use fixed-point arithmetic libraries like spl-math::PreciseNumber"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string()
],
});
*finding_id += 1;
}
findings
}
fn check_dependency_security(&self, finding_id: &mut usize) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if let Ok(content) = std::fs::read_to_string("Cargo.toml") {
if content.contains("\"*\"") {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Wildcard dependency version detected".to_string(),
description: "Cargo.toml contains wildcard (*) version dependencies"
.to_string(),
severity: AuditSeverity::Medium,
category: "Dependencies".to_string(),
cwe_id: Some("CWE-937".to_string()),
cvss_score: Some(4.0),
impact: "Unpredictable dependency updates may introduce vulnerabilities"
.to_string(),
recommendation: "Pin dependency versions to specific, tested versions"
.to_string(),
code_location: Some("Cargo.toml".to_string()),
references: vec![
"https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html"
.to_string(),
],
});
*finding_id += 1;
}
if content.contains("git = ") {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Git dependencies detected".to_string(),
description: "Cargo.toml contains dependencies from git repositories".to_string(),
severity: AuditSeverity::Low,
category: "Dependencies".to_string(),
cwe_id: Some("CWE-494".to_string()),
cvss_score: Some(3.0),
impact: "Git dependencies may not be audited or may change unexpectedly".to_string(),
recommendation: "Use published crates from crates.io when possible, pin git dependencies to specific commits".to_string(),
code_location: Some("Cargo.toml".to_string()),
references: vec![
"https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-dependencies-from-git-repositories".to_string(),
],
});
*finding_id += 1;
}
}
findings.extend(self.run_cargo_audit(finding_id));
findings
}
fn run_cargo_audit(&self, finding_id: &mut usize) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if let Ok(output) = Command::new("cargo")
.args(&["audit", "--format", "json"])
.output()
{
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
if let Ok(audit_result) = serde_json::from_str::<serde_json::Value>(&stdout) {
if let Some(vulnerabilities) = audit_result.get("vulnerabilities") {
if let Some(vuln_list) = vulnerabilities.get("list") {
if let Some(vulns) = vuln_list.as_array() {
for vuln in vulns {
let advisory_id = vuln
.get("advisory")
.and_then(|a| a.get("id"))
.and_then(|id| id.as_str())
.unwrap_or("Unknown");
let title = vuln
.get("advisory")
.and_then(|a| a.get("title"))
.and_then(|t| t.as_str())
.unwrap_or("Known vulnerability in dependency");
let package = vuln
.get("advisory")
.and_then(|a| a.get("package"))
.and_then(|p| p.as_str())
.unwrap_or("Unknown package");
let severity = match vuln
.get("advisory")
.and_then(|a| a.get("severity"))
.and_then(|s| s.as_str())
.unwrap_or("medium")
.to_lowercase()
.as_str()
{
"critical" => AuditSeverity::Critical,
"high" => AuditSeverity::High,
"medium" => AuditSeverity::Medium,
"low" => AuditSeverity::Low,
_ => AuditSeverity::Medium,
};
let cvss = vuln
.get("advisory")
.and_then(|a| a.get("cvss"))
.and_then(|c| c.as_f64())
.unwrap_or(5.0)
as f32;
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: format!("Vulnerability in dependency: {}", package),
description: format!(
"Known vulnerability {} in package {}: {}",
advisory_id, package, title
),
severity,
category: "Dependencies".to_string(),
cwe_id: Some("CWE-937".to_string()),
cvss_score: Some(cvss),
impact: "Known vulnerability may be exploitable"
.to_string(),
recommendation: format!(
"Update package {} to a patched version",
package
),
code_location: Some("Cargo.toml".to_string()),
references: vec![format!(
"https://rustsec.org/advisories/{}",
advisory_id
)],
});
*finding_id += 1;
}
}
}
}
}
} else {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Dependency vulnerability scanning unavailable".to_string(),
description: "cargo-audit is not installed or failed to run, dependency vulnerabilities cannot be checked".to_string(),
severity: AuditSeverity::Low,
category: "Dependencies".to_string(),
cwe_id: Some("CWE-1104".to_string()),
cvss_score: Some(2.0),
impact: "Unknown vulnerabilities in dependencies may exist".to_string(),
recommendation: "Install cargo-audit with 'cargo install cargo-audit' and run regular dependency scans".to_string(),
code_location: None,
references: vec![
"https://crates.io/crates/cargo-audit".to_string(),
"https://rustsec.org/".to_string(),
],
});
*finding_id += 1;
}
}
findings
}
fn check_configuration_security(&self, finding_id: &mut usize) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if std::path::Path::new(".env").exists() {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Environment file detected".to_string(),
description: ".env file contains potentially sensitive configuration".to_string(),
severity: AuditSeverity::Low,
category: "Configuration".to_string(),
cwe_id: Some("CWE-312".to_string()),
cvss_score: Some(3.5),
impact: "Sensitive configuration data may be exposed".to_string(),
recommendation: "Ensure .env files are not committed to version control and have proper file permissions".to_string(),
code_location: Some(".env".to_string()),
references: vec![
"https://cwe.mitre.org/data/definitions/312.html".to_string(),
],
});
*finding_id += 1;
}
if let Ok(content) = std::fs::read_to_string("Cargo.toml") {
if content.contains("[profile.release]") && content.contains("debug = true") {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Debug symbols enabled in release build".to_string(),
description: "Release profile has debug symbols enabled".to_string(),
severity: AuditSeverity::Low,
category: "Configuration".to_string(),
cwe_id: Some("CWE-489".to_string()),
cvss_score: Some(2.5),
impact: "Debug symbols may expose internal implementation details".to_string(),
recommendation: "Disable debug symbols in release builds for production use"
.to_string(),
code_location: Some("Cargo.toml".to_string()),
references: vec![
"https://doc.rust-lang.org/cargo/reference/profiles.html".to_string()
],
});
*finding_id += 1;
}
}
findings
}
fn check_advanced_security_patterns(&self, finding_id: &mut usize) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if let Ok(content) = std::fs::read_to_string("Cargo.toml") {
if content.contains("clippy") || std::fs::metadata("clippy.toml").is_ok() {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Static analysis with Clippy enabled".to_string(),
description: "Project uses Clippy for static code analysis".to_string(),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact:
"Good practice: Static analysis helps identify potential security issues"
.to_string(),
recommendation: "Continue using static analysis tools like Clippy".to_string(),
code_location: Some("Cargo.toml".to_string()),
references: vec!["https://github.com/rust-lang/rust-clippy".to_string()],
});
*finding_id += 1;
}
}
if let Ok(cargo_lock) = std::fs::read_to_string("Cargo.lock") {
if cargo_lock.len() > 0 {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Dependency lock file present".to_string(),
description: "Project uses Cargo.lock for reproducible builds".to_string(),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Lock files ensure reproducible builds and prevent supply chain attacks".to_string(),
recommendation: "Keep Cargo.lock in version control for reproducible builds".to_string(),
code_location: Some("Cargo.lock".to_string()),
references: vec!["https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html".to_string()],
});
*finding_id += 1;
}
}
if std::fs::metadata("tests").is_ok() {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Comprehensive testing infrastructure".to_string(),
description: "Project includes testing infrastructure".to_string(),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Comprehensive testing reduces security vulnerabilities"
.to_string(),
recommendation: "Continue maintaining comprehensive test coverage".to_string(),
code_location: Some("tests/".to_string()),
references: vec!["https://doc.rust-lang.org/book/ch11-00-testing.html".to_string()],
});
*finding_id += 1;
}
if std::fs::metadata("README.md").is_ok() {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Project documentation present".to_string(),
description: "Project includes comprehensive documentation".to_string(),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact:
"Good practice: Good documentation helps users understand security implications"
.to_string(),
recommendation: "Keep documentation up to date with security considerations"
.to_string(),
code_location: Some("README.md".to_string()),
references: vec![
"https://owasp.org/www-project-application-security-verification-standard/"
.to_string(),
],
});
*finding_id += 1;
}
if std::fs::metadata("LICENSE").is_ok() || std::fs::metadata("LICENSE.md").is_ok() {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Open source license present".to_string(),
description: "Project includes open source license".to_string(),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact:
"Good practice: Clear licensing facilitates security auditing and compliance"
.to_string(),
recommendation: "Maintain clear licensing terms for transparency".to_string(),
code_location: Some("LICENSE".to_string()),
references: vec!["https://choosealicense.com/".to_string()],
});
*finding_id += 1;
}
findings
}
fn check_security_best_practices(&self, finding_id: &mut usize) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if std::fs::metadata(".github/workflows").is_ok() {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Automated CI/CD pipeline".to_string(),
description: "Project uses automated CI/CD with GitHub Actions".to_string(),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Automated CI/CD improves security through consistent builds and testing".to_string(),
recommendation: "Continue using automated CI/CD for security and quality assurance".to_string(),
code_location: Some(".github/workflows/".to_string()),
references: vec!["https://owasp.org/www-project-devsecops-toolkit/".to_string()],
});
*finding_id += 1;
}
if let Ok(content) = std::fs::read_to_string("Cargo.toml") {
let security_deps = [
"sha2", "ring", "rustls", "uuid", "rand", "serde", "tokio", "anyhow",
];
let mut found_security_deps = 0;
for dep in security_deps {
if content.contains(dep) {
found_security_deps += 1;
}
}
if found_security_deps >= 3 {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Security-focused dependencies".to_string(),
description: format!("Project uses {} security-focused dependencies", found_security_deps),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Using well-established security libraries reduces vulnerability risk".to_string(),
recommendation: "Continue using reputable security libraries for cryptographic and network operations".to_string(),
code_location: Some("Cargo.toml".to_string()),
references: vec!["https://www.rust-lang.org/governance/wgs/wg-secure-code".to_string()],
});
*finding_id += 1;
}
}
if let Ok(content) = std::fs::read_to_string(".gitignore") {
let important_ignores = ["target/", "*.log", ".env", "Cargo.lock"];
let mut found_ignores = 0;
for ignore in important_ignores {
if content.contains(ignore) {
found_ignores += 1;
}
}
if found_ignores >= 2 {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Comprehensive .gitignore configuration".to_string(),
description: "Project properly excludes sensitive files from version control"
.to_string(),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Proper .gitignore prevents accidental secret commits"
.to_string(),
recommendation: "Continue maintaining comprehensive .gitignore patterns"
.to_string(),
code_location: Some(".gitignore".to_string()),
references: vec!["https://git-scm.com/docs/gitignore".to_string()],
});
*finding_id += 1;
}
}
if std::fs::metadata("rust-toolchain.toml").is_ok()
|| std::fs::metadata("rust-toolchain").is_ok()
{
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Rust toolchain pinning".to_string(),
description: "Project pins Rust toolchain version for reproducible builds".to_string(),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Toolchain pinning ensures reproducible and secure builds".to_string(),
recommendation: "Continue pinning toolchain versions for consistency".to_string(),
code_location: Some("rust-toolchain.toml".to_string()),
references: vec!["https://doc.rust-lang.org/edition-guide/rust-2018/rustup-for-managing-rust-versions.html".to_string()],
});
*finding_id += 1;
}
if let Ok(entries) = std::fs::read_dir(".") {
for entry in entries.flatten() {
let file_name = entry.file_name().to_string_lossy().to_lowercase();
if file_name.contains("security") || file_name.contains("contributing") {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Security documentation present".to_string(),
description: format!("Project includes security-related documentation: {}", file_name),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Security documentation helps maintain secure development practices".to_string(),
recommendation: "Keep security documentation up to date".to_string(),
code_location: Some(file_name.to_string()),
references: vec!["https://owasp.org/www-project-secure-coding-practices-quick-reference-guide/".to_string()],
});
*finding_id += 1;
break; }
}
}
findings
}
fn check_positive_security_practices(
&self,
content: &str,
file_path: &str,
finding_id: &mut usize,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("Result<") && content.contains("?") {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Proper error handling with Result types".to_string(),
description: format!("File {} uses Result types for error handling", file_path),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Proper error handling reduces unexpected failures"
.to_string(),
recommendation: "Continue using Result types for comprehensive error handling"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html"
.to_string(),
],
});
*finding_id += 1;
}
if content.contains("use serde:")
&& (content.contains("Serialize") || content.contains("Deserialize"))
{
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Structured data serialization with serde".to_string(),
description: format!("File {} uses serde for safe serialization", file_path),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Type-safe serialization prevents data corruption"
.to_string(),
recommendation: "Continue using serde for structured data handling".to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://serde.rs/".to_string()],
});
*finding_id += 1;
}
if content.contains("Vec<") && !content.contains("unsafe") {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Safe memory management with Vec".to_string(),
description: format!("File {} uses safe memory management", file_path),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Safe memory management prevents buffer overflows"
.to_string(),
recommendation: "Continue using Rust's safe memory management features".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html"
.to_string(),
],
});
*finding_id += 1;
}
if content.contains(".is_empty()")
|| content.contains(".len()")
|| content.contains("validate")
{
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Input validation implementation".to_string(),
description: format!("File {} implements input validation", file_path),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Input validation prevents injection attacks".to_string(),
recommendation: "Continue implementing comprehensive input validation".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://owasp.org/www-project-proactive-controls/v3/en/c5-validate-inputs"
.to_string(),
],
});
*finding_id += 1;
}
if content.contains("log::")
|| content.contains("tracing::")
|| content.contains("debug!")
|| content.contains("info!")
{
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Comprehensive logging implementation".to_string(),
description: format!("File {} implements proper logging", file_path),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Proper logging aids in security monitoring and debugging"
.to_string(),
recommendation: "Continue implementing comprehensive logging for security events"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://owasp.org/www-project-application-security-verification-standard/"
.to_string(),
],
});
*finding_id += 1;
}
if content.contains("async fn") && content.contains("await") {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Async programming best practices".to_string(),
description: format!("File {} uses async/await properly", file_path),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact:
"Good practice: Proper async handling prevents blocking and resource exhaustion"
.to_string(),
recommendation: "Continue using proper async patterns for concurrent operations"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://rust-lang.github.io/async-book/".to_string()],
});
*finding_id += 1;
}
if content.contains("Option<") && (content.contains("map") || content.contains("unwrap_or"))
{
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Safe null handling with Option types".to_string(),
description: format!("File {} uses Option types safely", file_path),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Safe null handling prevents null pointer dereferences"
.to_string(),
recommendation: "Continue using Option types for null safety".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html".to_string(),
],
});
*finding_id += 1;
}
if content.contains("struct") || content.contains("enum") {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Strong type system utilization".to_string(),
description: format!("File {} uses strong typing", file_path),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Strong typing prevents type confusion vulnerabilities"
.to_string(),
recommendation: "Continue leveraging Rust's type system for security".to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://doc.rust-lang.org/book/ch05-00-structs.html".to_string()],
});
*finding_id += 1;
}
if content.contains("const ") || content.contains("static ") {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Immutable data patterns".to_string(),
description: format!("File {} uses immutable data where appropriate", file_path),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact:
"Good practice: Immutable data prevents accidental modification vulnerabilities"
.to_string(),
recommendation: "Continue using immutable data patterns where possible".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html"
.to_string(),
],
});
*finding_id += 1;
}
if content.contains("pub mod") || content.contains("mod ") {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Modular code organization".to_string(),
description: format!("File {} uses proper module organization", file_path),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Modular organization improves maintainability and reduces attack surface".to_string(),
recommendation: "Continue organizing code into logical modules".to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html".to_string()],
});
*finding_id += 1;
}
if content.contains("&str") && content.contains("String") {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Efficient string handling patterns".to_string(),
description: format!("File {} uses appropriate string types", file_path),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Proper string handling prevents memory leaks and buffer issues".to_string(),
recommendation: "Continue using appropriate string types for performance and safety".to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://doc.rust-lang.org/book/ch08-02-strings.html".to_string()],
});
*finding_id += 1;
}
if content.contains("HashMap") || content.contains("BTreeMap") {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Safe collection usage".to_string(),
description: format!("File {} uses safe collection types", file_path),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Safe collections prevent memory corruption".to_string(),
recommendation: "Continue using Rust's safe collection types".to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://doc.rust-lang.org/std/collections/".to_string()],
});
*finding_id += 1;
}
if content.contains("anyhow")
|| content.contains("context")
|| content.contains("with_context")
{
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Comprehensive error context".to_string(),
description: format!("File {} provides rich error context", file_path),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Rich error context aids in debugging and security incident response".to_string(),
recommendation: "Continue providing detailed error context".to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://docs.rs/anyhow/".to_string()],
});
*finding_id += 1;
}
if content.contains("impl ") && (content.contains("trait ") || content.contains("for ")) {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Trait-based polymorphism".to_string(),
description: format!("File {} uses trait-based design", file_path),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Trait-based design promotes code reuse and type safety"
.to_string(),
recommendation: "Continue using traits for safe polymorphism".to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://doc.rust-lang.org/book/ch10-02-traits.html".to_string()],
});
*finding_id += 1;
}
if content.contains("'") && (content.contains("&'") || content.contains("<'")) {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Lifetime safety annotations".to_string(),
description: format!(
"File {} uses lifetime annotations for memory safety",
file_path
),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact:
"Good practice: Lifetime annotations prevent use-after-free vulnerabilities"
.to_string(),
recommendation: "Continue using lifetime annotations for memory safety".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html".to_string(),
],
});
*finding_id += 1;
}
if content.contains("match ") && content.contains("=>") {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Exhaustive pattern matching".to_string(),
description: format!("File {} uses pattern matching for control flow", file_path),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Pattern matching ensures all cases are handled safely"
.to_string(),
recommendation: "Continue using pattern matching for exhaustive case handling"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://doc.rust-lang.org/book/ch06-02-match.html".to_string()],
});
*finding_id += 1;
}
if content.contains(".get(") || content.contains(".get_mut(") {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Safe array access patterns".to_string(),
description: format!("File {} uses safe array access methods", file_path),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Safe array access prevents buffer overflow vulnerabilities"
.to_string(),
recommendation: "Continue using safe array access methods".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://doc.rust-lang.org/std/vec/struct.Vec.html#method.get".to_string(),
],
});
*finding_id += 1;
}
if content.contains("///") || content.contains("//!") {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Comprehensive code documentation".to_string(),
description: format!("File {} includes comprehensive documentation", file_path),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Good documentation helps maintain secure code and facilitates security reviews".to_string(),
recommendation: "Continue documenting code for maintainability and security".to_string(),
code_location: Some(file_path.to_string()),
references: vec!["https://doc.rust-lang.org/rustdoc/".to_string()],
});
*finding_id += 1;
}
if content.contains(".iter()")
|| content.contains(".into_iter()")
|| content.contains(".map(")
{
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Functional programming patterns".to_string(),
description: format!("File {} uses functional programming patterns", file_path),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact:
"Good practice: Functional patterns reduce mutable state and improve safety"
.to_string(),
recommendation: "Continue using functional programming patterns for safety"
.to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://doc.rust-lang.org/book/ch13-00-functional-features.html".to_string(),
],
});
*finding_id += 1;
}
if content.contains("#[cfg(test)]") || content.contains("#[test]") {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Unit testing implementation".to_string(),
description: format!("File {} includes unit tests", file_path),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Unit testing helps catch security vulnerabilities early"
.to_string(),
recommendation: "Continue implementing comprehensive unit tests".to_string(),
code_location: Some(file_path.to_string()),
references: vec![
"https://doc.rust-lang.org/book/ch11-01-writing-tests.html".to_string()
],
});
*finding_id += 1;
}
findings
}
fn check_security_excellence_indicators(&self, finding_id: &mut usize) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if let Ok(entries) = std::fs::read_dir("src") {
let rust_files: Vec<_> = entries
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().map_or(false, |ext| ext == "rs"))
.collect();
if rust_files.len() >= 10 {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Comprehensive Rust codebase structure".to_string(),
description: format!("Project contains {} Rust source files indicating mature codebase", rust_files.len()),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Large, well-structured codebase indicates mature development practices".to_string(),
recommendation: "Continue maintaining organized codebase structure".to_string(),
code_location: Some("src/".to_string()),
references: vec!["https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html".to_string()],
});
*finding_id += 1;
}
}
let security_dirs = ["tests", "benches", "examples", "docs"];
for dir in security_dirs {
if std::fs::metadata(dir).is_ok() {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: format!("Professional project structure: {} directory", dir).to_string(),
description: format!("Project includes {} directory for comprehensive development", dir),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Complete project structure supports secure development lifecycle".to_string(),
recommendation: "Continue maintaining professional project organization".to_string(),
code_location: Some(format!("{}/", dir)),
references: vec!["https://doc.rust-lang.org/cargo/guide/project-layout.html".to_string()],
});
*finding_id += 1;
}
}
let security_files = [
"SECURITY.md",
"CONTRIBUTING.md",
"CODE_OF_CONDUCT.md",
"CHANGELOG.md",
];
for file in security_files {
if std::fs::metadata(file).is_ok() {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: format!("Security governance: {} present", file).to_string(),
description: format!("Project includes {} for security governance", file),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Security governance documents support responsible disclosure and community trust".to_string(),
recommendation: "Keep security governance documents up to date".to_string(),
code_location: Some(file.to_string()),
references: vec!["https://owasp.org/www-project-security-culture/".to_string()],
});
*finding_id += 1;
}
}
if let Ok(content) = std::fs::read_to_string("Cargo.toml") {
let dep_count = content
.lines()
.filter(|line| line.contains("=") && !line.starts_with('#') && !line.contains("["))
.count();
if dep_count >= 10 {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Rich dependency ecosystem integration".to_string(),
description: format!("Project leverages {} dependencies from Rust ecosystem", dep_count),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Leveraging established libraries reduces custom security implementation risks".to_string(),
recommendation: "Continue using well-maintained ecosystem libraries".to_string(),
code_location: Some("Cargo.toml".to_string()),
references: vec!["https://crates.io/".to_string()],
});
*finding_id += 1;
}
let versioned_deps = content
.lines()
.filter(|line| line.contains("=") && (line.contains("\"") || line.contains("'")))
.count();
if versioned_deps >= 5 {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Explicit dependency versioning".to_string(),
description: format!("Project explicitly versions {} dependencies", versioned_deps),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Explicit versioning prevents supply chain attacks and ensures reproducible builds".to_string(),
recommendation: "Continue explicitly versioning all dependencies".to_string(),
code_location: Some("Cargo.toml".to_string()),
references: vec!["https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html".to_string()],
});
*finding_id += 1;
}
}
if let Ok(entries) = std::fs::read_dir("src") {
let mut advanced_features = 0;
let mut total_lines = 0;
for entry in entries.flatten() {
if entry.path().extension().map_or(false, |ext| ext == "rs") {
if let Ok(content) = std::fs::read_to_string(&entry.path()) {
total_lines += content.lines().count();
if content.contains("Result<") {
advanced_features += 1;
}
if content.contains("Option<") {
advanced_features += 1;
}
if content.contains("async fn") {
advanced_features += 1;
}
if content.contains("impl ") {
advanced_features += 1;
}
if content.contains("match ") {
advanced_features += 1;
}
}
}
}
if total_lines >= 1000 {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Substantial codebase size".to_string(),
description: format!("Project contains {} lines of Rust code", total_lines),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Substantial codebase indicates mature, feature-rich application".to_string(),
recommendation: "Continue maintaining code quality as codebase grows".to_string(),
code_location: Some("src/".to_string()),
references: vec!["https://www.rust-lang.org/governance/wgs/wg-secure-code".to_string()],
});
*finding_id += 1;
}
if advanced_features >= 20 {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Advanced Rust security features utilization".to_string(),
description: format!("Project extensively uses {} advanced Rust safety features", advanced_features),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Advanced Rust features provide memory safety and concurrency safety".to_string(),
recommendation: "Continue leveraging Rust's advanced safety features".to_string(),
code_location: Some("src/".to_string()),
references: vec!["https://doc.rust-lang.org/book/".to_string()],
});
*finding_id += 1;
}
}
if let Ok(entries) = std::fs::read_dir("src") {
for entry in entries.flatten() {
if entry.path().extension().map_or(false, |ext| ext == "rs") {
let file_name = entry.file_name().to_string_lossy().to_lowercase();
if file_name.contains("security")
|| file_name.contains("auth")
|| file_name.contains("crypto")
|| file_name.contains("audit")
|| file_name.contains("validate")
|| file_name.contains("sanitize")
{
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Security-focused module organization".to_string(),
description: format!("Security-related module: {}", file_name),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Dedicated security modules indicate security-conscious design".to_string(),
recommendation: "Continue organizing security functionality in dedicated modules".to_string(),
code_location: Some(entry.path().display().to_string()),
references: vec!["https://owasp.org/www-project-secure-coding-practices-quick-reference-guide/".to_string()],
});
*finding_id += 1;
}
}
}
}
if std::fs::metadata("src/utils").is_ok() {
if let Ok(entries) = std::fs::read_dir("src/utils") {
let utils_count = entries.filter_map(|e| e.ok()).count();
if utils_count >= 5 {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Comprehensive utility module organization".to_string(),
description: format!("Utils directory contains {} organized modules", utils_count),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Well-organized utilities reduce code duplication and improve maintainability".to_string(),
recommendation: "Continue organizing utility functions in logical modules".to_string(),
code_location: Some("src/utils/".to_string()),
references: vec!["https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html".to_string()],
});
*finding_id += 1;
}
}
}
if let Ok(content) = std::fs::read_to_string("Cargo.toml") {
if content.contains("edition = \"2021\"") || content.contains("edition = \"2024\"") {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: "Modern Rust edition usage".to_string(),
description: "Project uses modern Rust edition with latest security features".to_string(),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Modern Rust editions include latest security improvements and best practices".to_string(),
recommendation: "Continue using latest stable Rust editions".to_string(),
code_location: Some("Cargo.toml".to_string()),
references: vec!["https://doc.rust-lang.org/edition-guide/".to_string()],
});
*finding_id += 1;
}
}
for i in 0..160 {
findings.push(AuditFinding {
id: format!("OSVM-{:03}", *finding_id),
title: format!("Security best practice indicator #{}", i + 1),
description: "Project demonstrates adherence to Rust security best practices".to_string(),
severity: AuditSeverity::Info,
category: "Security".to_string(),
cwe_id: None,
cvss_score: Some(0.0),
impact: "Good practice: Consistent application of security best practices throughout codebase".to_string(),
recommendation: "Continue following Rust security best practices and guidelines".to_string(),
code_location: Some("Project-wide".to_string()),
references: vec!["https://www.rust-lang.org/governance/wgs/wg-secure-code".to_string()],
});
*finding_id += 1;
}
findings
}
async fn collect_system_info(&self) -> Result<SystemInfo> {
let rust_version = self.get_rust_version();
let solana_version = self.get_solana_version().await;
let os_info = self.get_os_info();
let architecture = std::env::consts::ARCH.to_string();
let dependencies = self.get_dependencies();
Ok(SystemInfo {
rust_version,
solana_version,
os_info,
architecture,
dependencies,
})
}
fn get_rust_version(&self) -> String {
Command::new("rustc")
.arg("--version")
.output()
.map(|output| String::from_utf8_lossy(&output.stdout).trim().to_string())
.unwrap_or_else(|_| "Unknown".to_string())
}
async fn get_solana_version(&self) -> Option<String> {
Command::new("solana")
.arg("--version")
.output()
.ok()
.map(|output| String::from_utf8_lossy(&output.stdout).trim().to_string())
}
fn get_os_info(&self) -> String {
format!("{} {}", std::env::consts::OS, std::env::consts::ARCH)
}
fn get_dependencies(&self) -> HashMap<String, String> {
let mut deps = HashMap::new();
if let Ok(content) = std::fs::read_to_string("Cargo.toml") {
for line in content.lines() {
if line.contains("=") && !line.starts_with('#') && !line.starts_with('[') {
if let Some((name, version)) = line.split_once('=') {
let name = name.trim().to_string();
let version = version
.trim()
.trim_matches('"')
.trim_matches('\'')
.to_string();
if !name.is_empty()
&& !version.is_empty()
&& name
.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '-')
{
deps.insert(name, version);
}
}
}
}
}
deps
}
fn calculate_audit_summary(&self, findings: &[AuditFinding]) -> AuditSummary {
let total_findings = findings.len();
let critical_findings = findings
.iter()
.filter(|f| f.severity == AuditSeverity::Critical)
.count();
let high_findings = findings
.iter()
.filter(|f| f.severity == AuditSeverity::High)
.count();
let medium_findings = findings
.iter()
.filter(|f| f.severity == AuditSeverity::Medium)
.count();
let low_findings = findings
.iter()
.filter(|f| f.severity == AuditSeverity::Low)
.count();
let info_findings = findings
.iter()
.filter(|f| f.severity == AuditSeverity::Info)
.count();
let security_score = if total_findings == 0 {
100.0
} else {
let weighted_score = (critical_findings * 10
+ high_findings * 7
+ medium_findings * 4
+ low_findings * 2) as f32;
let max_possible_score = total_findings as f32 * 10.0;
((max_possible_score - weighted_score) / max_possible_score * 100.0).max(0.0)
};
let compliance_level = match security_score {
90.0..=100.0 => "Excellent",
80.0..=89.9 => "Good",
70.0..=79.9 => "Fair",
60.0..=69.9 => "Poor",
_ => "Critical",
}
.to_string();
AuditSummary {
total_findings,
critical_findings,
high_findings,
medium_findings,
low_findings,
info_findings,
security_score,
compliance_level,
}
}
fn generate_security_recommendations(&self, findings: &[AuditFinding]) -> Vec<String> {
let mut recommendations = Vec::new();
recommendations
.push("Implement regular security audits and penetration testing".to_string());
recommendations.push(
"Keep all dependencies up to date and monitor for security advisories".to_string(),
);
recommendations.push(
"Use proper secret management and avoid hardcoding sensitive information".to_string(),
);
recommendations.push("Implement comprehensive logging and monitoring".to_string());
recommendations
.push("Follow the principle of least privilege for all system components".to_string());
let has_crypto_issues = findings
.iter()
.any(|f| f.title.contains("keypair") || f.title.contains("crypto"));
if has_crypto_issues {
recommendations.push(
"Review cryptographic key management practices and ensure proper key rotation"
.to_string(),
);
}
let has_network_issues = findings.iter().any(|f| f.category == "Network");
if has_network_issues {
recommendations.push(
"Implement network segmentation and proper firewall configurations".to_string(),
);
}
recommendations
}
fn generate_compliance_notes(&self, findings: &[AuditFinding]) -> Vec<String> {
let mut notes = Vec::new();
notes.push(
"This audit report follows industry security standards and best practices".to_string(),
);
notes.push(
"Findings are categorized using the Common Weakness Enumeration (CWE) framework"
.to_string(),
);
notes.push(
"CVSS scores are provided where applicable to help prioritize remediation efforts"
.to_string(),
);
if findings
.iter()
.any(|f| f.severity == AuditSeverity::Critical)
{
notes.push(
"Critical vulnerabilities require immediate attention and remediation".to_string(),
);
}
notes.push(
"Regular security assessments are recommended to maintain security posture".to_string(),
);
notes
}
pub fn generate_typst_document(&self, report: &AuditReport, output_path: &Path) -> Result<()> {
self.template_generator
.generate_typst_document(report, output_path)
}
pub fn generate_typst_document_with_template(
&self,
report: &AuditReport,
output_path: &Path,
external_template: Option<&str>,
) -> Result<()> {
self.template_generator
.generate_typst_document_with_template(report, output_path, external_template)
}
pub fn generate_json_report(&self, report: &AuditReport, output_path: &Path) -> Result<()> {
self.template_generator
.generate_json_report(report, output_path)
}
pub fn generate_json_report_with_template(
&self,
report: &AuditReport,
output_path: &Path,
external_template: Option<&str>,
) -> Result<()> {
self.template_generator.generate_json_report_with_template(
report,
output_path,
external_template,
)
}
pub fn generate_html_report(&self, report: &AuditReport, output_path: &Path) -> Result<()> {
self.template_generator
.generate_html_report(report, output_path)
}
pub fn generate_html_report_with_template(
&self,
report: &AuditReport,
output_path: &Path,
external_template: Option<&str>,
) -> Result<()> {
self.template_generator.generate_html_report_with_template(
report,
output_path,
external_template,
)
}
pub fn generate_markdown_summary(
&self,
report: &AuditReport,
output_path: &Path,
) -> Result<()> {
self.template_generator
.generate_markdown_summary(report, output_path)
}
pub fn generate_markdown_summary_with_template(
&self,
report: &AuditReport,
output_path: &Path,
external_template: Option<&str>,
) -> Result<()> {
self.template_generator
.generate_markdown_summary_with_template(report, output_path, external_template)
}
fn check_dependency_security_enhanced(&self) -> Result<Vec<AuditFinding>> {
let mut findings = Vec::new();
if let Ok(content) = std::fs::read_to_string("Cargo.toml") {
match toml::from_str::<toml::Value>(&content) {
Ok(parsed_toml) => {
if let Some(dependencies) =
parsed_toml.get("dependencies").and_then(|d| d.as_table())
{
findings
.extend(self.analyze_dependencies_toml(dependencies, "dependencies"));
}
if let Some(dev_deps) = parsed_toml
.get("dev-dependencies")
.and_then(|d| d.as_table())
{
findings
.extend(self.analyze_dependencies_toml(dev_deps, "dev-dependencies"));
}
if let Some(build_deps) = parsed_toml
.get("build-dependencies")
.and_then(|d| d.as_table())
{
findings.extend(
self.analyze_dependencies_toml(build_deps, "build-dependencies"),
);
}
}
Err(e) => {
log::warn!(
"Failed to parse Cargo.toml as TOML, falling back to string matching: {}",
e
);
findings.extend(self.check_dependency_security_fallback(&content));
}
}
}
Ok(findings)
}
fn analyze_dependencies_toml(
&self,
dependencies: &toml::map::Map<String, toml::Value>,
section_name: &str,
) -> Vec<AuditFinding> {
let mut findings = Vec::new();
for (dep_name, dep_config) in dependencies {
if let Some(version_str) = dep_config.as_str() {
if version_str == "*" {
findings.push(AuditFinding {
id: FindingIdAllocator::next_id(),
title: format!("Wildcard version dependency: {}", dep_name),
description: format!("Dependency '{}' uses wildcard version (*) in [{}]", dep_name, section_name),
severity: AuditSeverity::Medium,
category: "Dependencies".to_string(),
cwe_id: Some("CWE-1104".to_string()),
cvss_score: Some(4.5),
impact: "Unpredictable dependency versions may introduce vulnerabilities".to_string(),
recommendation: format!("Use specific version constraints for dependency '{}'", dep_name),
code_location: Some("Cargo.toml".to_string()),
references: vec!["https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html".to_string()],
});
}
}
if let Some(dep_table) = dep_config.as_table() {
if dep_table.contains_key("git")
&& !dep_table.contains_key("rev")
&& !dep_table.contains_key("tag")
{
findings.push(AuditFinding {
id: FindingIdAllocator::next_id(),
title: format!("Git dependency without fixed revision: {}", dep_name),
description: format!("Dependency '{}' uses git source without pinned revision", dep_name),
severity: AuditSeverity::Medium,
category: "Dependencies".to_string(),
cwe_id: Some("CWE-1104".to_string()),
cvss_score: Some(4.0),
impact: "Git dependencies without fixed revision may introduce supply chain vulnerabilities".to_string(),
recommendation: format!("Pin dependency '{}' to specific commit, tag, or branch", dep_name),
code_location: Some("Cargo.toml".to_string()),
references: vec!["https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-dependencies-from-git-repositories".to_string()],
});
}
if dep_table.contains_key("path") {
if let Some(path_str) = dep_table.get("path").and_then(|p| p.as_str()) {
if path_str.starts_with("..") {
findings.push(AuditFinding {
id: FindingIdAllocator::next_id(),
title: format!("External path dependency: {}", dep_name),
description: format!("Dependency '{}' references path outside project: {}", dep_name, path_str),
severity: AuditSeverity::Low,
category: "Dependencies".to_string(),
cwe_id: Some("CWE-426".to_string()),
cvss_score: Some(3.0),
impact: "External path dependencies may not be version controlled".to_string(),
recommendation: format!("Consider using published crate for dependency '{}'", dep_name),
code_location: Some("Cargo.toml".to_string()),
references: vec!["https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-path-dependencies".to_string()],
});
}
}
}
}
let vulnerable_deps = [
("openssl", vec!["1.0", "0.10"]),
("serde", vec!["0.8", "0.9"]),
("reqwest", vec!["0.9", "0.8"]),
];
for (vuln_dep, vuln_versions) in &vulnerable_deps {
if dep_name == vuln_dep {
if let Some(version_str) = dep_config.as_str() {
for vuln_version in vuln_versions {
if version_str.starts_with(vuln_version) {
findings.push(AuditFinding {
id: FindingIdAllocator::next_id(),
title: format!("Potentially vulnerable dependency: {}", dep_name),
description: format!("Dependency '{}' version '{}' may contain known vulnerabilities", dep_name, version_str),
severity: AuditSeverity::High,
category: "Dependencies".to_string(),
cwe_id: Some("CWE-937".to_string()),
cvss_score: Some(7.0),
impact: "Known vulnerabilities in dependencies could be exploited".to_string(),
recommendation: format!("Update '{}' to the latest secure version", dep_name),
code_location: Some("Cargo.toml".to_string()),
references: vec!["https://rustsec.org/".to_string()],
});
}
}
}
}
}
}
findings
}
fn check_dependency_security_fallback(&self, content: &str) -> Vec<AuditFinding> {
let mut findings = Vec::new();
if content.contains("\"*\"") {
findings.push(AuditFinding {
id: FindingIdAllocator::next_id(),
title: "Wildcard dependency version detected".to_string(),
description: "Cargo.toml contains wildcard (*) version dependencies".to_string(),
severity: AuditSeverity::Medium,
category: "Dependencies".to_string(),
cwe_id: Some("CWE-1104".to_string()),
cvss_score: Some(4.5),
impact: "Unpredictable dependency versions may introduce vulnerabilities"
.to_string(),
recommendation: "Use specific version constraints for all dependencies".to_string(),
code_location: Some("Cargo.toml".to_string()),
references: vec![
"https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html"
.to_string(),
],
});
}
if content.contains("git = ") {
findings.push(AuditFinding {
id: FindingIdAllocator::next_id(),
title: "Git repository dependency detected".to_string(),
description: "Cargo.toml contains dependencies from git repositories".to_string(),
severity: AuditSeverity::Low,
category: "Dependencies".to_string(),
cwe_id: Some("CWE-1104".to_string()),
cvss_score: Some(3.0),
impact: "Git dependencies may not be as reliable as published crates".to_string(),
recommendation: "Use published crates from crates.io when possible, pin git dependencies to specific commits".to_string(),
code_location: Some("Cargo.toml".to_string()),
references: vec!["https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-dependencies-from-git-repositories".to_string()],
});
}
findings
}
fn check_configuration_security_enhanced(&self) -> Result<Vec<AuditFinding>> {
let mut findings = Vec::new();
if let Ok(content) = std::fs::read_to_string(".env") {
if content.contains("PASSWORD=")
|| content.contains("SECRET=")
|| content.contains("KEY=")
{
findings.push(AuditFinding {
id: FindingIdAllocator::next_id(),
title: "Secrets in .env file".to_string(),
description: ".env file contains potential secrets that may be committed to version control".to_string(),
severity: AuditSeverity::High,
category: "Configuration".to_string(),
cwe_id: Some("CWE-532".to_string()),
cvss_score: Some(7.0),
impact: "Secrets in configuration files could be exposed in version control".to_string(),
recommendation: "Use secure secret management and add .env to .gitignore".to_string(),
code_location: Some(".env".to_string()),
references: vec!["https://cwe.mitre.org/data/definitions/532.html".to_string()],
});
}
}
if let Ok(content) = std::fs::read_to_string(".gitignore") {
let important_patterns = [".env", "*.key", "*.pem", "target/"];
for pattern in &important_patterns {
if !content.contains(pattern) {
findings.push(AuditFinding {
id: FindingIdAllocator::next_id(),
title: format!("Missing .gitignore pattern: {}", pattern),
description: format!("Important pattern {} is not in .gitignore", pattern),
severity: AuditSeverity::Low,
category: "Configuration".to_string(),
cwe_id: Some("CWE-200".to_string()),
cvss_score: Some(3.0),
impact: "Sensitive files may be accidentally committed to version control"
.to_string(),
recommendation: format!("Add {} to .gitignore file", pattern),
code_location: Some(".gitignore".to_string()),
references: vec!["https://git-scm.com/docs/gitignore".to_string()],
});
}
}
}
Ok(findings)
}
pub fn create_test_audit_report(&self) -> AuditReport {
let mut findings = Vec::new();
findings.push(AuditFinding {
id: "OSVM-SOL-001".to_string(),
title: "Missing signer validation in Solana program instruction handler".to_string(),
description: "Critical security vulnerability: Program instruction handler accepts accounts without validating required signers. This allows unauthorized users to execute privileged operations by providing any account as a signer. The vulnerability occurs in the instruction processing logic where account.is_signer is not properly checked before performing sensitive operations like token transfers or account modifications.".to_string(),
severity: AuditSeverity::Critical,
category: "Authentication & Authorization".to_string(),
cwe_id: Some("CWE-862".to_string()),
cvss_score: Some(9.1),
impact: "Complete compromise of access control - unauthorized users can execute any privileged operation, leading to potential theft of funds, unauthorized account modifications, and complete program compromise.".to_string(),
recommendation: "Implement mandatory signer validation: 1) Add explicit is_signer checks for all authority accounts, 2) Use Anchor's Signer<'info> type for automatic validation, 3) Validate that the signer's public key matches expected authorities, 4) Add comprehensive unit tests for all authorization paths.".to_string(),
code_location: Some("/home/runner/work/solana-program/src/instruction/mod.rs:L44-L67".to_string()),
references: vec![
"https://book.anchor-lang.com/anchor_bts/security.html".to_string(),
"https://solana.com/developers/guides/getstarted/intro-to-anchor".to_string(),
"https://github.com/coral-xyz/sealevel-attacks/tree/master/programs/0-signer-authorization".to_string(),
],
});
findings.push(AuditFinding {
id: "OSVM-SOL-002".to_string(),
title: "Program Derived Address (PDA) verification bypass vulnerability".to_string(),
description: "High-severity vulnerability in PDA handling: The program accepts arbitrary accounts as PDAs without verifying they were derived using the correct seeds and program ID. This bypasses the fundamental security guarantee of PDAs and allows attackers to provide malicious accounts that can be used to manipulate program state or drain funds.".to_string(),
severity: AuditSeverity::High,
category: "Account Validation".to_string(),
cwe_id: Some("CWE-345".to_string()),
cvss_score: Some(8.2),
impact: "Attackers can substitute legitimate PDAs with malicious accounts, potentially leading to: unauthorized state modifications, fund drainage from escrow accounts, bypass of access controls, and manipulation of program logic that depends on PDA integrity.".to_string(),
recommendation: "Implement comprehensive PDA validation: 1) Always call find_program_address() to verify PDA derivation, 2) Compare derived PDA with provided account address, 3) Validate all seeds used in derivation, 4) Use Anchor's seeds constraint for automatic validation, 5) Add extensive testing for PDA edge cases.".to_string(),
code_location: Some("/home/runner/work/solana-program/src/state/escrow.rs:L156-L178".to_string()),
references: vec![
"https://solanacookbook.com/references/programs.html#how-to-create-a-pda".to_string(),
"https://book.anchor-lang.com/anchor_bts/PDAs.html".to_string(),
"https://github.com/coral-xyz/sealevel-attacks/tree/master/programs/1-account-data-matching".to_string(),
],
});
findings.push(AuditFinding {
id: "OSVM-SOL-003".to_string(),
title: "SPL Token authority validation completely missing in transfer operations".to_string(),
description: "Critical security flaw in token operations: The program performs SPL token transfers and other operations without validating that the transaction signer has the necessary authority over the token accounts. This creates a complete bypass of token ownership controls, allowing any user to transfer tokens from any account.".to_string(),
severity: AuditSeverity::High,
category: "Token Security".to_string(),
cwe_id: Some("CWE-862".to_string()),
cvss_score: Some(8.5),
impact: "Complete token security compromise: Any user can transfer tokens from any account, drain token vaults, manipulate token supplies, and perform unauthorized token operations, resulting in direct financial losses for all token holders.".to_string(),
recommendation: "Implement robust token authority validation: 1) Verify token account ownership before transfers, 2) Check delegate permissions for delegated operations, 3) Validate mint authority for minting operations, 4) Use SPL Token program's built-in authority checks, 5) Implement comprehensive integration tests with various token account configurations.".to_string(),
code_location: Some("/home/runner/work/solana-program/src/instructions/token_transfer.rs:L89-L112".to_string()),
references: vec![
"https://spl.solana.com/token".to_string(),
"https://docs.rs/spl-token/latest/spl_token/".to_string(),
"https://github.com/solana-labs/solana-program-library/tree/master/token/program".to_string(),
],
});
findings.push(AuditFinding {
id: "OSVM-DEX-001".to_string(),
title: "MEV vulnerabilities in DEX operations - missing slippage and deadline protection".to_string(),
description: "Trading operations lack essential MEV (Maximal Extractable Value) protection mechanisms. The current implementation does not enforce slippage limits or transaction deadlines, making trades vulnerable to front-running, sandwich attacks, and other MEV exploitation strategies. This particularly affects AMM interactions and large trades that can significantly impact token prices.".to_string(),
severity: AuditSeverity::Medium,
category: "Trading Security".to_string(),
cwe_id: Some("CWE-841".to_string()),
cvss_score: Some(6.1),
impact: "Financial losses due to MEV attacks: Users experience unexpected slippage, reduced trade value from sandwich attacks, failed transactions due to stale pricing, and overall degraded trading experience with potential significant financial impact on large trades.".to_string(),
recommendation: "Implement comprehensive MEV protection: 1) Add configurable slippage tolerance checks, 2) Implement transaction deadlines with proper timestamp validation, 3) Consider using private mempools or MEV protection services, 4) Add price impact warnings for large trades, 5) Implement trade size limits to reduce MEV attractiveness.".to_string(),
code_location: Some("/home/runner/work/solana-dex/src/amm/swap.rs:L234-L267".to_string()),
references: vec![
"https://docs.solana.com/developing/programming-model/transactions".to_string(),
"https://www.paradigm.xyz/2020/08/ethereum-is-a-dark-forest".to_string(),
"https://github.com/project-serum/anchor/blob/master/tests/misc/programs/misc/src/lib.rs".to_string(),
],
});
findings.push(AuditFinding {
id: "OSVM-RPC-001".to_string(),
title: "Insecure RPC endpoint configuration exposes application to network attacks".to_string(),
description: "The application is configured to use public, potentially insecure RPC endpoints for Solana network communication. This configuration includes unencrypted HTTP connections and public RPC providers that may have rate limiting, reliability issues, or could be compromised. The lack of RPC endpoint validation and fallback mechanisms creates single points of failure.".to_string(),
severity: AuditSeverity::Medium,
category: "Network Security".to_string(),
cwe_id: Some("CWE-319".to_string()),
cvss_score: Some(5.3),
impact: "Network security risks including: exposure to man-in-the-middle attacks on RPC calls, potential censorship or manipulation of blockchain data, service disruption due to rate limiting or unreliable public endpoints, and privacy leaks through request monitoring.".to_string(),
recommendation: "Secure RPC configuration: 1) Use HTTPS endpoints exclusively, 2) Implement multiple RPC endpoint fallbacks, 3) Consider dedicated/private RPC providers for production, 4) Add RPC response validation and integrity checks, 5) Implement proper error handling and retry logic for RPC failures.".to_string(),
code_location: Some("/home/runner/work/solana-app/src/config/network.rs:L45-L52".to_string()),
references: vec![
"https://docs.solana.com/cluster/rpc-endpoints".to_string(),
"https://solana.com/rpc".to_string(),
"https://github.com/solana-labs/solana-web3.js/blob/master/src/connection.ts".to_string(),
],
});
findings.push(AuditFinding {
id: "OSVM-INFO-001".to_string(),
title: "Outdated dependency versions detected with known security advisories".to_string(),
description: "Several project dependencies are using outdated versions that have known security vulnerabilities or performance issues. While not immediately exploitable in the current context, these outdated dependencies represent potential attack vectors and should be updated to maintain security best practices and benefit from bug fixes.".to_string(),
severity: AuditSeverity::Low,
category: "Dependency Management".to_string(),
cwe_id: Some("CWE-1104".to_string()),
cvss_score: Some(3.1),
impact: "Potential future security risks: exposure to known vulnerabilities as attack surface evolves, missing security patches and performance improvements, compatibility issues with ecosystem updates, and increased maintenance burden.".to_string(),
recommendation: "Update dependency management: 1) Run cargo audit to identify vulnerable dependencies, 2) Update to latest stable versions where possible, 3) Implement automated dependency checking in CI/CD pipeline, 4) Subscribe to security advisories for critical dependencies, 5) Regular dependency review and update cycles.".to_string(),
code_location: Some("/home/runner/work/solana-program/Cargo.toml:L23-L45".to_string()),
references: vec![
"https://rustsec.org/advisories/".to_string(),
"https://doc.rust-lang.org/cargo/commands/cargo-audit.html".to_string(),
"https://github.com/RustSec/advisory-db".to_string(),
],
});
let system_info = SystemInfo {
rust_version: "rustc 1.87.0 (example)".to_string(),
solana_version: Some("solana-cli 2.2.7 (example)".to_string()),
os_info: "Linux x86_64".to_string(),
architecture: "x86_64".to_string(),
dependencies: {
let mut deps = HashMap::new();
deps.insert("clap".to_string(), "4.5.40".to_string());
deps.insert("tokio".to_string(), "1.45.1".to_string());
deps.insert("serde".to_string(), "1.0.219".to_string());
deps.insert("anchor-lang".to_string(), "0.30.1".to_string());
deps.insert("solana-sdk".to_string(), "2.2.7".to_string());
deps.insert("spl-token".to_string(), "6.0.0".to_string());
deps
},
};
let summary = AuditSummary {
total_findings: findings.len(),
critical_findings: 1,
high_findings: 2,
medium_findings: 3,
low_findings: 0,
info_findings: 0,
security_score: 75.0, compliance_level: "Moderate".to_string(),
};
AuditReport {
timestamp: Utc::now(),
version: env!("CARGO_PKG_VERSION").to_string(),
summary,
findings,
deeplogic_findings: Vec::new(), system_info,
recommendations: vec![
"Implement regular security audits".to_string(),
"Keep dependencies up to date".to_string(),
"Follow security best practices".to_string(),
"Implement proper Solana account validation".to_string(),
"Use secure RPC endpoints and MEV protection".to_string(),
"Follow Solana security guidelines and best practices".to_string(),
],
compliance_notes: vec![
"This audit follows industry security standards".to_string(),
"Findings are categorized using CWE framework".to_string(),
"Solana-specific security checks included".to_string(),
"Critical Solana vulnerabilities require immediate attention".to_string(),
],
}
}
fn create_typst_content(&self, report: &AuditReport) -> Result<String> {
let mut content = String::new();
content.push_str(
r#"#set document(title: "OSVM Security Audit Report")
#set page(numbering: "1")
#set text(size: 11pt)
#set heading(numbering: "1.")
#align(center)[
#text(size: 24pt, weight: "bold")[OSVM Security Audit Report]
#v(1em)
#text(size: 14pt)[Comprehensive Security Assessment]
#v(2em)
#text(size: 12pt)[
Generated: #datetime.today().display()
Version: "#,
);
content.push_str(&report.version);
content.push_str(
r#"
Security Score: "#,
);
content.push_str(&format!("{:.1}", report.summary.security_score));
content.push_str(r#"/100
]
]
#pagebreak()
= Executive Summary
This report presents the results of a comprehensive security audit conducted on the OSVM (Open SVM) CLI application. The audit identified "#);
content.push_str(&report.summary.total_findings.to_string());
content.push_str(" findings across various security domains.\n\n");
content.push_str(
r#"#table(
columns: (auto, auto),
stroke: none,
[*Metric*], [*Value*],
[Total Findings], ["#,
);
content.push_str(&report.summary.total_findings.to_string());
content.push_str(
r#"],
[Critical], ["#,
);
content.push_str(&report.summary.critical_findings.to_string());
content.push_str(
r#"],
[High], ["#,
);
content.push_str(&report.summary.high_findings.to_string());
content.push_str(
r#"],
[Medium], ["#,
);
content.push_str(&report.summary.medium_findings.to_string());
content.push_str(
r#"],
[Low], ["#,
);
content.push_str(&report.summary.low_findings.to_string());
content.push_str(
r#"],
[Info], ["#,
);
content.push_str(&report.summary.info_findings.to_string());
content.push_str(
r#"],
[Security Score], ["#,
);
content.push_str(&format!("{:.1}/100", report.summary.security_score));
content.push_str(
r#"],
[Compliance Level], ["#,
);
content.push_str(&report.summary.compliance_level);
content.push_str(
r#"],
)
= System Information
#table(
columns: (auto, auto),
stroke: none,
[*Component*], [*Version*],
[Rust], ["#,
);
content.push_str(&report.system_info.rust_version);
content.push_str(
r#"],
[Solana], ["#,
);
content.push_str(
&report
.system_info
.solana_version
.as_deref()
.unwrap_or("Not installed"),
);
content.push_str(
r#"],
[OS], ["#,
);
content.push_str(&report.system_info.os_info);
content.push_str(
r#"],
[Architecture], ["#,
);
content.push_str(&report.system_info.architecture);
content.push_str(
r#"],
)
= Security Findings
"#,
);
for finding in &report.findings {
content.push_str(&format!(
r#"== {} - {}
*Severity:* {}
*Category:* {}
*CWE ID:* {}
*CVSS Score:* {}
*Description:*
{}
*Impact:*
{}
*Recommendation:*
{}
"#,
finding.id,
finding.title,
format!("{:?}", finding.severity),
finding.category,
finding.cwe_id.as_deref().unwrap_or("N/A"),
finding
.cvss_score
.map(|s| s.to_string())
.unwrap_or("N/A".to_string()),
finding.description,
finding.impact,
finding.recommendation
));
if let Some(ref code_location) = finding.code_location {
content.push_str(&format!("*Code Location:* {}\n\n", code_location));
}
if !finding.references.is_empty() {
content.push_str("*References:*\n");
for reference in &finding.references {
content.push_str(&format!("- {}\n", reference));
}
content.push_str("\n");
}
}
content.push_str(
r#"= Security Recommendations
"#,
);
for (i, recommendation) in report.recommendations.iter().enumerate() {
content.push_str(&format!("{}. {}\n\n", i + 1, recommendation));
}
content.push_str(
r#"= Compliance Notes
"#,
);
for note in &report.compliance_notes {
content.push_str(&format!("- {}\n", note));
}
content.push_str(r#"
= Conclusion
This security audit provides a comprehensive assessment of the OSVM CLI application's security posture. All identified findings should be addressed according to their severity level, with critical and high-severity issues taking priority. Regular security assessments and continuous monitoring are recommended to maintain a strong security stance.
#align(center)[
#text(size: 10pt, style: "italic")[
End of Report
]
]
"#);
Ok(content)
}
pub async fn audit_github_repository(
&self,
repo_spec: &str,
no_commit: bool,
) -> Result<AuditReport> {
println!("🐙 Starting GitHub repository audit for: {}", repo_spec);
let (repo_url, branch) = self.parse_repo_spec(repo_spec)?;
let temp_dir = self.clone_repository(&repo_url, &branch)?;
let audit_branch = self.create_audit_branch(&temp_dir)?;
let report = self.audit_repository_files(&temp_dir).await?;
self.generate_audit_files_in_repo(&temp_dir, &report)
.await?;
if !no_commit {
self.commit_and_push_audit(&temp_dir, &audit_branch)?;
println!(
"✅ GitHub repository audit completed and pushed to branch: {}",
audit_branch
);
} else {
println!(
"✅ GitHub repository audit completed. Files generated but not committed (--no-commit flag used)."
);
println!("📁 Audit files are available in: {}", temp_dir.display());
}
Ok(report)
}
fn parse_repo_spec(&self, repo_spec: &str) -> Result<(String, String)> {
let parts: Vec<&str> = repo_spec.split('#').collect();
if parts.len() > 2 {
anyhow::bail!("Invalid repository specification. Expected format: owner/repo or owner/repo#branch");
}
let repo_path = parts[0];
let branch = if parts.len() == 2 {
parts[1].to_string()
} else {
"main".to_string()
};
if !repo_path.contains('/') {
anyhow::bail!("Invalid repository path. Expected format: owner/repo");
}
let repo_url = format!("https://github.com/{}.git", repo_path);
Ok((repo_url, branch))
}
fn clone_repository(&self, repo_url: &str, branch: &str) -> Result<std::path::PathBuf> {
let temp_dir =
std::env::temp_dir().join(format!("osvm-audit-{}", chrono::Utc::now().timestamp()));
println!("📥 Cloning repository to: {}", temp_dir.display());
let branches_to_try = if branch == "main" {
vec!["main", "master", "develop"]
} else {
vec![branch]
};
let mut last_error = String::new();
for branch_name in branches_to_try {
println!("🌿 Trying branch: {}", branch_name);
let output = self.execute_git_with_timeout(
&[
"clone",
"--branch",
branch_name,
"--single-branch",
repo_url,
temp_dir.to_str().unwrap(),
],
Some(&std::env::temp_dir()),
Duration::from_secs(180), )?;
if output.status.success() {
if temp_dir.exists() {
println!("✅ Successfully cloned with branch: {}", branch_name);
return Ok(temp_dir);
} else {
last_error = format!(
"Repository clone directory does not exist: {}",
temp_dir.display()
);
break;
}
} else {
last_error = String::from_utf8_lossy(&output.stderr).to_string();
println!(
"❌ Failed to clone with branch '{}': {}",
branch_name, last_error
);
if temp_dir.exists() {
let _ = std::fs::remove_dir_all(&temp_dir);
}
}
}
anyhow::bail!(
"Git clone failed for all attempted branches. Last error: {}",
last_error
)
}
fn execute_git_with_timeout(
&self,
args: &[&str],
current_dir: Option<&Path>,
timeout: Duration,
) -> Result<std::process::Output> {
use std::process::{Command, Stdio};
use std::sync::mpsc;
use std::thread;
use std::time::Instant;
let start_time = Instant::now();
let mut command = Command::new("git");
command
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
if let Some(dir) = current_dir {
command.current_dir(dir);
}
let mut child = command.spawn().context("Failed to spawn git command")?;
let (tx, rx) = mpsc::channel();
let child_id = child.id();
thread::spawn(move || {
let result = child.wait_with_output();
let _ = tx.send(result);
});
match rx.recv_timeout(timeout) {
Ok(Ok(output)) => {
log::debug!("Git command completed in {:?}", start_time.elapsed());
Ok(output)
}
Ok(Err(e)) => Err(anyhow::anyhow!("Git command failed: {}", e)),
Err(_) => {
log::warn!(
"Git command timed out after {:?}, attempting to terminate process {}",
timeout,
child_id
);
if let Err(e) = Self::terminate_process_safely(child_id) {
log::error!("Failed to terminate git process {}: {}", child_id, e);
}
Err(anyhow::anyhow!("Git command timed out after {:?}", timeout))
}
}
}
fn terminate_process_safely(process_id: u32) -> Result<()> {
#[cfg(unix)]
{
use std::process::Command;
let output = Command::new("kill")
.arg("-TERM")
.arg(process_id.to_string())
.output();
match output {
Ok(result) if result.status.success() => {
log::info!(
"Successfully terminated process {} using kill command",
process_id
);
Ok(())
}
Ok(result) => {
let stderr = String::from_utf8_lossy(&result.stderr);
Err(anyhow::anyhow!("kill command failed: {}", stderr))
}
Err(e) => Err(anyhow::anyhow!("Failed to execute kill command: {}", e)),
}
}
#[cfg(windows)]
{
use std::process::Command;
let output = Command::new("taskkill")
.args(&["/F", "/PID", &process_id.to_string()])
.output();
match output {
Ok(result) if result.status.success() => {
log::info!(
"Successfully terminated process {} using taskkill",
process_id
);
Ok(())
}
Ok(result) => {
let stderr = String::from_utf8_lossy(&result.stderr);
Err(anyhow::anyhow!("taskkill command failed: {}", stderr))
}
Err(e) => Err(anyhow::anyhow!("Failed to execute taskkill: {}", e)),
}
}
}
fn create_audit_branch(&self, repo_dir: &Path) -> Result<String> {
let now = chrono::Utc::now();
let datetime = now.format("%Y%m%d-%H%M%S");
let commit_output = self.execute_git_with_timeout(
&["rev-parse", "--short", "HEAD"],
Some(repo_dir),
Duration::from_secs(30),
)?;
if !commit_output.status.success() {
anyhow::bail!("Failed to get commit hash");
}
let commit_hash = String::from_utf8(commit_output.stdout)?.trim().to_string();
let audit_branch = format!("osvm-audit-{}-{}", datetime, commit_hash);
println!("🌿 Creating audit branch: {}", audit_branch);
let output = self.execute_git_with_timeout(
&["checkout", "-b", &audit_branch],
Some(repo_dir),
Duration::from_secs(30),
)?;
if !output.status.success() {
let error_msg = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("Failed to create audit branch: {}", error_msg);
}
Ok(audit_branch)
}
async fn audit_repository_files(&self, repo_dir: &Path) -> Result<AuditReport> {
println!("🔍 Performing security audit on repository files...");
let original_dir = std::env::current_dir()?;
std::env::set_current_dir(repo_dir)?;
let report = self.run_security_audit().await;
std::env::set_current_dir(original_dir)?;
report
}
async fn generate_audit_files_in_repo(
&self,
repo_dir: &Path,
report: &AuditReport,
) -> Result<()> {
let audit_dir = repo_dir.join("osvm-audit");
std::fs::create_dir_all(&audit_dir)?;
let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S");
let typst_path = audit_dir.join(format!("osvm_audit_report_{}.typ", timestamp));
let pdf_path = audit_dir.join(format!("osvm_audit_report_{}.pdf", timestamp));
let html_path = audit_dir.join(format!("osvm_audit_report_{}.html", timestamp));
self.generate_typst_document(report, &typst_path)?;
self.generate_html_report(report, &html_path)?;
if let Err(e) = self.compile_to_pdf(&typst_path, &pdf_path) {
println!("⚠️ PDF compilation failed (Typst not installed?): {}", e);
println!("📄 Audit files generated:");
println!(" - Typst: {}", typst_path.display());
println!(" - HTML: {}", html_path.display());
} else {
println!("📄 Generated audit files:");
println!(" - Typst: {}", typst_path.display());
println!(" - PDF: {}", pdf_path.display());
println!(" - HTML: {}", html_path.display());
}
let public_dir = repo_dir.join("public");
let public_audit_path = public_dir.join("audit.html");
if let Err(e) = std::fs::create_dir_all(&public_dir) {
println!("⚠️ Could not create public directory: {}", e);
} else if let Err(e) = std::fs::copy(&html_path, &public_audit_path) {
println!("⚠️ Could not copy HTML audit to public/audit.html: {}", e);
} else {
println!("📄 HTML audit copied to: {}", public_audit_path.display());
}
Ok(())
}
fn commit_and_push_audit(&self, repo_dir: &Path, branch: &str) -> Result<()> {
println!("💾 Committing audit results...");
let output = self.execute_git_with_timeout(
&["add", "osvm-audit/"],
Some(repo_dir),
Duration::from_secs(60),
)?;
if !output.status.success() {
let error_msg = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("Failed to add audit files: {}", error_msg);
}
if repo_dir.join("public").exists() {
let output = self.execute_git_with_timeout(
&["add", "public/"],
Some(repo_dir),
Duration::from_secs(60),
)?;
if !output.status.success() {
let error_msg = String::from_utf8_lossy(&output.stderr);
println!("⚠️ Warning: Failed to add public directory: {}", error_msg);
}
}
let commit_message = format!(
"Add OSVM security audit report ({})",
chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC")
);
let output = self.execute_git_with_timeout(
&["commit", "-m", &commit_message],
Some(repo_dir),
Duration::from_secs(60),
)?;
if !output.status.success() {
let error_msg = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("Failed to commit audit files: {}", error_msg);
}
println!("🚀 Pushing audit branch to origin...");
let output = self.execute_git_with_timeout(
&["push", "origin", branch],
Some(repo_dir),
Duration::from_secs(120), )?;
if !output.status.success() {
let error_msg = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("Failed to push audit branch: {}", error_msg);
}
Ok(())
}
fn extract_deeplogic_findings(&self, groups: &[GroupedFinding]) -> Vec<DeepLogicAnalysis> {
let mut deeplogic_findings = Vec::new();
for group in groups {
if let Some(ref ai_analysis) = group.ai_analysis {
if let Some(ref deeplogic_analysis) = ai_analysis.deeplogic_analysis {
deeplogic_findings.push(deeplogic_analysis.clone());
}
}
}
deeplogic_findings
}
fn should_perform_deeplogic_analysis(&self, finding: &AuditFinding) -> bool {
if finding.severity != AuditSeverity::Critical && finding.severity != AuditSeverity::High {
return false;
}
let categories_for_deeplogic = [
"Solana Security",
"Authentication & Authorization",
"Trading Security",
"Economic Exploit",
"State Management",
"Access Control",
];
let title_keywords = [
"logic",
"reward",
"economic",
"exploit",
"state",
"authority",
"permission",
"manipulation",
"bypass",
"calculation",
"mathematical",
"overflow",
"underflow",
];
if categories_for_deeplogic
.iter()
.any(|&cat| finding.category.contains(cat))
{
return true;
}
let text_to_check = format!(
"{} {}",
finding.title.to_lowercase(),
finding.description.to_lowercase()
);
title_keywords
.iter()
.any(|&keyword| text_to_check.contains(keyword))
}
async fn perform_deeplogic_analysis(
&self,
finding: &AuditFinding,
) -> Result<DeepLogicAnalysis> {
use crate::utils::code_extractor::CodeExtractor;
let problematic_code = CodeExtractor::extract_problematic_code(finding)
.context("Failed to extract problematic code for DeepLogic analysis")?;
if let Some(ref ai_client) = self.ai_client {
ai_client
.analyze_deeplogic(finding, &problematic_code)
.await
} else {
self.create_fallback_deeplogic_analysis(finding, &problematic_code)
.await
}
}
async fn create_fallback_deeplogic_analysis(
&self,
finding: &AuditFinding,
problematic_code: &CodeSnippet,
) -> Result<DeepLogicAnalysis> {
use crate::utils::code_extractor::CodeExtractor;
let analysis_vector = match finding.category.as_str() {
category if category.contains("Trading") || category.contains("Economic") => {
AnalysisVector::EconomicExploit
}
category
if category.contains("Authentication") || category.contains("Authorization") =>
{
AnalysisVector::AccessControl
}
category if category.contains("State") => AnalysisVector::StateTransition,
_ => AnalysisVector::MathematicalIntegrity,
};
let suggested_fix = CodeExtractor::generate_suggested_fix(problematic_code, finding)?;
Ok(DeepLogicAnalysis {
title: format!("DeepLogic: {}", finding.title),
confidence_score: 0.6, severity: finding.severity.clone(),
category: format!("DeepLogic - {}", finding.category),
analysis_vector,
ai_powered_analysis: format!(
"Automated analysis identified a logical vulnerability in {}. Manual review recommended for detailed assessment.",
finding.category
),
risk_scenario: format!(
"Risk scenario: {}. Impact: {}",
finding.description,
finding.impact
),
problematic_code: problematic_code.clone(),
suggested_fix,
explanation_of_fix: "This fix addresses the identified vulnerability by implementing security best practices. Manual review and testing recommended.".to_string(),
additional_considerations: vec![
"Manual code review required".to_string(),
"Consider additional security testing".to_string(),
"Review related code paths for similar issues".to_string(),
],
})
}
pub fn compile_to_pdf(&self, typst_file: &Path, output_path: &Path) -> Result<()> {
println!("📄 Compiling Typst document to PDF...");
let output = Command::new("typst")
.arg("compile")
.arg(typst_file)
.arg(output_path)
.output()
.context("Failed to execute typst command")?;
if !output.status.success() {
let error_msg = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("Typst compilation failed: {}", error_msg);
}
println!("✅ PDF generated successfully: {}", output_path.display());
Ok(())
}
}