forked from mirrors/kingfisher
v1.80.0
This commit is contained in:
parent
ec8761c451
commit
2866367c2e
18 changed files with 49 additions and 24 deletions
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [v1.80.0]
|
||||
- Added `--full-validation-response` flag to include complete validation response bodies without truncation. By default, validation responses are still truncated to 512 characters for readability. When enabled, users can parse and present full validation responses as needed (e.g., for GitHub token validation responses that include user metadata beyond the first 512 characters).
|
||||
|
||||
## [v1.79.0]
|
||||
- Added revocation support for SendGrid, Tailscale, MongoDB Atlas, Twilio, and NPM using multi-step (lookup ID then delete) pattern.
|
||||
- Added new Sumo Logic rule with direct revocation support.
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ publish = false
|
|||
|
||||
[package]
|
||||
name = "kingfisher"
|
||||
version = "1.79.0"
|
||||
version = "1.80.0"
|
||||
description = "MongoDB's blazingly fast and accurate secret scanning and validation tool"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ rules:
|
|||
\b
|
||||
(
|
||||
(?:A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)
|
||||
[2-7A-Z]{16}
|
||||
[A-Z0-9]{16}
|
||||
)
|
||||
\b
|
||||
pattern_requirements:
|
||||
|
|
|
|||
|
|
@ -102,6 +102,10 @@ pub struct ScanArgs {
|
|||
)]
|
||||
pub validation_retries: u32,
|
||||
|
||||
/// Include full validation response bodies without truncation
|
||||
#[arg(global = true, long, default_value_t = false)]
|
||||
pub full_validation_response: bool,
|
||||
|
||||
/// Map validated cloud credentials to their effective identities; use only when
|
||||
/// authorized for the target account because this triggers additional network
|
||||
/// requests to determine granted access
|
||||
|
|
|
|||
|
|
@ -222,9 +222,7 @@ fn render_extractor(
|
|||
let template = parser
|
||||
.parse(template_str)
|
||||
.map_err(|e| anyhow!("Failed to parse extractor template: {}", e))?;
|
||||
template
|
||||
.render(globals)
|
||||
.map_err(|e| anyhow!("Failed to render extractor template: {}", e))
|
||||
template.render(globals).map_err(|e| anyhow!("Failed to render extractor template: {}", e))
|
||||
};
|
||||
|
||||
match extractor {
|
||||
|
|
@ -234,9 +232,7 @@ fn render_extractor(
|
|||
ResponseExtractor::Regex { pattern } => {
|
||||
Ok(ResponseExtractor::Regex { pattern: render(pattern)? })
|
||||
}
|
||||
ResponseExtractor::Header { name } => {
|
||||
Ok(ResponseExtractor::Header { name: render(name)? })
|
||||
}
|
||||
ResponseExtractor::Header { name } => Ok(ResponseExtractor::Header { name: render(name)? }),
|
||||
// Body and StatusCode have no string fields to render
|
||||
other => Ok(other.clone()),
|
||||
}
|
||||
|
|
@ -417,8 +413,8 @@ async fn execute_revocation_step(
|
|||
|
||||
for (var_name, extractor) in extractors {
|
||||
// Render any Liquid templates in the extractor (e.g., {{ TOKEN | prefix: 8 }})
|
||||
let rendered_extractor = render_extractor(extractor, parser, globals)
|
||||
.with_context(|| {
|
||||
let rendered_extractor =
|
||||
render_extractor(extractor, parser, globals).with_context(|| {
|
||||
format!(
|
||||
"Failed to render extractor template for '{}' in step {}",
|
||||
var_name, step_number
|
||||
|
|
@ -1252,7 +1248,10 @@ mod tests {
|
|||
.unwrap();
|
||||
let mut globals = Object::new();
|
||||
// kingfisher:ignore (test fixture, not a real token)
|
||||
globals.insert("TOKEN".into(), Value::scalar("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".to_string()));
|
||||
globals.insert(
|
||||
"TOKEN".into(),
|
||||
Value::scalar("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".to_string()),
|
||||
);
|
||||
|
||||
let extractor = ResponseExtractor::Regex {
|
||||
pattern: r#""key":"([^"]+)","token":"{{ TOKEN | prefix: 8 }}"#.to_string(),
|
||||
|
|
@ -1274,7 +1273,10 @@ mod tests {
|
|||
.unwrap();
|
||||
let mut globals = Object::new();
|
||||
// kingfisher:ignore (test fixture, not a real token)
|
||||
globals.insert("TOKEN".into(), Value::scalar("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".to_string()));
|
||||
globals.insert(
|
||||
"TOKEN".into(),
|
||||
Value::scalar("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".to_string()),
|
||||
);
|
||||
|
||||
let extractor = ResponseExtractor::Regex {
|
||||
pattern: r#""key":"([^"]+)","token":"{{ TOKEN | prefix: 8 }}"#.to_string(),
|
||||
|
|
@ -1284,13 +1286,9 @@ mod tests {
|
|||
// Simulated npm API response with multiple tokens
|
||||
let body = r#"{"objects":[{"key":"e089a40c-800b-4ec0-95b1-c17a63305887","token":"npm_yJcQ...rEf1"},{"key":"43c14e2d-8b5d-4f8b-91cd-280a7afead0c","token":"npm_rmll...aQIU"},{"key":"1ced5278-29a9-4266-bf8e-03223bc9c30c","token":"npm_ahWC...2pw1"}]}"#;
|
||||
|
||||
let result = extract_value_from_response(
|
||||
&rendered,
|
||||
body,
|
||||
&HeaderMap::new(),
|
||||
&StatusCode::OK,
|
||||
)
|
||||
.unwrap();
|
||||
let result =
|
||||
extract_value_from_response(&rendered, body, &HeaderMap::new(), &StatusCode::OK)
|
||||
.unwrap();
|
||||
|
||||
// Should extract the key for the token matching prefix "npm_rmll", NOT the first one
|
||||
assert_eq!(result, "43c14e2d-8b5d-4f8b-91cd-280a7afead0c");
|
||||
|
|
|
|||
|
|
@ -866,6 +866,7 @@ pub(crate) fn create_minimal_scan_args() -> crate::cli::commands::scan::ScanArgs
|
|||
no_ignore_if_contains: false,
|
||||
validation_timeout: 10,
|
||||
validation_retries: 1,
|
||||
full_validation_response: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -547,6 +547,7 @@ fn create_default_scan_args() -> cli::commands::scan::ScanArgs {
|
|||
no_ignore_if_contains: false,
|
||||
validation_timeout: 10,
|
||||
validation_retries: 1,
|
||||
full_validation_response: false,
|
||||
}
|
||||
}
|
||||
/// Run the rules check command
|
||||
|
|
|
|||
|
|
@ -662,12 +662,16 @@ impl DetailsReporter {
|
|||
"Inactive Credential".to_string()
|
||||
};
|
||||
|
||||
const MAX_RESPONSE_LENGTH: usize = 512;
|
||||
let validation_body_str = validation_body::as_str(&rm.validation_response_body);
|
||||
let truncated_body: String =
|
||||
validation_body_str.chars().take(MAX_RESPONSE_LENGTH).collect();
|
||||
let ellipsis = if validation_body_str.len() > MAX_RESPONSE_LENGTH { "..." } else { "" };
|
||||
let response_body = format!("{}{}", truncated_body, ellipsis);
|
||||
let response_body = if args.full_validation_response {
|
||||
validation_body_str.to_string()
|
||||
} else {
|
||||
const MAX_RESPONSE_LENGTH: usize = 512;
|
||||
let truncated_body: String =
|
||||
validation_body_str.chars().take(MAX_RESPONSE_LENGTH).collect();
|
||||
let ellipsis = if validation_body_str.len() > MAX_RESPONSE_LENGTH { "..." } else { "" };
|
||||
format!("{}{}", truncated_body, ellipsis)
|
||||
};
|
||||
|
||||
let git_metadata_val = rm
|
||||
.origin
|
||||
|
|
@ -1237,6 +1241,7 @@ mod tests {
|
|||
no_ignore_if_contains: false,
|
||||
validation_timeout: 10,
|
||||
validation_retries: 1,
|
||||
full_validation_response: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -197,6 +197,7 @@ mod tests {
|
|||
no_ignore_if_contains: false,
|
||||
validation_timeout: 10,
|
||||
validation_retries: 1,
|
||||
full_validation_response: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -159,6 +159,7 @@ fn run_skiplist(skip_regex: Vec<String>, skip_skipword: Vec<String>) -> Result<u
|
|||
no_ignore_if_contains: false,
|
||||
validation_retries: 1,
|
||||
validation_timeout: 10,
|
||||
full_validation_response: false,
|
||||
};
|
||||
|
||||
let global_args = GlobalArgs {
|
||||
|
|
|
|||
|
|
@ -159,6 +159,7 @@ fn test_bitbucket_remote_scan() -> Result<()> {
|
|||
no_ignore_if_contains: false,
|
||||
validation_retries: 1,
|
||||
validation_timeout: 10,
|
||||
full_validation_response: false,
|
||||
};
|
||||
|
||||
let global_args = GlobalArgs {
|
||||
|
|
|
|||
|
|
@ -179,6 +179,7 @@ rules:
|
|||
no_ignore_if_contains: false,
|
||||
validation_retries: 1,
|
||||
validation_timeout: 10,
|
||||
full_validation_response: false,
|
||||
};
|
||||
|
||||
let global_args = GlobalArgs {
|
||||
|
|
|
|||
|
|
@ -166,6 +166,7 @@ fn test_github_remote_scan() -> Result<()> {
|
|||
no_ignore_if_contains: false,
|
||||
validation_retries: 1,
|
||||
validation_timeout: 10,
|
||||
full_validation_response: false,
|
||||
};
|
||||
// Create global arguments
|
||||
let global_args = GlobalArgs {
|
||||
|
|
|
|||
|
|
@ -164,6 +164,7 @@ fn test_gitlab_remote_scan() -> Result<()> {
|
|||
no_ignore_if_contains: false,
|
||||
validation_retries: 1,
|
||||
validation_timeout: 10,
|
||||
full_validation_response: false,
|
||||
};
|
||||
|
||||
let global_args = GlobalArgs {
|
||||
|
|
@ -328,6 +329,7 @@ fn test_gitlab_remote_scan_no_history() -> Result<()> {
|
|||
view_report: false,
|
||||
validation_retries: 1,
|
||||
validation_timeout: 10,
|
||||
full_validation_response: false,
|
||||
};
|
||||
|
||||
let global_args = GlobalArgs {
|
||||
|
|
|
|||
|
|
@ -142,6 +142,7 @@ async fn test_redact_hashes_finding_values() -> Result<()> {
|
|||
no_ignore_if_contains: false,
|
||||
validation_retries: 1,
|
||||
validation_timeout: 10,
|
||||
full_validation_response: false,
|
||||
};
|
||||
|
||||
let global_args = GlobalArgs {
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@ impl TestContext {
|
|||
no_ignore_if_contains: false,
|
||||
validation_retries: 1,
|
||||
validation_timeout: 10,
|
||||
full_validation_response: false,
|
||||
};
|
||||
|
||||
let loaded = RuleLoader::from_rule_specifiers(&scan_args.rules).load(&scan_args)?;
|
||||
|
|
@ -297,6 +298,7 @@ async fn test_scan_slack_messages() -> Result<()> {
|
|||
view_report: false,
|
||||
validation_retries: 1,
|
||||
validation_timeout: 10,
|
||||
full_validation_response: false,
|
||||
};
|
||||
|
||||
let global_args = GlobalArgs {
|
||||
|
|
|
|||
|
|
@ -222,6 +222,7 @@ async fn test_validation_cache_and_depvars() -> Result<()> {
|
|||
no_ignore_if_contains: false,
|
||||
validation_retries: 1,
|
||||
validation_timeout: 10,
|
||||
full_validation_response: false,
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------- *
|
||||
|
|
|
|||
|
|
@ -165,6 +165,7 @@ impl TestContext {
|
|||
no_ignore_if_contains: false,
|
||||
validation_retries: 1,
|
||||
validation_timeout: 10,
|
||||
full_validation_response: false,
|
||||
};
|
||||
|
||||
let loaded = RuleLoader::from_rule_specifiers(&scan_args.rules)
|
||||
|
|
@ -302,6 +303,7 @@ impl TestContext {
|
|||
no_ignore_if_contains: false,
|
||||
validation_retries: 1,
|
||||
validation_timeout: 10,
|
||||
full_validation_response: false,
|
||||
};
|
||||
|
||||
let global_args = GlobalArgs {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue