Skip to content

Commit 6912cb0

Browse files
committed
接入Gradle原生OWASP任务报告与显式依赖缓存隔离
1 parent 80a5058 commit 6912cb0

70 files changed

Lines changed: 4010 additions & 12 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,3 +1088,5 @@ flowchart LR
10881088
Real native tests cover all seven rules, repeated identity, presence, noqa suppression and absence after documentation repair; facts remain open. Additional cases retain native exemptions, implicit-exception conflicts and unselected DOC rules. Existing protocols admit native rule IDs and redacted guidance; historical schemas, approved mappings and closure authority are not expanded. See [Ruff DOC acceptance](tests/acceptance/ruff-documentation-contract.md) for evidence and version limits. Complete Python documentation, independent precision, all platforms and production qualification remain pending.
10891089

10901090
Read-only four-core acceptance plan: `codeguard capabilities [language] --acceptance-plan --format=json`. All 57 languages / 228 obligations remain unqualified; filtering preserves full obligations. See [acceptance plan](docs/Codeguard-Production-Acceptance-Plan.md).
1091+
1092+
Java Gradle vulnerability checks now expose explicit original tasks through `codeguard cve java`, preserving JSON configuration and optional module-cache inputs. Observations remain unverified; workbench integration and full native acceptance are pending. See [Gradle OWASP](docs/Codeguard-Gradle-Vulnerability-Checks.md).

‎README.zh-CN.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,3 +1065,5 @@ flowchart LR
10651065
真实七项规则已验证重复扫描身份、存在、noqa 抑制及文档修复后未受信消失,事实保持 open;另有原生豁免、隐式异常冲突和未选择 DOC 的边界。既有协议允许原规则 ID 和脱敏指引,本次不扩大历史 schema、受批准映射或关闭权限。验收与版本限制见 [Ruff DOC 验收](tests/acceptance/ruff-documentation-contract.md)。当前仍不是完整 Python 文档、独立误报评测、全平台或生产资格。
10661066

10671067
四核心验收计划只读入口:`codeguard capabilities [language] --acceptance-plan --format=json`. 57 语言/228 义务保留未授予资格,筛选不缩减总义务。参见[acceptance plan](docs/Codeguard-Production-Acceptance-Plan.zh_CN.md).
1068+
1069+
Java Gradle 漏洞检查新增显式原任务入口 `codeguard cve java`;JSON 原配置与可选模块缓存保留,结果仍未受信,工作台和完整原生验收待完成。详见[Gradle OWASP](docs/Codeguard-Gradle-Vulnerability-Checks.zh_CN.md).
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use crate::{GradleCheckerModel, GradleDependencyCheckTaskPlan, parse_gradle_checker_model};
2+
use std::collections::BTreeSet;
3+
4+
/// 选择显式OWASP任务;参数为原生模型和完整任务路径,返回经插件/类型/启用状态核验的计划。
5+
/// 不自动执行同名普通任务,不将根插件身份继承给子项目。
6+
pub fn plan_gradle_dependency_check_tasks(
7+
model: &GradleCheckerModel,
8+
requested: &[String],
9+
) -> Result<Vec<GradleDependencyCheckTaskPlan>, &'static str> {
10+
let model = parse_gradle_checker_model(
11+
&serde_json::to_vec(model).map_err(|_| "gradle_model_invalid")?,
12+
)?;
13+
if requested.is_empty() || requested.len() > 128 {
14+
return Err("gradle_owasp_task_selection_invalid");
15+
}
16+
let mut seen = BTreeSet::new();
17+
let mut plans = Vec::new();
18+
for path in requested {
19+
if !seen.insert(path) {
20+
return Err("gradle_owasp_task_selection_duplicate");
21+
}
22+
let mut selected = None;
23+
for project in &model.projects {
24+
let prefix = if project.path == ":" {
25+
""
26+
} else {
27+
project.path.as_str()
28+
};
29+
for task in project.dependency_check_tasks() {
30+
if format!("{prefix}:{}", task.name) == *path {
31+
selected = Some(GradleDependencyCheckTaskPlan {
32+
project_path: project.path.clone(),
33+
task_path: path.clone(),
34+
implementation: task.implementation.clone(),
35+
});
36+
}
37+
}
38+
}
39+
plans.push(selected.ok_or("gradle_owasp_task_unavailable")?);
40+
}
41+
Ok(plans)
42+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// 显式选择的Gradle OWASP原任务;仅保存原生模型身份,不签发漏洞扫描资格。
2+
#[derive(Clone, Debug, Eq, PartialEq)]
3+
pub struct GradleDependencyCheckTaskPlan {
4+
/// 所属项目完整路径。
5+
pub project_path: String,
6+
/// 本轮显式请求的完整任务路径。
7+
pub task_path: String,
8+
/// 原生模型观察到的官方分析/聚合基类。
9+
pub implementation: String,
10+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
/// 一张原生OWASP报告的所属任务与输出身份;不代表依赖归属或漏洞库时效。
4+
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
5+
#[serde(deny_unknown_fields)]
6+
pub struct GradleOwaspReportOwnership {
7+
/// 原生所属项目路径。
8+
pub project_path: String,
9+
/// 显式执行的完整任务路径。
10+
pub task_path: String,
11+
/// 原生官方基类名称。
12+
pub implementation: String,
13+
/// 原任务用于报告的项目显示名。
14+
pub report_project_name: String,
15+
/// 私有受检副本内的唯一JSON输出相对路径。
16+
pub report_path: String,
17+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use crate::{
2+
GradleCheckerModel, GradleOwaspReportOwnership, parse_unique_json,
3+
plan_gradle_dependency_check_tasks,
4+
};
5+
use std::collections::BTreeSet;
6+
7+
/// 验证本轮原生输出归属;参数为有界JSON、当前模型及原请求路径,返回逐任务唯一报告位置。
8+
/// 拒绝不匹配/重复任务、输出别名/越界和缺失归属,不根据报告文件名推断扫描完成。
9+
pub fn parse_gradle_owasp_report_ownership(
10+
raw: &[u8],
11+
model: &GradleCheckerModel,
12+
requested: &[String],
13+
) -> Result<Vec<GradleOwaspReportOwnership>, &'static str> {
14+
if raw.is_empty() || raw.len() > 1024 * 1024 {
15+
return Err("gradle_owasp_ownership_budget_invalid");
16+
}
17+
let doc = parse_unique_json(raw).map_err(|_| "gradle_owasp_ownership_json_invalid")?;
18+
let object = doc
19+
.as_object()
20+
.ok_or("gradle_owasp_ownership_shape_invalid")?;
21+
if object.len() != 3
22+
|| doc["schema_version"] != "0.1.0"
23+
|| doc["report_type"] != "gradle_owasp_report_ownership"
24+
{
25+
return Err("gradle_owasp_ownership_protocol_invalid");
26+
}
27+
let plans = plan_gradle_dependency_check_tasks(model, requested)?;
28+
let rows = doc["tasks"]
29+
.as_array()
30+
.ok_or("gradle_owasp_ownership_shape_invalid")?;
31+
if rows.len() != plans.len() {
32+
return Err("gradle_owasp_ownership_incomplete");
33+
}
34+
let mut paths = BTreeSet::new();
35+
let mut tasks = BTreeSet::new();
36+
let mut result = Vec::new();
37+
for value in rows {
38+
let row: GradleOwaspReportOwnership = serde_json::from_value(value.clone())
39+
.map_err(|_| "gradle_owasp_ownership_shape_invalid")?;
40+
if !plans.iter().any(|plan| {
41+
plan.project_path == row.project_path
42+
&& plan.task_path == row.task_path
43+
&& plan.implementation == row.implementation
44+
}) || !tasks.insert(row.task_path.clone())
45+
{
46+
return Err("gradle_owasp_ownership_task_mismatch");
47+
}
48+
if row.report_project_name.is_empty()
49+
|| row.report_project_name.len() > 256
50+
|| row.report_project_name.chars().any(char::is_control)
51+
{
52+
return Err("gradle_owasp_project_name_invalid");
53+
}
54+
if row.report_path.len() > 256
55+
|| !row.report_path.ends_with("/dependency-check-report.json")
56+
|| row.report_path.split('/').any(|part| {
57+
part.is_empty()
58+
|| matches!(part, "." | "..")
59+
|| part.contains(['\\', ':'])
60+
|| part.chars().any(char::is_control)
61+
})
62+
|| !paths.insert(row.report_path.clone())
63+
{
64+
return Err("gradle_owasp_report_scope_invalid");
65+
}
66+
result.push(row);
67+
}
68+
Ok(result)
69+
}

‎crates/codeguard-adapters/src/lib.rs‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,3 +469,13 @@ pub use gradle_javadoc_diagnostic::GradleJavadocDiagnostic;
469469
pub use gradle_javadoc_output::parse_gradle_javadoc_output;
470470

471471
pub use production_acceptance_plan::parse_production_acceptance_plan;
472+
473+
mod gradle_dependency_check_task_plan;
474+
mod gradle_dependency_check_plan;
475+
pub use gradle_dependency_check_task_plan::GradleDependencyCheckTaskPlan;
476+
pub use gradle_dependency_check_plan::plan_gradle_dependency_check_tasks;
477+
478+
mod gradle_owasp_report_ownership;
479+
mod gradle_owasp_report_ownership_parser;
480+
pub use gradle_owasp_report_ownership::GradleOwaspReportOwnership;
481+
pub use gradle_owasp_report_ownership_parser::parse_gradle_owasp_report_ownership;
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use codeguard_adapters::{parse_gradle_checker_model, plan_gradle_dependency_check_tasks};
2+
use serde_json::json;
3+
4+
#[test]
5+
fn explicitly_selected_task_requires_owning_plugin_class_and_enabled_state() {
6+
for (plugin, implementation, enabled, expected) in [
7+
(
8+
true,
9+
"org.owasp.dependencycheck.gradle.tasks.Analyze",
10+
true,
11+
true,
12+
),
13+
(
14+
true,
15+
"org.owasp.dependencycheck.gradle.tasks.Aggregate",
16+
true,
17+
true,
18+
),
19+
(
20+
false,
21+
"org.owasp.dependencycheck.gradle.tasks.Analyze",
22+
true,
23+
false,
24+
),
25+
(true, "org.gradle.api.DefaultTask", true, false),
26+
(
27+
true,
28+
"org.owasp.dependencycheck.gradle.tasks.Analyze",
29+
false,
30+
false,
31+
),
32+
] {
33+
let value = json!({"schema_version":"0.1.0","report_type":"gradle_checker_model","gradle_version":"8.10.2","included_build_count":0,"projects":[{"path":":","project_dir":".","build_dir":"build","plugins":if plugin {vec!["org.owasp.dependencycheck"]}else{vec![]},"tasks":[{"name":"customScan","implementation":implementation,"enabled":enabled}]}]});
34+
let model = parse_gradle_checker_model(&serde_json::to_vec(&value).unwrap()).unwrap();
35+
assert_eq!(
36+
plan_gradle_dependency_check_tasks(&model, &[":customScan".into()]).is_ok(),
37+
expected
38+
);
39+
for requested in [
40+
vec![],
41+
vec![":unknown".into()],
42+
vec![":customScan".into(), ":customScan".into()],
43+
] {
44+
assert!(plan_gradle_dependency_check_tasks(&model, &requested).is_err());
45+
}
46+
}
47+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
use codeguard_adapters::{parse_gradle_checker_model, parse_gradle_owasp_report_ownership};
2+
use serde_json::{Value, json};
3+
fn model() -> codeguard_adapters::GradleCheckerModel {
4+
parse_gradle_checker_model(&serde_json::to_vec(&json!({"schema_version":"0.1.0","report_type":"gradle_checker_model","gradle_version":"8.10.2","included_build_count":0,"projects":[{"path":":","project_dir":".","build_dir":"build","plugins":["org.owasp.dependencycheck"],"tasks":[{"name":"dependencyCheckAnalyze","implementation":"org.owasp.dependencycheck.gradle.tasks.Analyze","enabled":true}]}]})).unwrap()).unwrap()
5+
}
6+
fn ownership() -> Value {
7+
json!({"schema_version":"0.1.0","report_type":"gradle_owasp_report_ownership","tasks":[{"project_path":":","task_path":":dependencyCheckAnalyze","implementation":"org.owasp.dependencycheck.gradle.tasks.Analyze","report_project_name":"root project 'sample'","report_path":"build/reports/dependency-check-report.json"}]})
8+
}
9+
#[test]
10+
fn ownership_is_attributed_to_exact_requested_official_task() {
11+
assert_eq!(
12+
parse_gradle_owasp_report_ownership(
13+
&serde_json::to_vec(&ownership()).unwrap(),
14+
&model(),
15+
&[":dependencyCheckAnalyze".into()]
16+
)
17+
.unwrap()
18+
.len(),
19+
1
20+
);
21+
for mutation in 0..8 {
22+
let mut value = ownership();
23+
match mutation {
24+
0 => value["tasks"][0]["report_path"] = "../outside.json".into(),
25+
1 => value["tasks"][0]["report_path"] = "/absolute.json".into(),
26+
2 => value["tasks"][0]["task_path"] = ":ordinary".into(),
27+
3 => value["tasks"][0]["implementation"] = "org.gradle.api.DefaultTask".into(),
28+
4 => value["tasks"][0]["project_path"] = ":other".into(),
29+
5 => value["tasks"][0]["report_project_name"] = "".into(),
30+
6 => {
31+
value["tasks"].as_array_mut().unwrap().clear();
32+
}
33+
_ => {
34+
let item = value["tasks"][0].clone();
35+
value["tasks"].as_array_mut().unwrap().push(item);
36+
}
37+
}
38+
assert!(
39+
parse_gradle_owasp_report_ownership(
40+
&serde_json::to_vec(&value).unwrap(),
41+
&model(),
42+
&[":dependencyCheckAnalyze".into()]
43+
)
44+
.is_err()
45+
);
46+
}
47+
}
48+
#[test]
49+
fn duplicate_json_fields_and_budget_overruns_are_rejected() {
50+
assert!(
51+
parse_gradle_owasp_report_ownership(
52+
br#"{"schema_version":"0.1.0","schema_version":"0.1.0"}"#,
53+
&model(),
54+
&[":dependencyCheckAnalyze".into()]
55+
)
56+
.is_err()
57+
);
58+
assert!(
59+
parse_gradle_owasp_report_ownership(
60+
&vec![b' '; 1024 * 1024 + 1],
61+
&model(),
62+
&[":dependencyCheckAnalyze".into()]
63+
)
64+
.is_err()
65+
);
66+
}
67+
68+
#[test]
69+
fn two_official_tasks_cannot_claim_one_output_path() {
70+
let mut model = model();
71+
let mut task = model.projects[0].tasks[0].clone();
72+
task.name = "otherScan".into();
73+
model.projects[0].tasks.push(task);
74+
let mut doc = ownership();
75+
let mut record = doc["tasks"][0].clone();
76+
record["task_path"] = ":otherScan".into();
77+
doc["tasks"].as_array_mut().unwrap().push(record);
78+
let paths = vec![":dependencyCheckAnalyze".into(), ":otherScan".into()];
79+
assert_eq!(
80+
parse_gradle_owasp_report_ownership(&serde_json::to_vec(&doc).unwrap(), &model, &paths)
81+
.unwrap_err(),
82+
"gradle_owasp_report_scope_invalid"
83+
);
84+
doc["tasks"][1]["report_path"] = "build/other/dependency-check-report.json".into();
85+
assert_eq!(
86+
parse_gradle_owasp_report_ownership(&serde_json::to_vec(&doc).unwrap(), &model, &paths)
87+
.unwrap()
88+
.len(),
89+
2
90+
);
91+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//! 已有工具的局部OWASP应用服务探针;非发布命令或生产资格入口。
2+
#[cfg(unix)]
3+
fn main() -> Result<(), Box<dyn std::error::Error>> {
4+
use codeguard_cli::{
5+
gradle_dependency_check_probe::observe, gradle_dependency_check_request::Request,
6+
gradle_model_probe_request::Request as NativeRequest,
7+
};
8+
use std::{
9+
collections::BTreeSet,
10+
path::PathBuf,
11+
sync::atomic::AtomicBool,
12+
time::{Duration, Instant},
13+
};
14+
let args = std::env::args().skip(1).collect::<Vec<_>>();
15+
if args.len() < 6 {
16+
return Err(
17+
"usage: PROJECT_ROOT GRADLE_BUNDLE JAVA_HOME TASK_PATH PROJECT_FILE PROJECT_FILE..."
18+
.into(),
19+
);
20+
}
21+
let request = Request {
22+
module_cache: None,
23+
native: NativeRequest {
24+
project_root: PathBuf::from(&args[0]),
25+
gradle_bundle: PathBuf::from(&args[1]),
26+
java_home: PathBuf::from(&args[2]),
27+
project_files: args[4..].iter().map(PathBuf::from).collect::<BTreeSet<_>>(),
28+
deadline: Instant::now() + Duration::from_secs(90),
29+
},
30+
task_paths: vec![args[3].clone()],
31+
};
32+
let report = observe(&request, &AtomicBool::new(false));
33+
println!("{}", serde_json::to_string_pretty(&report)?);
34+
// 读取原生观察不能充当漏洞清洁或交付成功。
35+
std::process::exit(3);
36+
}
37+
#[cfg(not(unix))]
38+
fn main() {
39+
eprintln!("Gradle原生执行探针尚未在此平台验收");
40+
std::process::exit(3);
41+
}

0 commit comments

Comments
 (0)