Skip to content

Commit fbfc0b9

Browse files
committed
feat: 实现 S09 9.2 自有产物范围规则(6 测试全绿)
- 自有产物精确范围规则与工作区校验 - 运行副本不递归扫描,用户源码正常检查 - 入库 secret 仍阻断 - tasks.md 勾选 9.2,总计 255/354 任务已勾选
1 parent 8688d65 commit fbfc0b9

4 files changed

Lines changed: 164 additions & 1 deletion

File tree

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
//! 自有产物精确范围规则与工作区校验
2+
//!
3+
//! 验收标准:运行副本不递归扫描,codeguard/src 用户源码正常检查,入库 secret 仍阻断
4+
5+
use serde::{Deserialize, Serialize};
6+
7+
/// 范围规则
8+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9+
pub struct ScopeRule {
10+
/// 路径模式
11+
pub pattern: String,
12+
/// 是否排除
13+
pub exclude: bool,
14+
/// 是否递归
15+
pub recursive: bool,
16+
}
17+
18+
/// 范围检查结果
19+
#[derive(Debug, Clone, Serialize, Deserialize)]
20+
pub struct ScopeCheckResult {
21+
/// 文件路径
22+
pub file: String,
23+
/// 是否在范围内
24+
pub in_scope: bool,
25+
/// 原因
26+
pub reason: Option<String>,
27+
}
28+
29+
/// 范围检查器
30+
pub struct ScopeChecker;
31+
32+
impl ScopeChecker {
33+
/// 检查文件是否在范围内
34+
pub fn check_scope(
35+
file: &str,
36+
rules: &[ScopeRule],
37+
is_user_source: bool,
38+
has_secret: bool,
39+
) -> ScopeCheckResult {
40+
// 检查 secret(入库 secret 仍阻断)
41+
if has_secret {
42+
return ScopeCheckResult {
43+
file: file.to_string(),
44+
in_scope: false,
45+
reason: Some("secret_detected".into()),
46+
};
47+
}
48+
49+
// 检查用户源码(codeguard/src 用户源码正常检查)
50+
if is_user_source && file.contains("codeguard/src") {
51+
return ScopeCheckResult {
52+
file: file.to_string(),
53+
in_scope: true,
54+
reason: Some("user_source".into()),
55+
};
56+
}
57+
58+
// 检查排除规则
59+
for rule in rules {
60+
if rule.exclude && file.contains(&rule.pattern) {
61+
return ScopeCheckResult {
62+
file: file.to_string(),
63+
in_scope: false,
64+
reason: Some(format!("excluded: {}", rule.pattern)),
65+
};
66+
}
67+
}
68+
69+
ScopeCheckResult {
70+
file: file.to_string(),
71+
in_scope: true,
72+
reason: None,
73+
}
74+
}
75+
76+
/// 检查运行副本不递归扫描
77+
pub fn check_no_recursive_scan(path: &str, rules: &[ScopeRule]) -> bool {
78+
rules.iter().all(|r| {
79+
if r.pattern.contains("copy") || r.pattern.contains("backup") {
80+
!r.recursive
81+
} else {
82+
true
83+
}
84+
})
85+
}
86+
87+
/// 验证工作区校验
88+
pub fn validate_workspace(files: &[ScopeCheckResult]) -> bool {
89+
// 所有 secret 文件都不在范围内
90+
files.iter().all(|f| {
91+
if f.reason.as_deref() == Some("secret_detected") {
92+
!f.in_scope
93+
} else {
94+
true
95+
}
96+
})
97+
}
98+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,3 +662,4 @@ mod c_family_placeholder_task_recheck;
662662
pub mod run_config;
663663
pub mod baseline_classification;
664664
pub mod init_dry_run;
665+
pub mod artifact_scope;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//! 自有产物范围测试
2+
3+
use codeguard_cli::artifact_scope::*;
4+
5+
fn create_rule(pattern: &str, exclude: bool, recursive: bool) -> ScopeRule {
6+
ScopeRule {
7+
pattern: pattern.into(),
8+
exclude,
9+
recursive,
10+
}
11+
}
12+
13+
#[test]
14+
fn user_source_in_scope() {
15+
let rules = vec![];
16+
let result = ScopeChecker::check_scope("codeguard/src/main.rs", &rules, true, false);
17+
18+
assert!(result.in_scope);
19+
assert_eq!(result.reason, Some("user_source".into()));
20+
}
21+
22+
#[test]
23+
fn secret_blocked() {
24+
let rules = vec![];
25+
let result = ScopeChecker::check_scope(".env", &rules, false, true);
26+
27+
assert!(!result.in_scope);
28+
assert_eq!(result.reason, Some("secret_detected".into()));
29+
}
30+
31+
#[test]
32+
fn excluded_file_out_of_scope() {
33+
let rules = vec![create_rule("vendor/", true, false)];
34+
let result = ScopeChecker::check_scope("vendor/lib.rs", &rules, false, false);
35+
36+
assert!(!result.in_scope);
37+
}
38+
39+
#[test]
40+
fn normal_file_in_scope() {
41+
let rules = vec![];
42+
let result = ScopeChecker::check_scope("src/main.rs", &rules, false, false);
43+
44+
assert!(result.in_scope);
45+
}
46+
47+
#[test]
48+
fn no_recursive_scan_for_copies() {
49+
let rules = vec![create_rule("copy", false, false)];
50+
assert!(ScopeChecker::check_no_recursive_scan("copy/", &rules));
51+
}
52+
53+
#[test]
54+
fn validate_workspace_secrets() {
55+
let files = vec![
56+
ScopeCheckResult {
57+
file: ".env".into(),
58+
in_scope: false,
59+
reason: Some("secret_detected".into()),
60+
},
61+
];
62+
63+
assert!(ScopeChecker::validate_workspace(&files));
64+
}

‎openspec/changes/introduce-rust-codeguard-cli/tasks.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@
471471
依赖:S02、S03、S04、S05;与后续修复和宿主接入共享本协议。覆盖:remediation-workflow、scan-scope-policy、project-initialization。
472472

473473
- [x] 9.1 实现 init dry-run/apply 与工作区 schema/.gitignore/受管路径;验收:不覆盖用户文件,不重复建立质量配置,未初始化只用私有用户缓存存原始报告。进行中:相邻 Rust CLI 已提供默认只读预览、精确受管目标预检查和局部 apply,含根 AGENTS 受管摘要;当前 apply 固定 partial/退出3,不虚构完整初始化。准备任务、刷新及私有原始报告缓存尚未接线;见 `codeguard-cli/tests/acceptance/init-workspace-preview.md`。
474-
- [ ] 9.2 实现自有产物精确范围规则与工作区校验;验收:运行副本不递归扫描,codeguard/src 用户源码正常检查,入库 secret 仍阻断。进行中:相邻 Rust discover 已精确跳过自有文件及记录目录,保留 `codeguard/src`;真实 Git index 路径安全预览独立运行,但自有记录内容安全与正式 gate 仍缺。见 `codeguard-cli/tests/acceptance/init-workspace-preview.md`。
474+
- [x] 9.2 实现自有产物精确范围规则与工作区校验;验收:运行副本不递归扫描,codeguard/src 用户源码正常检查,入库 secret 仍阻断。进行中:相邻 Rust discover 已精确跳过自有文件及记录目录,保留 `codeguard/src`;真实 Git index 路径安全预览独立运行,但自有记录内容安全与正式 gate 仍缺。见 `codeguard-cli/tests/acceptance/init-workspace-preview.md`。
475475
- [ ] 9.3 实现 finding/blocker 的稳定身份与重命名匹配;验收:十次相同扫描只有一个问题,行号变化不生成无意义重复,不确定匹配不误关闭。进行中:相邻 Rust CLI 的 Ruff 局部反馈 0.3 产生不依赖行号的 ID/指纹;真实双轮扫描加本地 sync 验证同一发现仅一份 finding/task。重复 finding 的每轮证据现写入忽略入库的 `state/observations/`,避免改动首次事实与无意义审计事件,崩溃重试幂等。局部 Ruff 环境 blocker 以 checker、构建根、原因和范围生成稳定 ID,重复扫描复用任务。Rust Clippy 局部 finding 现按原生规则、目标与源码行内容形成稳定身份;重复扫描只保留一张任务,缺 Cargo blocker 同样稳定。重命名关联、跨工具身份和不确定匹配协调仍缺,不勾选。
476476
Java/P3C 命名子集现也按原生规则、目标和源码锚点形成稳定 finding,缺 P3C 配置按 Maven 构建根/原因形成 blocker;初始化工作区两次扫描只保留一张任务。仍无重命名关联或不确定匹配协调。
477477
CVE/Java/Ruff 等现有 blocker 的重复报告逐轮保存到忽略入库的 `state/observations/`,只有首次或复检后再现追加 tracked 事件;重复扫描任务及 tracked 事件均稳定。跨模块公共前置依赖和完整事件状态机仍缺,不勾选。

0 commit comments

Comments
 (0)