Fixed broken pagerduty rule

This commit is contained in:
Mick Grove 2025-06-25 20:56:24 -07:00
commit e7e391ab98
8 changed files with 50 additions and 46 deletions

View file

@ -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

View file

@ -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

View file

@ -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":']
status: [200]
- type: WordMatch
words: ['"user":']

View file

@ -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;
}
}

View file

@ -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<ConcurrentRuleProfiler>>,
) {
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();

View file

@ -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!("{:-<width$}", "", width = name_w + id_w + 49);
@ -187,14 +187,13 @@ pub fn print_scan_summary(
rs.slowest_match_time,
rs.average_match_time,
name_w = name_w,
id_w = id_w
id_w = id_w
);
}
}
}
}
debug!("\nAll Rules with Matches:");
debug!("=======================");
let max_rule_length = sorted_findings.iter().map(|(rule, _)| rule.len()).max().unwrap_or(0);

View file

@ -30,7 +30,7 @@ fn scan_fails_for_bad_rule_yaml() {
"--rules-path",
tmp.path().to_str().unwrap(), // point loader at bad YAML
"--no-validate", // keep the test fast
"--no-update-check", // skip update check to avoid network calls
"--no-update-check", // skip update check to avoid network calls
])
.assert()
.failure()
@ -72,7 +72,7 @@ rules:
tmp.path().to_str().unwrap(), // only the custom rule
"--no-dedup",
"--load-builtins=false", // skip the builtin rules
"--no-update-check", // skip update check to avoid network calls
"--no-update-check", // skip update check to avoid network calls
])
.assert()
.failure() // CLI exits 0

View file

@ -30,7 +30,14 @@ fn smoke_scan_tar_gz_archive() -> 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));