forked from mirrors/kingfisher
JWT tokens without both 'iss' and 'aud' are no longer reported as active credentials
This commit is contained in:
parent
de181634cb
commit
b71fb5e6e2
5 changed files with 85 additions and 7 deletions
|
|
@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
## [1.36.0]
|
||||
- Fixed GitHub organization and GitLab group scans when using `--git-history=none`
|
||||
- JWT tokens without both `iss` and `aud` are no longer reported as active credentials
|
||||
|
||||
## [1.35.0]
|
||||
- Remote scans with `--git-history=none` now clone repositories with a working tree and scan the current files instead of erroring with "No inputs to scan".
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ rules:
|
|||
visible: false
|
||||
confidence: medium
|
||||
examples:
|
||||
- example-jira.atlassian.net
|
||||
- examplefoo-jira.atlassian.net
|
||||
- jira.sprintUri= https://example.atlassian.net/rest
|
||||
|
||||
- name: Jira Token
|
||||
|
|
|
|||
|
|
@ -335,12 +335,6 @@ pub async fn run_secret_validation(
|
|||
ds.replace_matches(updated_arcs);
|
||||
}
|
||||
|
||||
// ── 5. Done ─────────────────────────────────────────────────────────────
|
||||
println!(
|
||||
"Validation complete – {} succeeded, {} failed",
|
||||
success_count.load(Ordering::Relaxed),
|
||||
fail_count.load(Ordering::Relaxed)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,11 @@ pub async fn validate_jwt(token: &str) -> Result<(bool, String)> {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
let issuer = claims.iss.clone().unwrap_or_default();
|
||||
let aud_strings = extract_aud_strings(&claims);
|
||||
|
||||
if issuer.trim().is_empty() && aud_strings.iter().all(|s| s.trim().is_empty()) {
|
||||
return Ok((false, "JWT missing issuer and audience".to_string()));
|
||||
}
|
||||
if let Some(iss) = claims.iss.clone() {
|
||||
// parse header now (kid, alg)
|
||||
let header = decode_header(token).map_err(|e| anyhow!("decode header: {e}"))?;
|
||||
|
|
|
|||
79
tests/int_rules_no_validated_findings.rs
Normal file
79
tests/int_rules_no_validated_findings.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
use anyhow::Result;
|
||||
use assert_cmd::Command;
|
||||
use serde_json::Value;
|
||||
|
||||
#[test]
|
||||
fn scan_rules_has_no_validated_findings() -> Result<()> {
|
||||
let output = Command::cargo_bin("kingfisher")?
|
||||
.args([
|
||||
"scan", "data/rules",
|
||||
"--format", "json",
|
||||
"--no-update-check",
|
||||
"--only-valid",
|
||||
])
|
||||
.output()?;
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
// Find the first '[' — start of array
|
||||
let start = match stdout.find('[') {
|
||||
Some(i) => i,
|
||||
None => return Ok(()), // no array found
|
||||
};
|
||||
|
||||
let mut depth = 0usize;
|
||||
let mut end = None;
|
||||
for (i, ch) in stdout.char_indices().skip(start) {
|
||||
match ch {
|
||||
'[' => depth += 1,
|
||||
']' => {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
end = Some(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let json_array_str = match end {
|
||||
Some(end_idx) => &stdout[start..=end_idx],
|
||||
None => return Ok(()), // no matching close found
|
||||
};
|
||||
|
||||
if json_array_str.trim().is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let findings: Vec<Value> = serde_json::from_str(json_array_str)?;
|
||||
|
||||
for finding in findings {
|
||||
let rule_id = finding["rule"]["id"].as_str().unwrap_or("unknown");
|
||||
let rule_prevalidated = finding["rule"]["prevalidated"].as_bool().unwrap_or(false);
|
||||
|
||||
let status = finding["finding"]["validation"]["status"]
|
||||
.as_str()
|
||||
.unwrap_or("")
|
||||
.to_ascii_lowercase();
|
||||
|
||||
let response = finding["finding"]["validation"]["response"]
|
||||
.as_str()
|
||||
.unwrap_or("")
|
||||
.to_ascii_lowercase();
|
||||
|
||||
// Skip anything intentionally marked as prevalidated
|
||||
if rule_prevalidated || status == "prevalidated" || response == "prevalidated" {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fail only on genuinely validated secrets
|
||||
assert_ne!(
|
||||
status.as_str(),
|
||||
"active credential",
|
||||
"Validated finding detected in rule {rule_id}"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue