Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ serde_json = "1"
sha2 = "0.10.9"
sha1 = "=0.10.7"
ring = "=0.17.14"
base64 = "=0.22.1"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ Supply the original checker's required tool/configuration options to `task verif
| `task claim / heartbeat / release`, `task attempt start / finish` | Local ownership and attempt history | Unix local coordination, not distributed locking |
| `task verify` | Repeat selected original checkers | Formal closure/reopening pending |
| `rules whitelist list / explain / propose` | Inspect/propose false-positive dispositions and corrections | No public approval or activation |
| `gate pre-commit` | Git-index path-safety observation | Not a full content/security gate |
| `gate pre-commit` | Git-index path/object observation and narrow OpenSSH Ed25519 key detection | Incomplete preview; not a full content/security gate |

For Python edit feedback, run `codeguard lint python . --file src/changed.py --format json`. `--file` is repeatable with a limit of eight distinct paths, 512 bytes per path and 2 KiB combined. The response declares `scan_scope=selected_files` and observes only those files; it is not imported as a full workspace scan and cannot approve delivery. Without `--file`, the existing command still scans discovered Python files and synchronizes its local report.

Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ codeguard task verify "$TASK_ID" . --format json
| `task claim / heartbeat / release`、`task attempt start / finish` | 本地占用与尝试历史 | Unix 本地协作,不保证分布式锁 |
| `task verify` | 重跑部分原检查器 | 正式关闭与重开待完成 |
| `rules whitelist list / explain / propose` | 查询或提出误报处置与纠错 | 无公开批准或生效入口 |
| `gate pre-commit` | Git index 路径安全观察 | 尚非完整内容或安全门禁 |
| `gate pre-commit` | Git index 路径、对象及未加密 OpenSSH Ed25519 私钥局部观察 | 仍是不完整预览,尚非完整内容或安全门禁 |

Python 编辑快反馈可执行 `codeguard lint python . --file src/changed.py --format json`;`--file` 可重复,最多 8 个不同路径,单路径 512 字节、总计 2 KiB。报告标明 `scan_scope=selected_files`,只观察选中文件;局部反馈不导入完整工作台,也不代表全项目或交付通过。原有不带 `--file` 的命令仍扫描发现到的 Python 文件并同步局部报告。

Expand Down
7 changes: 1 addition & 6 deletions crates/codeguard-adapters/src/eslint_finding_identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,7 @@ pub fn project_eslint_findings(
}

/// 复核脱敏任务输入的稳定投影;消息摘要只验证形状,不自证原生来源。
pub fn validate_records(
relative: &str,
absolute: &str,
source: &[u8],
records: &[Value],
) -> bool {
pub fn validate_records(relative: &str, absolute: &str, source: &[u8], records: &[Value]) -> bool {
let mut diagnostics = Vec::new();
for record in records {
let native = &record["native"];
Expand Down
1 change: 1 addition & 0 deletions crates/codeguard-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ sha1.workspace = true
http = "=1.5.0"

[dev-dependencies]
base64.workspace = true
ring.workspace = true
tokio = { version="=1.53.1", features=["rt","time","net"] }
2 changes: 1 addition & 1 deletion crates/codeguard-cli/src/eslint_workbench.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
//! ESLint 局部原生观察到既有修复队列的连接;没有审批或关闭权威。
use codeguard_adapters::project_eslint_findings;
use crate::{
eslint_lint_arguments::EslintLintArguments,
eslint_probe_request::EslintProbeRequest,
eslint_probe_result::EslintProbeResult,
work_sync::{save_local_report, sync_local_workspace},
workspace_refresh::read_workspace_baseline,
};
use codeguard_adapters::project_eslint_findings;
use codeguard_runtime::read_bounded_regular_file;
use serde_json::{Value, json};
use sha2::{Digest, Sha256};
Expand Down
40 changes: 31 additions & 9 deletions crates/codeguard-cli/src/git_index_safety.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use std::path::{Path, PathBuf};
use std::sync::atomic::AtomicBool;
use std::time::{Duration, Instant};

use codeguard_core::{RepositoryPathViolation, check_repository_paths};
use codeguard_core::{
RepositoryPathViolation, check_repository_paths, has_unencrypted_openssh_ed25519_private_key,
};
use codeguard_runtime::{ProcessSpec, Termination, run_process};
use sha1::Sha1;
use sha2::{Digest, Sha256};
Expand Down Expand Up @@ -183,7 +185,7 @@ pub(crate) fn observe_index_safety_with_deadline(
let first = git(&root, git_tool, index.as_deref(), &args, deadline, None)?;
let entries = parse_index_listing(&first, format)?;
let paths: Vec<String> = entries.iter().map(|entry| entry.path.clone()).collect();
let violations = check_repository_paths(&paths)?;
let mut violations = check_repository_paths(&paths)?;
let object_result = verify_index_objects(
&root,
git_tool,
Expand All @@ -198,7 +200,10 @@ pub(crate) fn observe_index_safety_with_deadline(
}
let (object_evidence, unresolved_object_paths, object_verification_reason) = match object_result
{
Ok((evidence, unresolved)) => (evidence, unresolved, None),
Ok((evidence, unresolved, content_violations)) => {
violations.extend(content_violations);
(evidence, unresolved, None)
}
Err(reason) => (Vec::new(), paths.clone(), Some(reason)),
};
let objects_verified = object_verification_reason.is_none()
Expand All @@ -223,7 +228,14 @@ fn verify_index_objects(
entries: &[GitIndexEntry],
format: &str,
deadline: Instant,
) -> Result<(Vec<IndexObjectEvidence>, Vec<String>), String> {
) -> Result<
(
Vec<IndexObjectEvidence>,
Vec<String>,
Vec<RepositoryPathViolation>,
),
String,
> {
let mut requested = BTreeMap::<&str, usize>::new();
let mut unresolved = Vec::new();
for entry in entries {
Expand All @@ -234,7 +246,7 @@ fn verify_index_objects(
}
}
if requested.is_empty() {
return Ok((Vec::new(), unresolved));
return Ok((Vec::new(), unresolved, Vec::new()));
}
let oid_request = requested
.keys()
Expand Down Expand Up @@ -273,7 +285,7 @@ fn verify_index_objects(
}
*size = count;
}
let mut verified = BTreeMap::<String, (usize, String, bool)>::new();
let mut verified = BTreeMap::<String, (usize, String, bool, bool)>::new();
let mut batch = Vec::<(&str, usize)>::new();
let mut batch_size = 0_usize;
for (oid, size) in requested {
Expand All @@ -289,11 +301,13 @@ fn verify_index_objects(
read_blob_batch(root, tool, index, &batch, format, deadline, &mut verified)?;
}
let mut evidence = Vec::new();
let mut content_violations = Vec::new();
for entry in entries {
if entry.mode == "160000" {
continue;
}
let (size, digest, lfs_pointer) = verified.get(&entry.oid).ok_or("Git 对象验证结果缺失")?;
let (size, digest, lfs_pointer, private_key) =
verified.get(&entry.oid).ok_or("Git 对象验证结果缺失")?;
let kind = if entry.mode == "120000" {
"symlink_unresolved"
} else if *lfs_pointer {
Expand All @@ -304,6 +318,13 @@ fn verify_index_objects(
if kind != "regular" {
unresolved.push(entry.path.clone());
}
// 仅对已核对 OID 的普通 blob 记录精确结构命中;不回显私钥字节。
if kind == "regular" && *private_key {
content_violations.push(RepositoryPathViolation {
path: entry.path.clone(),
rule_id: "repository_policy.unencrypted_openssh_ed25519_private_key".into(),
});
}
evidence.push(IndexObjectEvidence {
path: entry.path.clone(),
oid: entry.oid.clone(),
Expand All @@ -313,7 +334,7 @@ fn verify_index_objects(
});
}
unresolved.sort();
Ok((evidence, unresolved))
Ok((evidence, unresolved, content_violations))
}

fn read_blob_batch(
Expand All @@ -323,7 +344,7 @@ fn read_blob_batch(
batch: &[(&str, usize)],
format: &str,
deadline: Instant,
verified: &mut BTreeMap<String, (usize, String, bool)>,
verified: &mut BTreeMap<String, (usize, String, bool, bool)>,
) -> Result<(), String> {
let input = batch
.iter()
Expand Down Expand Up @@ -364,6 +385,7 @@ fn read_blob_batch(
*size,
format!("{:x}", Sha256::digest(content)),
content.starts_with(b"version https://git-lfs.github.com/spec/v1"),
has_unencrypted_openssh_ed25519_private_key(content),
),
);
cursor = content_end + 1;
Expand Down
4 changes: 2 additions & 2 deletions crates/codeguard-cli/src/git_index_safety_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ pub fn run(args: &[String]) -> ExitCode {
incomplete_reasons.push("index_objects_unresolved");
}
json!({
"schema_version":"0.2.0",
"schema_version":"0.3.0",
"report_type":"git_index_safety_preview",
"operation":"gate_pre_commit",
"command_status":"incomplete",
Expand All @@ -77,7 +77,7 @@ pub fn run(args: &[String]) -> ExitCode {
})
}
Err(_) => json!({
"schema_version":"0.2.0",
"schema_version":"0.3.0",
"report_type":"git_index_safety_preview",
"operation":"gate_pre_commit",
"command_status":"incomplete",
Expand Down
2 changes: 1 addition & 1 deletion crates/codeguard-cli/src/go_lint_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

use crate::check_budget::parse_check_timeout;
use crate::discovery::discover;
use codeguard_adapters::go_finding_record;
use crate::work_sync::{save_local_report, sync_local_workspace};
use crate::workspace_refresh::read_workspace_baseline;
use codeguard_adapters::go_finding_record;
use codeguard_adapters::{GoVetParseState, legacy_registry, parse_go_vet_json};
use codeguard_runtime::{
NativeObservation, ProcessSpec, Termination, read_bounded_regular_file, run_process,
Expand Down
2 changes: 1 addition & 1 deletion crates/codeguard-cli/src/rust_build_command.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//! 原生 Cargo/check 类型检查观察;完整项目策略与修复任务仍独立核验。

use codeguard_adapters::cargo_build_finding_record;
use crate::cargo_build_repair_brief::cargo_build_repair_brief;
use crate::check_budget::{
budget_record, parse_check_timeout, resolve_project_default, select_check_timeout,
Expand All @@ -9,6 +8,7 @@ use crate::discovery::discover;
use crate::doctor_scratch::DoctorScratch;
use crate::work_sync::{save_local_report, sync_local_workspace};
use crate::workspace_refresh::read_workspace_baseline;
use codeguard_adapters::cargo_build_finding_record;
use codeguard_adapters::{legacy_registry, parse_cargo_build_json};
use codeguard_runtime::{
NativeObservation, ProcessSpec, SourceSnapshot, Termination, read_bounded_regular_file,
Expand Down
2 changes: 1 addition & 1 deletion crates/codeguard-cli/src/rust_comments_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ use crate::check_budget::{
};
use crate::discovery::discover;
use crate::doctor_scratch::DoctorScratch;
use codeguard_adapters::rustdoc_finding_record;
use crate::rustdoc_repair_brief::rustdoc_repair_brief;
use crate::work_sync::{save_local_report, sync_local_workspace};
use crate::workspace_refresh::read_workspace_baseline;
use codeguard_adapters::rustdoc_finding_record;
use codeguard_adapters::{legacy_registry, parse_cargo_rustdoc_json};
use codeguard_runtime::{
NativeObservation, ProcessSpec, SourceSnapshot, Termination, read_bounded_regular_file,
Expand Down
7 changes: 1 addition & 6 deletions crates/codeguard-cli/src/work_sync/eslint_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,7 @@ pub(super) fn parse(
{
return Err("eslint_completion_invalid");
}
if !codeguard_adapters::validate_records(
relative,
absolute,
&bytes["source"],
records,
) {
if !codeguard_adapters::validate_records(relative, absolute, &bytes["source"], records) {
return Err("eslint_finding_identity_invalid");
}
let findings = records
Expand Down
2 changes: 1 addition & 1 deletion crates/codeguard-cli/src/work_sync/rust_build_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ use super::{
BlockerInput, FindingInput, MAX_REPORT_BYTES, ReportInput, read_bounded_file, safe_reason,
safe_relative_path, safe_run_id, valid_sha256,
};
use codeguard_adapters::cargo_build_finding_record;
use crate::cargo_build_repair_brief::cargo_build_repair_brief;
use codeguard_adapters::CargoBuildDiagnostic;
use codeguard_adapters::cargo_build_finding_record;
use codeguard_runtime::SourceSnapshot;
use serde_json::{Value, json};
use sha2::{Digest, Sha256};
Expand Down
2 changes: 1 addition & 1 deletion crates/codeguard-cli/src/work_sync/rustdoc_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ use super::{
BlockerInput, FindingInput, MAX_REPORT_BYTES, ReportInput, read_bounded_file, safe_reason,
safe_relative_path, safe_run_id, valid_sha256,
};
use codeguard_adapters::rustdoc_finding_record;
use crate::rustdoc_repair_brief::rustdoc_repair_brief;
use codeguard_adapters::RustdocFinding;
use codeguard_adapters::rustdoc_finding_record;
use serde_json::{Value, json};
use sha2::{Digest, Sha256};
use std::collections::BTreeSet;
Expand Down
15 changes: 13 additions & 2 deletions crates/codeguard-cli/tests/crate_boundaries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::process::Command;

fn allowed(package: &str, dependency: &str, kind: &str) -> bool {
match package {
"codeguard-core" => dependency == "serde",
"codeguard-core" => dependency == "serde" || (dependency == "base64" && kind == "normal"),
"codeguard-runtime" => {
matches!(dependency, "codeguard-core" | "libc" | "ring")
|| (kind == "dev" && dependency == "rustls")
Expand All @@ -30,7 +30,7 @@ fn allowed(package: &str, dependency: &str, kind: &str) -> bool {
)
}
"codeguard-cli" => {
(matches!(dependency, "ring" | "tokio") && kind == "dev")
(matches!(dependency, "ring" | "tokio" | "base64") && kind == "dev")
|| (dependency == "http" && kind == "normal")
|| matches!(
dependency,
Expand Down Expand Up @@ -148,6 +148,9 @@ fn forbidden_edges_stay_forbidden_under_alias_build_and_target_variants() {
("codeguard-cli", "ring", "normal"),
("codeguard-cli", "ring", "build"),
("codeguard-core", "ring", "normal"),
("codeguard-core", "base64", "dev"),
("codeguard-core", "base64", "build"),
("codeguard-cli", "base64", "normal"),
("codeguard-core", "semver", "normal"),
("codeguard-runtime", "semver", "normal"),
("codeguard-cli", "semver", "normal"),
Expand All @@ -173,6 +176,14 @@ fn semver_parser_is_scoped_to_adapter_normal_dependencies() {
assert!(violations(&one_dependency("codeguard-adapters", "semver", "normal")).is_empty());
}

#[test]
fn base64_decoder_is_scoped_to_pure_core_and_cli_test_fixtures() {
assert!(violations(&one_dependency("codeguard-core", "base64", "normal")).is_empty());
assert!(violations(&one_dependency("codeguard-cli", "base64", "dev")).is_empty());
assert!(!violations(&one_dependency("codeguard-runtime", "base64", "normal")).is_empty());
assert!(!violations(&one_dependency("codeguard-adapters", "base64", "normal")).is_empty());
}

#[test]
fn dependency_checker_rejects_missing_metadata() {
assert!(!violations(&json!({})).is_empty());
Expand Down
Loading
Loading