This commit is contained in:
Mick Grove 2026-02-09 12:11:35 -08:00
commit 2866367c2e
18 changed files with 49 additions and 24 deletions

View file

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

View file

@ -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");

View file

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

View file

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

View file

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

View file

@ -197,6 +197,7 @@ mod tests {
no_ignore_if_contains: false,
validation_timeout: 10,
validation_retries: 1,
full_validation_response: false,
}
}