diff --git a/CHANGELOG.md b/CHANGELOG.md index 689cb46..453c0d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. +## [1.13.1] +- Fixed broken pagerduty rule + ## [1.13.0] - Added new rules for Planetscale, Postman, Openweather, opsgenie, pagerduty, pastebin, paypal, netlify, netrc, newrelic, ngrok, npm, nuget, mandrill, mapbox, microsoft teams, stripe, linkedin, mailchimp, mailgun, linear, line, huggingface, ibm cloud, intercom, ipstack, heroku, gradle, grafana - Added `--rule-stats` command-line flag that will display rule performance statistics during a scan. Useful when creating or debugging rules diff --git a/Cargo.toml b/Cargo.toml index 2b30bee..13e750d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ publish = false [package] name = "kingfisher" -version = "1.13.0" +version = "1.13.1" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/data/rules/pagerdutyapikey.yml b/data/rules/pagerdutyapikey.yml index be4b24f..f37be3f 100644 --- a/data/rules/pagerdutyapikey.yml +++ b/data/rules/pagerdutyapikey.yml @@ -2,35 +2,46 @@ rules: - name: PagerDuty API Key id: kingfisher.pagerduty.1 pattern: | - (?xi) + (?xi) \b - (?:pagerduty|pager[_-]duty|pd[-_\]=\)]|pd\.webhook?) - (?:.|[\n\r]){0,16}? - ( - u\+[A-Z0-9_+-]{18} # new personal tokens - | - [A-Z0-9_-]{20} # legacy personal tokens - | - [A-F0-9]{32} # integration keys / routing keys + (?: + Token | + Authorization | + pd[_-]? | + pd[_-]? | + pagerduty[_-]? | + pagerduty ) - \b - min_entropy: 3.3 + (?:.|[\n\r]){0,16}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN) + (?:.|[\n\r]){0,16}? + ( + u\+[A-Z0-9_+-]{18} | # personal user token (20 chars) + [A-Z0-9_-]{20} | # legacy PAT (20 chars, mixed case) + [a-f0-9]{32} # integration / routing key (32 hex, lower case) + ) + \b + min_entropy: 3.5 confidence: medium examples: - - pagerduty_key = u+Lyhd2_N2MCy+ZoH-S5 + - "Authorization: Token token=u+Lyhd2_N2MCy+ZoH-S5" - pd_key = u+3xVszZ-b4m+T6d23KA + - Token token=ABCDEF1234567890ABCDEF1234567890 + references: + - https://developer.pagerduty.com/api-reference/c96e889522dd6-list-users validation: type: Http content: request: method: GET - url: https://api.pagerduty.com/abilities + url: https://api.pagerduty.com/users headers: Authorization: Token token={{ TOKEN }} Accept: application/vnd.pagerduty+json;version=2 + Content-Type: application/json response_matcher: - report_response: true - type: StatusMatch - status: [200] - - type: WordMatch - words: ['"abilities":'] \ No newline at end of file + status: [200] + - type: WordMatch + words: ['"user":'] diff --git a/src/decompress.rs b/src/decompress.rs index 0ac0e0e..88a11ae 100644 --- a/src/decompress.rs +++ b/src/decompress.rs @@ -382,16 +382,11 @@ mod tests { Ok(()) } - /// 3) Nested archive: - /// outer.tar.gz ──▶ outer.tar (contains inner.tar.gz) - /// └──▶ inner.tar.gz ──▶ inner.tar (contains secret.txt) + /// 3) Nested archive: outer.tar.gz ──▶ outer.tar (contains inner.tar.gz) └──▶ inner.tar.gz + /// ──▶ inner.tar (contains secret.txt) #[test] fn smoke_decompress_nested_tar_gz_archives() -> anyhow::Result<()> { - use std::{ - fs::File, - io::Read, - path::PathBuf, - }; + use std::{fs::File, io::Read, path::PathBuf}; use flate2::{write::GzEncoder, Compression}; use tar::Builder; @@ -468,10 +463,7 @@ mod tests { for (logical, path) in files { if logical.ends_with("!secret.txt") { let txt = std::fs::read_to_string(&path)?; - assert!( - txt.contains("nested_secret=shh"), - "secret.txt content corrupted" - ); + assert!(txt.contains("nested_secret=shh"), "secret.txt content corrupted"); found = true; } } diff --git a/src/matcher.rs b/src/matcher.rs index 89aed1b..1a35b5f 100644 --- a/src/matcher.rs +++ b/src/matcher.rs @@ -25,7 +25,6 @@ use smallvec::SmallVec; use tracing::debug; use xxhash_rust::xxh3::xxh3_64; -use crate::rule_profiling::RuleTimer; use crate::{ blob::{Blob, BlobId, BlobIdMap}, entropy::calculate_shannon_entropy, @@ -33,7 +32,7 @@ use crate::{ origin::OriginSet, parser, parser::{Checker, Language}, - rule_profiling::{ConcurrentRuleProfiler, RuleStats}, + rule_profiling::{ConcurrentRuleProfiler, RuleStats, RuleTimer}, rules::rule::Rule, rules_database::RulesDatabase, safe_list::is_safe_match, @@ -464,15 +463,8 @@ fn filter_match<'b>( filename: &str, profiler: Option<&Arc>, ) { - let mut timer = profiler.map(|p| { - RuleTimer::new( - p, - rule.id(), - rule.name(), - &rule.syntax.pattern, - filename, - ) - }); + let mut timer = + profiler.map(|p| RuleTimer::new(p, rule.id(), rule.name(), &rule.syntax.pattern, filename)); let initial_len = matches.len(); diff --git a/src/scanner/summary.rs b/src/scanner/summary.rs index 11a45f9..7555d22 100644 --- a/src/scanner/summary.rs +++ b/src/scanner/summary.rs @@ -161,7 +161,7 @@ pub fn print_scan_summary( 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); + 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); @@ -173,7 +173,7 @@ pub fn print_scan_summary( "Slowest", "Average", name_w = name_w, - id_w = id_w + id_w = id_w ); safe_println!("{:- anyhow::Result<()> { // ── 1) extraction ENABLED -- secret should be found ───────────────────────── Command::cargo_bin("kingfisher")? - .args(["scan", tar_gz.to_str().unwrap(), "--confidence=low", "--format", "json", "--no-update-check"]) + .args([ + "scan", + tar_gz.to_str().unwrap(), + "--confidence=low", + "--format", + "json", + "--no-update-check", + ]) .assert() .code(findings_code) .stdout(predicates::str::contains(github_pat));