Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions casr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ license = "Apache-2.0"
exclude = ["/tests"]

[dependencies]
lettre = "0.11.15"
secrecy = "0.8"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, make these dependencies available only when casr is built with dojo feature (similar to tokio crate).

shell-words = "1.1"
anyhow = "1.0"
clap = { version = "4.5", features = ["wrap_help", "cargo", "env"] }
Expand Down
212 changes: 208 additions & 4 deletions casr/src/bin/casr-dojo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
use reqwest::{Client, Method, RequestBuilder, Response, Url};
use walkdir::WalkDir;

use lettre::{Message, SmtpTransport, Transport, transport::smtp::authentication::Credentials};
use secrecy::{ExposeSecret, Secret};
use std::env;

use std::collections::HashSet;
use std::collections::hash_map::DefaultHasher;
use std::fs;
Expand Down Expand Up @@ -221,7 +225,7 @@
extra_gdb_report: bool,
product_name: String,
test_id: i64,
) -> Result<()> {
) -> Result<FindingInfo> {

Check warning on line 228 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L228

Added line #L228 was not covered by tests
// Create new finding.
let mut executable = "";
if let Some(fname) = Path::new(&report.executable_path).file_name() {
Expand Down Expand Up @@ -366,7 +370,11 @@
debug!("Uploaded crash seed for finding '{}' with id={}", title, id);
}

Ok(())
Ok(FindingInfo {
Copy link
Collaborator

@SweetVishnya SweetVishnya Apr 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may just return JSON stored to finding variable. Thus, you won't need declaring an additional structure. Id can be stored in finding["id"].

title,
severity: severity.to_string(),
id,
})

Check warning on line 377 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L373-L377

Added lines #L373 - L377 were not covered by tests
}
}

Expand Down Expand Up @@ -552,6 +560,174 @@
d
}

fn parse_env_var(s: &str) -> Option<&str> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can hardcore exactly one supported environment variable CASR_MAIL_PASSWORD?

if let Some(stripped) = s.strip_prefix("${").and_then(|s| s.strip_suffix('}')) {
Some(stripped)
} else if let Some(stripped) = s.strip_prefix('$') {
Some(stripped)

Check warning on line 567 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L563-L567

Added lines #L563 - L567 were not covered by tests
} else {
None

Check warning on line 569 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L569

Added line #L569 was not covered by tests
}
}

Check warning on line 571 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L571

Added line #L571 was not covered by tests

struct FindingInfo {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add docs for structures, fields, and functions

title: String,
severity: String,
id: i64,
}

struct MailConfig {
server: String,
port: u16,
email: String,
use_ssl: bool,
password: Secret<String>,
recipients: Vec<String>,
defectdojo_url: String,
}

impl MailConfig {
pub fn new(toml: toml::Table, defectdojo_url: String) -> Result<Option<Self>> {
if !toml.contains_key("mail") || !toml["mail"].is_table() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-table 'mail' should be an error

return Ok(None);
}

let mail_settings = toml["mail"].as_table().unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may just write as_table()? to handle non-table case


if !mail_settings.contains_key("smtp_server") || !mail_settings["smtp_server"].is_str() {
bail!("[mail] smtp_server (string) must be specified in TOML");
}
if !mail_settings.contains_key("port") || !mail_settings["port"].is_integer() {
bail!("[mail] port (integer) must be specified in TOML");
}
if !mail_settings.contains_key("email") || !mail_settings["email"].is_str() {
bail!("[mail] email (string) must be specified in TOML");
}
if !mail_settings.contains_key("password") || !mail_settings["password"].is_str()

Check warning on line 606 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L590-L606

Added lines #L590 - L606 were not covered by tests
{
bail!("[mail] password (string) must be specified in TOML");
}
if !mail_settings.contains_key("recipients") || !mail_settings["recipients"].is_array() {
bail!("[mail] recipients (array) must be specified in TOML");
}

Check warning on line 612 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L608-L612

Added lines #L608 - L612 were not covered by tests

if let Some(recipients) = mail_settings["recipients"].as_array() {
for recipient in recipients {
if !recipient.is_str() {
bail!("All elements in [mail] recipients must be strings");
}

Check warning on line 618 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L614-L618

Added lines #L614 - L618 were not covered by tests
}
}

Check warning on line 620 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L620

Added line #L620 was not covered by tests

let server = mail_settings["smtp_server"].as_str().unwrap().to_string();
let port = mail_settings["port"].as_integer().unwrap() as u16;
let email = mail_settings["email"].as_str().unwrap().to_string();

let use_ssl = mail_settings
.get("use_ssl")
.and_then(|v| v.as_bool())
.unwrap_or(port == 465);

if !mail_settings.contains_key("use_ssl") {
info!(
"[mail] use_ssl not specified, assuming {} based on port {}",

Check warning on line 633 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L622-L633

Added lines #L622 - L633 were not covered by tests
use_ssl, port
);
}

Check warning on line 636 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L636

Added line #L636 was not covered by tests

let password = mail_settings["password"].as_str().unwrap().to_string();

Check warning on line 638 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L638

Added line #L638 was not covered by tests

let password = if let Some(env_var) = parse_env_var(&password) {
match env::var(env_var) {
Ok(val) => val,
Err(e) => {
warn!("Environment variable '{}' is not set: {}", env_var, e);
password

Check warning on line 645 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L640-L645

Added lines #L640 - L645 were not covered by tests
}
}
} else {
warn!(
"It is recommended to store SMTP passwords in environment variables for security (e.g. smtp_password = \"$SMTP_PASS\")"

Check warning on line 650 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L649-L650

Added lines #L649 - L650 were not covered by tests
);
password

Check warning on line 652 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L652

Added line #L652 was not covered by tests
};
let password = Secret::new(password);

let recipients = mail_settings["recipients"]
.as_array()
.unwrap()
.iter()
.map(|v| v.as_str().unwrap().to_string())
.collect();

Ok(Some(Self {
server,
port,
email,
use_ssl,
password,
recipients,
defectdojo_url,
}))
}

Check warning on line 672 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L654-L672

Added lines #L654 - L672 were not covered by tests

fn create_transport(&self) -> Result<SmtpTransport> {
let creds = Credentials::new(self.email.clone(), self.password.expose_secret().clone());
let builder = if self.use_ssl {
SmtpTransport::relay(&self.server)?

Check warning on line 677 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L674-L677

Added lines #L674 - L677 were not covered by tests
} else {
SmtpTransport::starttls_relay(&self.server)?

Check warning on line 679 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L679

Added line #L679 was not covered by tests
};
Ok(builder.credentials(creds).port(self.port).build())
}

Check warning on line 682 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L681-L682

Added lines #L681 - L682 were not covered by tests

pub async fn send_notification(
&self,
product_name: &str,
findings: &[FindingInfo],
) -> Result<()> {
if findings.is_empty() {
return Ok(());
}

Check warning on line 691 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L684-L691

Added lines #L684 - L691 were not covered by tests

let transport = self.create_transport()?;
let subject = format!(
"[DefectDojo] {} new findings for {}",
findings.len(),
product_name
);

let mut body = format!(
"New findings uploaded to DefectDojo for '{}':\n\n",

Check warning on line 701 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L693-L701

Added lines #L693 - L701 were not covered by tests
product_name
);

for finding in findings {
let finding_url = format!("{}/finding/{}", self.defectdojo_url, finding.id);
body.push_str(&format!(
"- {} (Severity: {})\n View: {}\n\n",
finding.title, finding.severity, finding_url
));
}

Check warning on line 711 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L705-L711

Added lines #L705 - L711 were not covered by tests

let mut email_builder = Message::builder().from(self.email.parse()?);

Check warning on line 713 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L713

Added line #L713 was not covered by tests

for recipient in &self.recipients {
email_builder = email_builder.to(recipient.parse()?);

Check warning on line 716 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L715-L716

Added lines #L715 - L716 were not covered by tests
}

let email = email_builder.subject(subject).body(body)?;

Check warning on line 719 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L719

Added line #L719 was not covered by tests

transport.send(&email)?;
info!(
"Summary email notification sent to {} recipients: {}",
self.recipients.len(),
self.recipients.join(", ")

Check warning on line 725 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L721-L725

Added lines #L721 - L725 were not covered by tests
);
Ok(())
}

Check warning on line 728 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L727-L728

Added lines #L727 - L728 were not covered by tests
}

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
let options = clap::Command::new("casr-dojo")
Expand Down Expand Up @@ -896,6 +1072,7 @@
let extra_gdb_report = new_casr_reports.iter().any(|(_, _, gdb)| gdb.is_some());
let mut tasks = tokio::task::JoinSet::new();
let mut new_casr_reports = new_casr_reports.into_iter();
let mut findings = Vec::new();

Check warning on line 1075 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L1075

Added line #L1075 was not covered by tests
loop {
while tasks.len() < CONCURRENCY_LIMIT {
let Some((path, report, gdb)) = new_casr_reports.next() else {
Expand All @@ -911,10 +1088,37 @@
let Some(r) = tasks.join_next().await else {
break;
};
if let Err(e) = r? {
error!("{}", e);
match r? {
Ok(finding) => {
findings.push(finding);
}
Err(e) => {
error!("{}", e);
}

Check warning on line 1097 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L1091-L1097

Added lines #L1091 - L1097 were not covered by tests
}
}

let mail_sender = match MailConfig::new(
toml.clone(),
options.get_one::<Url>("url").unwrap().to_string(),
) {
Ok(Some(config)) => config,
Ok(None) => {
warn!("Mail configuration not found in TOML, skipping notifications");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be no warning here

return Ok(());
}
Err(e) => {
error!("Failed to parse mail config: {}", e);
return Ok(());
}
};
info!("Sending email notification for product '{}'", product_name);
if let Err(e) = mail_sender
.send_notification(&product_name.to_string(), &findings)
.await
{
error!("Failed to send notification: {}", e);
}

Check warning on line 1122 in casr/src/bin/casr-dojo.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-dojo.rs#L1101-L1122

Added lines #L1101 - L1122 were not covered by tests
Ok(())
}
13 changes: 12 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ your project before (via `dotnet build` or `dotnet publish`) and specify `--no-b
## casr-libfuzzer

Triage crashes found by libFuzzer based fuzzer
(C/C++/go-fuzz/Atheris/Jazzer/Jazzer.js/jsfuzz/luzer) or LibAFL based fuzzer
(C/C++/go-fuzz/Atheris/Jazzer/Jazzer.js/jsfuzz) or LibAFL based fuzzer
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant change


Usage: casr-libfuzzer [OPTIONS] --output <OUTPUT_DIR> -- <ARGS>...

Expand Down Expand Up @@ -770,6 +770,17 @@ name = "load_fuzzer 2023-06-07T16:47:18+03:00"
[test]
test_type = "CASR DAST Report"
```
Also `casr-dojo` can send email notifications about newly uploaded findings. To enable this feature, add a `[mail]` section to your TOML configuration file:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add newline here


```toml
[mail]
smtp_server = "your.smtp.server.com" # SMTP server
port = 465 # SMTP port
use_ssl = true # Whether to use SSL (default: false, except when port is 465)
email = "sender@example.com" # Sender email address
password = "$ENV_VAR_NAME" # Password for the email address (can use environment variables with $ prefix)
recipients = ["recipient1@example.com", "recipient2@example.com"] # List of recipients to send the notification to
```

CASR must be built with `dojo` feature via `cargo install -F dojo casr` or
`cargo build -F dojo --release`.
Expand Down
Loading