Skip to content

Commit 004abf1

Browse files
authored
Merge pull request #3 from MindPatch/feature/comprehensive-fixes
πŸš€ Comprehensive code quality and security fixes
2 parents ffbcc41 + d8c8855 commit 004abf1

File tree

16 files changed

+278
-102
lines changed

16 files changed

+278
-102
lines changed

β€ŽCargo.lockβ€Ž

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

β€ŽCargo.tomlβ€Ž

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ regex = "1.10"
8181
# URL handling for webhooks
8282
url = "2.4"
8383

84+
# Semantic version parsing for vulnerability matching
85+
semver = "1.0"
86+
8487
[dev-dependencies]
8588
tokio-test = "0.4"
8689
tempfile = "3.8"

β€Žsrc/automation/git_monitor.rsβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ impl GitMonitor {
193193

194194
// Create scan configuration
195195
let scan_config = ScanConfig {
196-
target_path: repo_dir.clone(),
196+
target_path: repo_dir.to_path_buf(),
197197
output_file: None,
198198
recursive: true,
199199
ecosystems: repo_config.ecosystems.clone(),

β€Žsrc/automation/policy.rsβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ impl PolicyEngine {
182182
// This is a best-effort approach since vulnerability doesn't directly reference packages
183183
// We'll try to match based on package names mentioned in the vulnerability
184184

185-
for (_package_key, package) in package_map {
185+
for package in package_map.values() {
186186
// Check if package name is mentioned in vulnerability summary
187187
let package_name = &package.name;
188188

β€Žsrc/automation/scheduler.rsβ€Ž

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -221,49 +221,75 @@ fn should_notify(
221221
) -> bool {
222222
let result = &filtered_result.scan_result;
223223

224+
// Check if there are any vulnerabilities first
225+
if result.vulnerabilities.is_empty() {
226+
info!("No vulnerabilities found, skipping notification");
227+
return false;
228+
}
229+
224230
// Check minimum severity
225231
if let Some(min_severity) = &filters.min_severity {
226232
let has_qualifying_severity = result.vulnerabilities.iter().any(|v| {
227233
if let Some(severity) = &v.severity {
228-
severity_level(severity) >= severity_level(min_severity)
234+
let level = severity_level(severity);
235+
let min_level = severity_level(min_severity);
236+
info!("Checking severity: '{}' (level {}) >= '{}' (level {})",
237+
severity, level, min_severity, min_level);
238+
level >= min_level
229239
} else {
230240
false
231241
}
232242
});
233243

234244
if !has_qualifying_severity {
245+
info!("No vulnerabilities meet minimum severity requirement of '{}'", min_severity);
235246
return false;
236247
}
237248
}
238249

239250
// Check repository filter
240251
if let Some(repos) = &filters.repositories {
241252
if !repos.contains(&result.repository) {
253+
info!("Repository '{}' not in notification filter list", result.repository);
242254
return false;
243255
}
244256
}
245257

246-
// Check if there are any vulnerabilities
247-
if result.vulnerabilities.is_empty() {
248-
return false;
249-
}
250-
251-
// If only_new_vulnerabilities is true, we'd need to compare with previous scan
252-
// For now, we'll treat all vulnerabilities as "new" since we don't have persistent storage yet
258+
// For only_new_vulnerabilities filter:
259+
// If we don't have persistent storage to compare against previous scans,
260+
// we'll be more lenient and allow notifications for significant findings
253261
if filters.only_new_vulnerabilities {
254262
// Note: In a full implementation, this would compare against stored previous scan results
255-
// from a database. For now, we consider all vulnerabilities as potentially new.
256-
if result.vulnerabilities.is_empty() {
257-
return false;
258-
}
263+
// For now, we'll allow notifications if there are any vulnerabilities, since we can't
264+
// reliably determine what's "new" without persistent storage
265+
info!("only_new_vulnerabilities=true, but no previous scan data available. Allowing notification for {} vulnerabilities.", result.vulnerabilities.len());
259266
}
260267

268+
info!("Notification criteria met: {} vulnerabilities found", result.vulnerabilities.len());
261269
true
262270
}
263271

264272
/// Convert severity string to numeric level for comparison
265273
fn severity_level(severity: &str) -> u8 {
266-
match severity.to_lowercase().as_str() {
274+
let severity_lower = severity.to_lowercase();
275+
276+
// Handle CVSS format (e.g., "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H")
277+
if severity_lower.starts_with("cvss:") {
278+
// For CVSS format, determine severity based on the score components
279+
// C:H/I:H/A:H indicates High impact across all three categories
280+
if severity.contains("C:H") && severity.contains("I:H") && severity.contains("A:H") {
281+
return 4; // Critical - High impact on all three (Confidentiality, Integrity, Availability)
282+
} else if severity.contains("C:H") || severity.contains("I:H") || severity.contains("A:H") {
283+
return 3; // High - High impact on at least one category
284+
} else if severity.contains("C:M") || severity.contains("I:M") || severity.contains("A:M") {
285+
return 2; // Medium - Medium impact
286+
} else {
287+
return 1; // Low - Low or no significant impact
288+
}
289+
}
290+
291+
// Handle simple severity strings
292+
match severity_lower.as_str() {
267293
s if s.contains("critical") => 4,
268294
s if s.contains("high") => 3,
269295
s if s.contains("medium") => 2,

β€Žsrc/automation/webhooks.rsβ€Ž

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,22 @@ impl WebhookNotifier {
3939

4040
/// Send notification to a specific webhook
4141
async fn send_webhook_notification(&self, webhook: &Webhook, message: &NotificationMessage) -> Result<()> {
42+
// Validate webhook URL before attempting to send
43+
if webhook.url.contains("YOUR_WEBHOOK_ID") || webhook.url.contains("YOUR_WEBHOOK_TOKEN") {
44+
return Err(anyhow::anyhow!(
45+
"Webhook '{}' has placeholder URL. Please update with actual webhook URL from Discord/Slack.",
46+
webhook.name
47+
));
48+
}
49+
4250
let payload = match webhook.webhook_type {
4351
WebhookType::Discord => self.create_discord_payload(message),
4452
WebhookType::Slack => self.create_slack_payload(message),
4553
WebhookType::Generic => self.create_generic_payload(message),
4654
};
4755

56+
info!("Sending notification to {}: {}", webhook.name, message.title);
57+
4858
let response = self.client
4959
.post(&webhook.url)
5060
.json(&payload)
@@ -53,10 +63,12 @@ impl WebhookNotifier {
5363
.await?;
5464

5565
if response.status().is_success() {
66+
info!("Successfully sent notification to {}", webhook.name);
5667
Ok(())
5768
} else {
5869
let status = response.status();
5970
let body = response.text().await.unwrap_or_default();
71+
error!("Webhook request failed for {}: status {}, body: {}", webhook.name, status, body);
6072
Err(anyhow::anyhow!("Webhook request failed with status {}: {}", status, body))
6173
}
6274
}
@@ -224,15 +236,15 @@ impl WebhookNotifier {
224236

225237
// Determine overall severity - use severity field directly
226238
let severity = if current_scan.vulnerabilities.iter().any(|v|
227-
v.severity.as_ref().map_or(false, |s| s.to_lowercase().contains("critical"))
239+
v.severity.as_ref().is_some_and(|s| s.to_lowercase().contains("critical"))
228240
) {
229241
"critical"
230242
} else if current_scan.vulnerabilities.iter().any(|v|
231-
v.severity.as_ref().map_or(false, |s| s.to_lowercase().contains("high"))
243+
v.severity.as_ref().is_some_and(|s| s.to_lowercase().contains("high"))
232244
) {
233245
"high"
234246
} else if current_scan.vulnerabilities.iter().any(|v|
235-
v.severity.as_ref().map_or(false, |s| s.to_lowercase().contains("medium"))
247+
v.severity.as_ref().is_some_and(|s| s.to_lowercase().contains("medium"))
236248
) {
237249
"medium"
238250
} else {

β€Žsrc/cli.rsβ€Ž

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ impl Cli {
172172
.target_path(path)
173173
.output_file(output)
174174
.recursive(!no_recursive)
175-
.ecosystems(ecosystems.map(|e| parse_ecosystems(e)).transpose()?)
175+
.ecosystems(ecosystems.map(parse_ecosystems).transpose()?)
176176
.include_dev_dependencies(!no_dev_deps)
177177
.format(format)
178178
.quiet(quiet)
@@ -579,7 +579,7 @@ async fn execute_automation_run(config_path: PathBuf, workspace: PathBuf, reposi
579579

580580
for scan_result in &results {
581581
// Group vulnerabilities by package name
582-
let mut package_vulns: std::collections::HashMap<String, Vec<crate::types::Vulnerability>> = std::collections::HashMap::new();
582+
let mut vulnerabilities_by_package: std::collections::HashMap<String, Vec<crate::types::Vulnerability>> = std::collections::HashMap::new();
583583

584584
for vulnerability in &scan_result.vulnerabilities {
585585
// Extract package name from vulnerability ID (often in format "package-name@version")
@@ -593,11 +593,11 @@ async fn execute_automation_run(config_path: PathBuf, workspace: PathBuf, reposi
593593
references: vulnerability.references.clone(),
594594
};
595595

596-
package_vulns.entry(package_name).or_insert_with(Vec::new).push(vuln);
596+
vulnerabilities_by_package.entry(package_name).or_default().push(vuln);
597597
}
598598

599599
// Create PackageVulnerability entries
600-
for (package_name, vulns) in package_vulns {
600+
for (package_name, vulns) in vulnerabilities_by_package {
601601
let package_vuln = crate::types::PackageVulnerability {
602602
package: crate::types::Package {
603603
name: package_name,

β€Žsrc/error.rsβ€Ž

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ pub enum VulfyError {
2828
#[error("Package parsing error: {message}")]
2929
PackageParsing { message: String },
3030

31+
#[error("Version parsing error: {message}")]
32+
VersionParsing { message: String },
33+
3134
#[error("OSV API error: {message}")]
3235
OsvApi { message: String },
3336

0 commit comments

Comments
Β (0)