Skip to content

Commit 3f7d2f7

Browse files
committed
修复Maven与Gradle同根检查归属及漏洞反馈覆盖
1 parent d69076c commit 3f7d2f7

17 files changed

Lines changed: 8977 additions & 3 deletions

‎README.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -989,3 +989,7 @@ flowchart LR
989989
I --> J[Agent feedback and original native verification]
990990
J --> K[Closure requires accepted native repair evidence]
991991
```
992+
993+
## Maven and Gradle in the same build root
994+
995+
Static discovery now retains every Maven/Groovy-Gradle/Kotlin-Gradle configuration reference in a shared physical directory. A malformed POM cannot hide Gradle, and two Gradle build scripts remain separate observations. Java dependency/CVE/security aggregates keep the mixed-build or unresolved-Gradle condition and no global Maven checker identity; any available Maven-local dependency graph and advisory observations remain in `native_results`. Check feedback0.61 preserves closed normal-check and lint-only contracts, including narrowly constrained Java comments `not_configured`; prior schemas remain unchanged. This fixes scope attribution, not native Gradle plugin execution or production acceptance. The local cached Gradle8.10.2 version command ran, while the inspected OWASP Gradle plugin cache paths were absent; no installation/download was performed. See [acceptance](tests/acceptance/java-mixed-build-roots.md).

‎README.zh-CN.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -972,3 +972,7 @@ flowchart LR
972972
I --> J[智能体反馈与原工具复检]
973973
J --> K[关闭仍需通过原生修复验收]
974974
```
975+
976+
## 同构建根的 Maven 与 Gradle 归属
977+
978+
静态发现现逐份保留同一物理目录的Maven、Groovy Gradle、Kotlin Gradle配置引用。损坏POM不能遮蔽Gradle,两种Gradle脚本并存也分别保留。Java依赖/CVE/安全类别保留构建器混合或Gradle未解析状态,不再挂全范围Maven身份;已经取得的Maven局部依赖图和漏洞观察仍放在native_results。check反馈0.61保留普通检查/lint-only两个封闭契约,Java注释not_configured原因严格限于对应类别,旧schema不改。这是范围归属修复,不是原生Gradle插件执行或生产验收完成。本地缓存Gradle8.10.2版本命令已实际运行,所检查OWASP Gradle插件缓存路径不存在,本批未安装或下载。见[验收记录](tests/acceptance/java-mixed-build-roots.md)。

‎crates/codeguard-cli/src/check_command.rs‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1461,6 +1461,20 @@ fn run_scoped(args: &[String], lint_only: bool) -> ExitCode {
14611461
"status": if observed { "observed_unverified" } else if npm_selected || python_lint_selected || rust_lint_selected || rust_comments_selected || rust_build_selected || rust_cve_selected || go_lint_selected || java_lint_selected || java_comments_selected || (language == "java" && category == "dependencies" && dependency_configured) || (cve_scope_configured) { "native_incomplete" } else if language == "java" && category == "comments" { if javadoc_configuration_unresolved { "configuration_unresolved" } else { "not_configured" } } else if let Some(candidate) = java_checker.as_ref() { candidate.status } else { "not_integrated" },
14621462
"reason": if rust_cve_selected { if observed { "rust_cve_database_freshness_unverified" } else { "rust_cve_native_incomplete" } } else if npm_selected { "npm_advisory_coverage_and_freshness_unverified" } else if planned_language { "planned_language_adapter_gap" } else if observed { "trusted_policy_and_coverage_unavailable" } else if java_lint_selected && java_p3c["local_observation_complete"] == true { "p3c_declared_rulesets_unverified_coverage" } else if java_comments_selected && java_javadoc["maven_multifile_probes"].as_array().is_some_and(|probes| probes.iter().any(|probe| matches!(probe["observation"]["native_status"].as_str(), Some("findings_observed_untrusted" | "clean_log_unverified")))) { "javadoc_multifile_probe_unverified_project_coverage" } else if java_comments_selected && java_javadoc["local_probe_complete"] == true { "javadoc_single_file_probe_unverified_coverage" } else if language == "java" && category == "dependencies" && java_dependencies["observed_graph_count"].as_u64().is_some_and(|count| count > 0) { "dependency_graph_observed_unverified_coverage" } else if cve_scope_configured && java_cve["observed_report_count"].as_u64().is_some_and(|count| count > 0) { "cve_report_observed_database_unverified" } else if (language == "java" && category == "dependencies" && dependency_configured) || cve_scope_configured || python_lint_selected || rust_lint_selected || rust_comments_selected || rust_build_selected || go_lint_selected || java_lint_selected || java_comments_selected { "native_scan_incomplete" } else if language == "java" && category == "comments" { if javadoc_configuration_unresolved { "javadoc_configuration_unresolved" } else { "not_configured" } } else if let Some(candidate) = java_checker.as_ref() { candidate.reason } else { "native_adapter_not_integrated" }
14631463
});
1464+
// 原生Maven局部结果继续保留,但不能覆盖同一源码范围的Gradle未解析义务。
1465+
if let Some(scope) = java_checker.as_ref().filter(|scope| {
1466+
language == "java"
1467+
&& matches!(category, "dependencies" | "cve" | "security")
1468+
&& matches!(
1469+
scope.reason,
1470+
"checker_build_systems_mixed" | "gradle_model_not_resolved"
1471+
)
1472+
}) {
1473+
candidate["checker_id"] = json!(scope.checker_id);
1474+
candidate["status"] = json!(scope.status);
1475+
candidate["reason"] = json!(scope.reason);
1476+
candidate["next_action"] = json!(scope.next_action);
1477+
}
14641478
if language == "kotlin" && category == "lint" && kotlin_lint.is_object() {
14651479
candidate["checker_id"] = json!("kotlin.jvm.compiler");
14661480
candidate["status"] = json!("native_incomplete");
@@ -2108,8 +2122,12 @@ fn run_scoped(args: &[String], lint_only: bool) -> ExitCode {
21082122
if shell_lint.is_object() {
21092123
crate::check_shell_scan::refresh(&root, &mut shell_lint, deadline);
21102124
}
2125+
let gradle_model_unresolved = discovery
2126+
.checker_configurations
2127+
.iter()
2128+
.any(|entry| entry.checker_id.starts_with("java.gradle."));
21112129
let mut report = json!({
2112-
"schema_version":if next["schema_version"] == "0.21.0" || syntax_candidates["observations"].as_array().is_some_and(|rows| rows.iter().any(|row|row.get("javascript_mode_observation").is_some())) {"0.60.0"}else if lint_only {"0.59.0"}else if next["repair_brief"]["checker_id"] == "java.maven.p3c" && next["repair_brief"]["reason_code"] == "p3c_configuration_not_confirmed" {"0.58.0"}else if next["repair_brief"]["checker_id"] == "java.maven.javadoc" {"0.57.0"}else if next["repair_brief"]["checker_id"] == "rust.cargo_clippy" {"0.56.0"}else if syntax_candidates["observations"].as_array().is_some_and(|rows| rows.iter().any(|row| row["language"]=="erlang" && row.get("structural_observations").is_some())) {"0.55.0"} else if next["schema_version"] == "0.20.0" || syntax_candidates["observations"].as_array().is_some_and(|rows| rows.iter().any(|row| row["language"]=="javascript" && row.get("structural_observations").is_some())) {"0.54.0"}else if syntax_candidates["observations"].as_array().is_some_and(|rows|rows.iter().any(|row|row["language"]=="cfquery")) {"0.53.0"}else if shell_lint.is_object() || next["schema_version"]=="0.17.0" {"0.52.0"}else if ruby_lint.is_object() || next["schema_version"] == "0.15.0" {"0.51.0"} else if next["schema_version"] == "0.14.0" {"0.50.0"} else if next["schema_version"] == "0.13.0" || syntax_candidates["observations"].as_array().is_some_and(|rows| rows.iter().any(|row| row["language"] == "go" && row.get("structural_observations").is_some())) {"0.49.0"} else if syntax_candidates["observations"].as_array().is_some_and(|rows| rows.iter().any(|row| row.get("structural_observations").is_some())) {"0.48.0"} else if zig_lint["schema_version"] == "0.2.0" || next["schema_version"] == "0.12.0" {"0.47.0"}else if zig_lint.is_object() {"0.46.0"} else if matches!(parsed.selection, Selection::Language(_)) {"0.45.0"} else if swift_lint["schema_version"] == "0.2.0" || next["schema_version"] == "0.11.0" {"0.44.0"} else if swift_lint.is_object() {"0.43.0"} else if kotlin_lint.is_object() || next["schema_version"] == "0.10.0" {"0.42.0"} else if matches!(next["schema_version"].as_str(), Some("0.8.0" | "0.9.0")) {"0.41.0"} else if next["schema_version"] == "0.7.0" {"0.40.0"} else if next["schema_version"] == "0.6.0" {"0.39.0"} else {"0.38.0"}, "report_type":"check_feedback",
2130+
"schema_version":if gradle_model_unresolved {"0.61.0"}else if next["schema_version"] == "0.21.0" || syntax_candidates["observations"].as_array().is_some_and(|rows| rows.iter().any(|row|row.get("javascript_mode_observation").is_some())) {"0.60.0"}else if lint_only {"0.59.0"}else if next["repair_brief"]["checker_id"] == "java.maven.p3c" && next["repair_brief"]["reason_code"] == "p3c_configuration_not_confirmed" {"0.58.0"}else if next["repair_brief"]["checker_id"] == "java.maven.javadoc" {"0.57.0"}else if next["repair_brief"]["checker_id"] == "rust.cargo_clippy" {"0.56.0"}else if syntax_candidates["observations"].as_array().is_some_and(|rows| rows.iter().any(|row| row["language"]=="erlang" && row.get("structural_observations").is_some())) {"0.55.0"} else if next["schema_version"] == "0.20.0" || syntax_candidates["observations"].as_array().is_some_and(|rows| rows.iter().any(|row| row["language"]=="javascript" && row.get("structural_observations").is_some())) {"0.54.0"}else if syntax_candidates["observations"].as_array().is_some_and(|rows|rows.iter().any(|row|row["language"]=="cfquery")) {"0.53.0"}else if shell_lint.is_object() || next["schema_version"]=="0.17.0" {"0.52.0"}else if ruby_lint.is_object() || next["schema_version"] == "0.15.0" {"0.51.0"} else if next["schema_version"] == "0.14.0" {"0.50.0"} else if next["schema_version"] == "0.13.0" || syntax_candidates["observations"].as_array().is_some_and(|rows| rows.iter().any(|row| row["language"] == "go" && row.get("structural_observations").is_some())) {"0.49.0"} else if syntax_candidates["observations"].as_array().is_some_and(|rows| rows.iter().any(|row| row.get("structural_observations").is_some())) {"0.48.0"} else if zig_lint["schema_version"] == "0.2.0" || next["schema_version"] == "0.12.0" {"0.47.0"}else if zig_lint.is_object() {"0.46.0"} else if matches!(parsed.selection, Selection::Language(_)) {"0.45.0"} else if swift_lint["schema_version"] == "0.2.0" || next["schema_version"] == "0.11.0" {"0.44.0"} else if swift_lint.is_object() {"0.43.0"} else if kotlin_lint.is_object() || next["schema_version"] == "0.10.0" {"0.42.0"} else if matches!(next["schema_version"].as_str(), Some("0.8.0" | "0.9.0")) {"0.41.0"} else if next["schema_version"] == "0.7.0" {"0.40.0"} else if next["schema_version"] == "0.6.0" {"0.39.0"} else {"0.38.0"}, "report_type":"check_feedback",
21132131
"operation":"check", "selection":parsed.selection.as_str(), "command_status":if request_cancelled { "cancelled" } else { "incomplete" },
21142132
"exit_code":if request_cancelled { 130 } else { 3 }, "delivery_decision":if parsed.selection == Selection::All { "incomplete" } else { "not_evaluated" }, "authority":"local_unverified",
21152133
"reason":if request_cancelled { "request_cancelled" } else if parsed.selection == Selection::All { "full_project_obligations_and_trusted_policy_unavailable" } else if parsed.selection == Selection::Java { "java_selection_obligations_and_trusted_policy_unavailable" } else { "language_selection_obligations_and_trusted_policy_unavailable" },
@@ -2144,6 +2162,7 @@ fn run_scoped(args: &[String], lint_only: bool) -> ExitCode {
21442162
| "0.58.0"
21452163
| "0.59.0"
21462164
| "0.60.0"
2165+
| "0.61.0"
21472166
)
21482167
)
21492168
{
@@ -2172,6 +2191,7 @@ fn run_scoped(args: &[String], lint_only: bool) -> ExitCode {
21722191
| "0.58.0"
21732192
| "0.59.0"
21742193
| "0.60.0"
2194+
| "0.61.0"
21752195
)
21762196
) {
21772197
if let Some(native) = report["native_results"].as_object_mut() {
@@ -2199,6 +2219,7 @@ fn run_scoped(args: &[String], lint_only: bool) -> ExitCode {
21992219
| "0.58.0"
22002220
| "0.59.0"
22012221
| "0.60.0"
2222+
| "0.61.0"
22022223
)
22032224
) {
22042225
if let Some(native) = report["native_results"].as_object_mut() {
@@ -2218,6 +2239,7 @@ fn run_scoped(args: &[String], lint_only: bool) -> ExitCode {
22182239
| "0.58.0"
22192240
| "0.59.0"
22202241
| "0.60.0"
2242+
| "0.61.0"
22212243
)
22222244
) {
22232245
report["native_results"]["ruby_lint"] = ruby_lint.clone();
@@ -2234,6 +2256,7 @@ fn run_scoped(args: &[String], lint_only: bool) -> ExitCode {
22342256
| "0.58.0"
22352257
| "0.59.0"
22362258
| "0.60.0"
2259+
| "0.61.0"
22372260
)
22382261
) {
22392262
report["native_results"]["shell_lint"] = shell_lint.clone();

‎crates/codeguard-cli/src/discovery.rs‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,11 @@ pub fn discover<P: ObservationPort>(
250250
.extend(inspect_maven_unreadable(build_root, manifest));
251251
}
252252
}
253-
} else if let Some(manifest) = manifests
253+
}
254+
// 同根多构建器逐份保留身份,POM观察不能遮蔽Gradle脚本或其未解析义务。
255+
for manifest in manifests
254256
.iter()
255-
.find(|name| name.ends_with("build.gradle") || name.ends_with("build.gradle.kts"))
257+
.filter(|name| name.ends_with("build.gradle") || name.ends_with("build.gradle.kts"))
256258
{
257259
report
258260
.checker_configurations

‎crates/codeguard-cli/tests/check_java_cve_attribution.rs‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,21 @@ fi
9090
])
9191
.output()
9292
.unwrap();
93+
if let Some(dir) = std::env::var_os("CODEGUARD_JAVA_MIXED_BUILD_REPORT_DIR") {
94+
let value: Value = serde_json::from_slice(&output.stdout).unwrap();
95+
if value["schema_version"] == "0.61.0" {
96+
fs::create_dir_all(&dir).unwrap();
97+
fs::write(
98+
PathBuf::from(dir).join(format!(
99+
"native-attribution-{}-{}.json",
100+
std::process::id(),
101+
NEXT.fetch_add(1, Ordering::Relaxed)
102+
)),
103+
&output.stdout,
104+
)
105+
.unwrap();
106+
}
107+
}
93108
(
94109
output.status.code().unwrap(),
95110
serde_json::from_slice(&output.stdout).unwrap(),
@@ -157,3 +172,35 @@ fn same_pom_native_reports_produce_exact_digest_candidate_only() {
157172
);
158173
assert!(!private.to_string().contains("private.example"));
159174
}
175+
176+
#[test]
177+
fn mixed_build_root_keeps_maven_native_evidence_without_assigning_it_to_gradle() {
178+
let project = Project::new();
179+
fs::write(project.0.join("build.gradle.kts"), b"plugins { java }\n").unwrap();
180+
let digest = format!("{:x}", Sha256::digest(b"artifact-bytes"));
181+
let (exit, report, stderr) = project.check(&digest, false);
182+
assert_eq!(exit, 3, "{stderr}");
183+
assert_eq!(
184+
report["native_results"]["java_cve"]["observed_report_count"], 1,
185+
"{report}"
186+
);
187+
assert_eq!(
188+
report["native_results"]["java_dependencies"]["observed_graph_count"], 1,
189+
"{report}"
190+
);
191+
assert_eq!(
192+
report["native_results"]["java_cve"]["probes"][0]["observation"]["advisories"][0]["advisory_id"],
193+
"CVE-2026-1234"
194+
);
195+
for category in ["cve", "dependencies", "security"] {
196+
let row = report["category_candidates"]
197+
.as_array()
198+
.unwrap()
199+
.iter()
200+
.find(|row| row["language"] == "java" && row["category"] == category)
201+
.unwrap();
202+
assert_eq!(row["checker_id"], Value::Null, "{report}");
203+
assert_eq!(row["status"], "configuration_unresolved", "{report}");
204+
assert_eq!(row["reason"], "checker_build_systems_mixed", "{report}");
205+
}
206+
}

0 commit comments

Comments
 (0)