From bd27f7936a1d99f181875f95dbac2a91fe5eff5c Mon Sep 17 00:00:00 2001 From: ajianaz Date: Sat, 27 Jun 2026 12:31:57 +0700 Subject: [PATCH 1/3] fix(review): add post-LLM filter for hardcoded secret false positives Cora LLM sometimes flags struct field declarations (e.g. api_key: String) as 'Hardcoded password or secret in variable' even when no literal value is present. The built-in sec-hardcoded-secret regex only matches actual assignments like api_key = "sk-...", but the LLM can hallucinate these findings regardless of .cora.yaml rules. Add apply_llm_secret_fp_filter that cross-validates LLM security findings against actual diff lines. If a finding about hardcoded secrets points to a line that doesn't match the sec-hardcoded-secret regex, it's removed as a false positive. Unknown/unresolvable lines are kept (safe default). 4 unit tests cover: FP removal, real secret retention, non-security pass-through, and unknown-line preservation. --- src/engine/review.rs | 268 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 268 insertions(+) diff --git a/src/engine/review.rs b/src/engine/review.rs index 81b8c7c..db0ae5f 100644 --- a/src/engine/review.rs +++ b/src/engine/review.rs @@ -329,6 +329,14 @@ async fn review_diff_inner( } } + // Cross-validate LLM security findings about hardcoded secrets against + // actual diff lines. The LLM sometimes flags struct field declarations + // (e.g. `api_key: String`) as "hardcoded secret" even when no literal + // value is present. This filter removes such false positives by checking + // the added line at the reported file:line against the built-in + // sec-hardcoded-secret regex. + response.issues = apply_llm_secret_fp_filter(response.issues, &diff_chunks); + // Apply ignore rules: filter out issues matching ignored patterns response.issues = apply_ignore_rules(response.issues, &config.ignore.rules); @@ -362,6 +370,108 @@ async fn review_diff_inner( Ok(response) } +/// Filter out LLM findings about hardcoded secrets/passwords that point to +/// diff lines which don't actually contain a literal string assignment. +/// +/// The LLM sometimes flags struct field declarations like `api_key: String` +/// or `api_key: extract_api_key.clone()` as "Hardcoded password or secret in +/// variable". These are identifiers, not hardcoded values. +/// +/// This function cross-validates each security finding against the actual +/// added line in the diff. If the line doesn't match the `sec-hardcoded-secret` +/// regex (i.e. no `password/key/secret = "literal"` pattern), the finding is +/// removed as a false positive. +fn apply_llm_secret_fp_filter( + mut issues: Vec, + diff_chunks: &[crate::engine::diff_parser::FileChunk], +) -> Vec { + use crate::engine::diff_parser::DiffLineType; + + // Lazy-compiled regex matching the built-in sec-hardcoded-secret pattern. + // Only triggers for actual value assignments like `api_key = "sk-..."`. + static RE_SECRET_LITERAL: std::sync::LazyLock = std::sync::LazyLock::new(|| { + regex::Regex::new(r#"(?i)(?:password|api_?key|token|secret)\s*=\s*"[^"]+""#) + .expect("hardcoded secret regex must compile") + }); + + // Keywords that indicate an LLM finding is about hardcoded secrets. + static SECRET_KEYWORDS: &[&str] = &[ + "hardcoded password", + "hardcoded secret", + "hardcoded credential", + "hardcoded token", + "hardcoded api key", + "hardcoded api_key", + ]; + + // Pre-compute a lookup: (file_path, new_line_no) -> line content + let added_lines: std::collections::HashMap<(String, u32), &str> = diff_chunks + .iter() + .flat_map(|chunk| { + let path = chunk + .new_path + .as_deref() + .or(chunk.old_path.as_deref()) + .unwrap_or("unknown"); + chunk.chunks.iter().flat_map(|hunk| { + hunk.lines + .iter() + .filter(|l| l.line_type == DiffLineType::Add) + .filter_map(|l| { + l.new_line_no + .map(|ln| ((path.to_string(), ln), l.content.as_str())) + }) + }) + }) + .collect(); + + let before = issues.len(); + issues.retain(|issue| { + // Only check security-type findings about secrets + let issue_type = issue.issue_type.as_deref().unwrap_or(""); + let title_lower = issue.title.to_lowercase(); + + if issue_type != "security" { + return true; + } + + let is_secret_finding = SECRET_KEYWORDS.iter().any(|kw| title_lower.contains(kw)); + if !is_secret_finding { + return true; + } + + // Look up the actual diff line + let line_num = issue.line.unwrap_or(0); + let key = (issue.file.clone(), line_num); + if let Some(actual_line) = added_lines.get(&key) { + if !RE_SECRET_LITERAL.is_match(actual_line) { + debug!( + file = %issue.file, + line = line_num, + title = %issue.title, + "suppressed LLM false positive: line has no hardcoded secret literal" + ); + return false; // Remove this finding + } + } + + // If we can't find the line (hallucinated path/line or context line), + // keep the finding — better safe than sorry. + true + }); + + let filtered = before - issues.len(); + if filtered > 0 { + debug!( + filtered, + remaining = issues.len(), + "filtered LLM false positives for hardcoded secret findings" + ); + } + + issues +} + /// Filter out issues whose `issue_type` matches any ignored rule pattern. fn apply_ignore_rules(mut issues: Vec, ignore_rules: &[String]) -> Vec { if ignore_rules.is_empty() { @@ -389,6 +499,7 @@ fn is_valid_file_path(issue_file: &str, valid_files: &[String]) -> bool { #[cfg(test)] mod tests { use super::*; + use crate::engine::Severity; #[test] fn resolve_prompt_inline_takes_priority() { @@ -427,4 +538,161 @@ mod tests { "system_prompt_file outside project root should be rejected" ); } + + #[test] + fn secret_fp_filter_removes_struct_field_declarations() { + use crate::engine::diff_parser::*; + + // Simulate a diff with a struct field declaration (not a hardcoded secret) + let diff_chunks = vec![FileChunk { + old_path: None, + new_path: Some("crates/uteke-cli/src/cli.rs".to_string()), + language: "rs".to_string(), + chunks: vec![DiffHunk { + old_start: 230, + old_count: 0, + new_start: 234, + new_count: 2, + header: "".to_string(), + lines: vec![ + DiffLine { + line_type: DiffLineType::Add, + content: " extract_api_key: Option,".to_string(), + old_line_no: None, + new_line_no: Some(236), + }, + DiffLine { + line_type: DiffLineType::Add, + content: " extract_base_url: Option,".to_string(), + old_line_no: None, + new_line_no: Some(237), + }, + ], + }], + is_binary: false, + is_deleted: false, + is_new: false, + }]; + + let issues = vec![ReviewIssue { + file: "crates/uteke-cli/src/cli.rs".to_string(), + line: Some(236), + severity: Severity::Critical, + issue_type: Some("security".to_string()), + title: "Hardcoded password or secret in variable".to_string(), + body: "Static security scanner detected...".to_string(), + suggested_fix: None, + }]; + + let result = apply_llm_secret_fp_filter(issues, &diff_chunks); + assert!( + result.is_empty(), + "struct field declaration should be filtered out" + ); + } + + #[test] + fn secret_fp_filter_keeps_actual_hardcoded_secrets() { + use crate::engine::diff_parser::*; + + let diff_chunks = vec![FileChunk { + old_path: None, + new_path: Some("src/config.rs".to_string()), + language: "rs".to_string(), + chunks: vec![DiffHunk { + old_start: 10, + old_count: 0, + new_start: 15, + new_count: 1, + header: "".to_string(), + lines: vec![DiffLine { + line_type: DiffLineType::Add, + content: " let api_key = \"sk-12345abcdef\";".to_string(), + old_line_no: None, + new_line_no: Some(15), + }], + }], + is_binary: false, + is_deleted: false, + is_new: false, + }]; + + let issues = vec![ReviewIssue { + file: "src/config.rs".to_string(), + line: Some(15), + severity: Severity::Critical, + issue_type: Some("security".to_string()), + title: "Hardcoded password or secret in variable".to_string(), + body: "API key hardcoded...".to_string(), + suggested_fix: None, + }]; + + let result = apply_llm_secret_fp_filter(issues, &diff_chunks); + assert_eq!(result.len(), 1, "actual hardcoded secret should be kept"); + } + + #[test] + fn secret_fp_filter_keeps_non_security_findings() { + use crate::engine::diff_parser::*; + + let diff_chunks = vec![FileChunk { + old_path: None, + new_path: Some("src/main.rs".to_string()), + language: "rs".to_string(), + chunks: vec![DiffHunk { + old_start: 1, + old_count: 0, + new_start: 1, + new_count: 1, + header: "".to_string(), + lines: vec![DiffLine { + line_type: DiffLineType::Add, + content: " api_key: String,".to_string(), + old_line_no: None, + new_line_no: Some(1), + }], + }], + is_binary: false, + is_deleted: false, + is_new: false, + }]; + + let issues = vec![ReviewIssue { + file: "src/main.rs".to_string(), + line: Some(1), + severity: Severity::Minor, + issue_type: Some("bugs".to_string()), + title: "Use of unwrap()".to_string(), + body: "This can panic".to_string(), + suggested_fix: None, + }]; + + let result = apply_llm_secret_fp_filter(issues, &diff_chunks); + assert_eq!(result.len(), 1, "non-security findings should pass through"); + } + + #[test] + fn secret_fp_filter_keeps_findings_with_unknown_lines() { + use crate::engine::diff_parser::*; + + // Empty diff — finding references a line not in the diff + let diff_chunks: Vec = vec![]; + + let issues = vec![ReviewIssue { + file: "src/config.rs".to_string(), + line: Some(999), + severity: Severity::Critical, + issue_type: Some("security".to_string()), + title: "Hardcoded password or secret in variable".to_string(), + body: "...".to_string(), + suggested_fix: None, + }]; + + let result = apply_llm_secret_fp_filter(issues, &diff_chunks); + assert_eq!( + result.len(), + 1, + "unknown lines should be kept (better safe than sorry)" + ); + } } From 25e3d16b479c7c4508427c4767978b1c47005891 Mon Sep 17 00:00:00 2001 From: ajianaz Date: Sat, 27 Jun 2026 12:47:01 +0700 Subject: [PATCH 2/3] =?UTF-8?q?fix(deps):=20bump=20git2=200.20=20=E2=86=92?= =?UTF-8?q?=200.21=20to=20resolve=20RUSTSEC=20advisories?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - git2 0.21.0 fixes RUSTSEC-2026-0183 (null ptr in Remote::list) and RUSTSEC-2026-0184 (null ptr in Blame::blame_buffer Signature) - Remote::url() API changed from Option<&str> to Result<&str, Error> - Adapted get_repo_info() to use .url().ok() chain --- Cargo.lock | 5 ++--- Cargo.toml | 2 +- src/git/diff.rs | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0d83ca1..f62c5a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -599,15 +599,14 @@ dependencies = [ [[package]] name = "git2" -version = "0.20.4" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" +checksum = "ddddbf932745a6be37109b6112d3ee09696106f848449069d3a57bba937ab82e" dependencies = [ "bitflags", "libc", "libgit2-sys", "log", - "url", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5a59478..17e16a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ serde_json = "1" serde_yaml_ng = "0.10" # Git operations -git2 = { version = "0.20", default-features = false, features = ["vendored-libgit2"] } +git2 = { version = "0.21", default-features = false, features = ["vendored-libgit2"] } # Terminal output colored = "3" diff --git a/src/git/diff.rs b/src/git/diff.rs index 4678ec0..fa6502d 100644 --- a/src/git/diff.rs +++ b/src/git/diff.rs @@ -156,7 +156,7 @@ pub fn get_repo_info() -> std::result::Result<(String, String, String), CoraErro let remote_url = repo .find_remote("origin") .ok() - .and_then(|r| r.url().map(std::string::ToString::to_string)) + .and_then(|r| r.url().ok().map(std::string::ToString::to_string)) .unwrap_or_default(); let (owner, repo_name) = parse_remote_url(&remote_url); From f51094fb4ad10d3a793656283e9b4a9974c41c86 Mon Sep 17 00:00:00 2001 From: ajianaz Date: Sat, 27 Jun 2026 12:54:00 +0700 Subject: [PATCH 3/3] =?UTF-8?q?fix(deps):=20bump=20quinn-proto=200.11.14?= =?UTF-8?q?=20=E2=86=92=200.11.15=20(RUSTSEC-2026-0185)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves remote memory exhaustion DoS vulnerability in quinn-proto unbounded out-of-order stream reassembly. --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f62c5a5..d1496d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1245,9 +1245,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.14" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e" dependencies = [ "bytes", "getrandom 0.3.4",