- --quiet now suppresses scan summaries and rule statistics unless --rule-stats is explicitly provided

- Added X Consumer key detection and validation
This commit is contained in:
Mick Grove 2025-08-09 15:36:12 -07:00
commit 2fd6cd30e1
3 changed files with 103 additions and 35 deletions

View file

@ -3,6 +3,7 @@
All notable changes to this project will be documented in this file.
## [1.38.0]
- `--quiet` now suppresses scan summaries and rule statistics unless `--rule-stats` is explicitly provided
- Added X Consumer key detection and validation
## [1.37.0]

View file

@ -38,14 +38,50 @@ macro_rules! safe_println {
pub fn print_scan_summary(
start_time: Instant,
datastore: &Arc<Mutex<findings_store::FindingsStore>>,
_global_args: &global::GlobalArgs,
global_args: &global::GlobalArgs,
args: &scan::ScanArgs,
// inputs: &FilesystemEnumeratorResult,
rules_db: &RulesDatabase,
matcher_stats: &Mutex<MatcherStats>,
profiler: Option<&ConcurrentRuleProfiler>,
) {
// let duration = start_time.elapsed();
if global_args.quiet {
if args.rule_stats {
if let Some(prof) = profiler {
let stats = prof.generate_report();
if !stats.is_empty() {
let name_w = stats.iter().map(|s| s.rule_name.len()).max().unwrap_or(4);
let id_w = stats.iter().map(|s| s.rule_id.len()).max().unwrap_or(2);
safe_println!("\n{:-^1$}", " Rule Performance Stats ", name_w + id_w + 47);
safe_println!(
"{: <name_w$} {: <id_w$} {: >8} {: >15} {: >15}",
"Rule",
"ID",
"Matches",
"Slowest",
"Average",
name_w = name_w,
id_w = id_w
);
safe_println!("{:-<width$}", "", width = name_w + id_w + 49);
for rs in stats {
safe_println!(
"{: <name_w$} {: <id_w$} {: >8} {: >15?} {: >15?}",
rs.rule_name,
rs.rule_id,
rs.total_matches,
rs.slowest_match_time,
rs.average_match_time,
name_w = name_w,
id_w = id_w
);
}
}
}
}
return;
}
let ds = datastore.lock().unwrap();
let num_rules = rules_db.num_rules();
@ -53,17 +89,12 @@ pub fn print_scan_summary(
let mut sorted_findings: Vec<_> = findings_by_rule.into_iter().collect();
sorted_findings.sort_by(|a, b| b.1.cmp(&a.1));
let duration = start_time.elapsed();
// let ds = datastore.lock().unwrap();
// Get all matches
let all_matches = ds.get_matches();
// Count total findings
let total_findings = if args.no_dedup {
// When no_dedup is true, count each origin of validated matches as a separate finding
all_matches.iter().fold(0, |count, msg| {
let (origin_set, _, match_item) = &**msg;
// If this is a validated match, count each origin as a separate finding
if match_item.validation_success {
count + origin_set.len()
} else {
@ -73,14 +104,13 @@ pub fn print_scan_summary(
} else {
ds.get_num_matches()
};
// Count successful and failed validations
let (successful_validations, failed_validations) =
all_matches.iter().fold((0, 0), |(success, fail), msg| {
let (origin_set, _, match_item) = &**msg;
if match_item.validation_success {
if match_item.validation_response_status != StatusCode::CONTINUE.as_u16() {
if args.no_dedup {
// Count each origin of a successful validation as a separate success
(success + origin_set.len(), fail)
} else {
(success + 1, fail)
@ -88,17 +118,14 @@ pub fn print_scan_summary(
} else {
(success, fail)
}
} else if match_item.validation_response_status != StatusCode::CONTINUE.as_u16() {
(success, fail + 1)
} else {
if match_item.validation_response_status != StatusCode::CONTINUE.as_u16() {
(success, fail + 1)
} else {
(success, fail)
}
(success, fail)
}
});
let matcher_stats = matcher_stats.lock().unwrap();
// Generate JSON or JSONL output
if args.output_args.format == ReportOutputFormat::Json
|| args.output_args.format == ReportOutputFormat::Jsonl
{
@ -107,15 +134,11 @@ pub fn print_scan_summary(
"successful_validations": successful_validations,
"failed_validations": failed_validations,
"rules_applied": num_rules,
// "git_repositories": num_git_repos,
// "commits": num_commits,
"blobs_scanned": matcher_stats.blobs_scanned,
// "files_read": num_files,
"bytes_scanned": matcher_stats.bytes_scanned,
"scan_duration": duration.as_secs_f64(),
"findings_by_rule": sorted_findings
});
// only printing to stdout, not to the file itself
safe_println!("{}", summary.to_string());
} else if args.output_args.format == ReportOutputFormat::Pretty
|| args.output_args.output.is_some()
@ -133,37 +156,23 @@ pub fn print_scan_summary(
failed_validations.separate_with_commas()
);
safe_println!(" |Rules Applied...............: {}", num_rules.separate_with_commas());
// safe_println!(" |Git Repositories............: {}",
// num_git_repos.separate_with_commas()); safe_println!(
// "|__Commits...................: {}",
// num_commits.separate_with_commas()
// );
safe_println!(
" |__Blobs Scanned.............: {}",
matcher_stats.blobs_scanned.separate_with_commas()
);
// safe_println!(" |Files Read..................: {}",
// num_files.separate_with_commas());
safe_println!(
" |Bytes Scanned...............: {}",
HumanBytes(matcher_stats.bytes_scanned)
);
safe_println!(
" |Scan Duration...............: {}",
// HumanDuration(duration),
humantime::format_duration(duration)
);
safe_println!(" |Scan Duration...............: {}", humantime::format_duration(duration));
}
if args.rule_stats {
if let Some(prof) = profiler {
let stats = prof.generate_report();
if !stats.is_empty() {
// Calculate dynamic column widths
let name_w = stats.iter().map(|s| s.rule_name.len()).max().unwrap_or(4);
let id_w = stats.iter().map(|s| s.rule_id.len()).max().unwrap_or(2);
// Header
safe_println!("\n{:-^1$}", " Rule Performance Stats ", name_w + id_w + 47);
safe_println!(
"{: <name_w$} {: <id_w$} {: >8} {: >15} {: >15}",
@ -177,7 +186,6 @@ pub fn print_scan_summary(
);
safe_println!("{:-<width$}", "", width = name_w + id_w + 49);
// Rows
for rs in stats {
safe_println!(
"{: <name_w$} {: <id_w$} {: >8} {: >15?} {: >15?}",

59
tests/int_quiet.rs Normal file
View file

@ -0,0 +1,59 @@
use assert_cmd::Command;
use predicates::prelude::*;
const FORMATS: [&str; 4] = ["pretty", "json", "jsonl", "bson"];
fn contains_bytes(haystack: &[u8], needle: &[u8]) -> bool {
haystack.windows(needle.len()).any(|window| window == needle)
}
#[test]
fn scan_quiet_suppresses_summary() {
for format in FORMATS {
Command::cargo_bin("kingfisher")
.unwrap()
.env("NO_COLOR", "1")
.args([
"scan",
"testdata/slack_tokens.properties",
"--confidence=low",
"--format",
format,
"--no-update-check",
"--no-validate",
"--quiet",
])
.assert()
.code(200)
.stdout(predicate::function(|out: &[u8]| !contains_bytes(out, b"Scan Summary")))
.stdout(predicate::function(|out: &[u8]| {
!contains_bytes(out, b"Rule Performance Stats")
}));
}
}
#[test]
fn scan_quiet_with_rule_stats_prints_rule_stats() {
for format in FORMATS {
Command::cargo_bin("kingfisher")
.unwrap()
.env("NO_COLOR", "1")
.args([
"scan",
"testdata/slack_tokens.properties",
"--confidence=low",
"--format",
format,
"--no-update-check",
"--quiet",
"--no-validate",
"--rule-stats",
])
.assert()
.code(200)
.stdout(predicate::function(|out: &[u8]| !contains_bytes(out, b"Scan Summary")))
.stdout(predicate::function(|out: &[u8]| {
contains_bytes(out, b"Rule Performance Stats")
}));
}
}