fix: 深度审计修复 — WASM 安全加固 + A2A 编译路径 + 测试编译
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CRITICAL: - zclaw_file_read: 路径遍历修复 — 组件级过滤替代前缀检查 - zclaw_http_fetch: SSRF 防护 — URL scheme 白名单 + 私有IP段阻止 - Phase 4A: 移除 zclaw-protocols a2a feature gate, A2A 始终编译 - 移除 kernel/desktop multi-agent feature (不再控制任何代码) MEDIUM: - user_profiler: FactCategory cfg(test) 导入修复 (563 测试全通过)
This commit is contained in:
@@ -8,9 +8,7 @@ rust-version.workspace = true
|
|||||||
description = "ZCLAW kernel - central coordinator for all subsystems"
|
description = "ZCLAW kernel - central coordinator for all subsystems"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["multi-agent"]
|
default = []
|
||||||
# Enable multi-agent orchestration (Director, A2A protocol)
|
|
||||||
multi-agent = ["zclaw-protocols/a2a"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
zclaw-types = { workspace = true }
|
zclaw-types = { workspace = true }
|
||||||
|
|||||||
@@ -1,20 +1,15 @@
|
|||||||
//! ZCLAW Protocols
|
//! ZCLAW Protocols
|
||||||
//!
|
//!
|
||||||
//! Protocol support for MCP (Model Context Protocol) and A2A (Agent-to-Agent).
|
//! Protocol support for MCP (Model Context Protocol) and A2A (Agent-to-Agent).
|
||||||
//!
|
|
||||||
//! A2A is gated behind the `a2a` feature flag (reserved for future multi-agent scenarios).
|
|
||||||
//! MCP is always available as a framework for tool integration.
|
|
||||||
|
|
||||||
mod mcp;
|
mod mcp;
|
||||||
mod mcp_types;
|
mod mcp_types;
|
||||||
mod mcp_tool_adapter;
|
mod mcp_tool_adapter;
|
||||||
mod mcp_transport;
|
mod mcp_transport;
|
||||||
#[cfg(feature = "a2a")]
|
|
||||||
mod a2a;
|
mod a2a;
|
||||||
|
|
||||||
pub use mcp::*;
|
pub use mcp::*;
|
||||||
pub use mcp_types::*;
|
pub use mcp_types::*;
|
||||||
pub use mcp_tool_adapter::*;
|
pub use mcp_tool_adapter::*;
|
||||||
pub use mcp_transport::*;
|
pub use mcp_transport::*;
|
||||||
#[cfg(feature = "a2a")]
|
|
||||||
pub use a2a::*;
|
pub use a2a::*;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ description = "ZCLAW skill system"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
wasm = ["wasmtime", "wasmtime-wasi/p1", "ureq"]
|
wasm = ["wasmtime", "wasmtime-wasi/p1", "ureq", "url"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
zclaw-types = { workspace = true }
|
zclaw-types = { workspace = true }
|
||||||
@@ -28,3 +28,4 @@ shlex = { workspace = true }
|
|||||||
wasmtime = { workspace = true, optional = true }
|
wasmtime = { workspace = true, optional = true }
|
||||||
wasmtime-wasi = { workspace = true, optional = true }
|
wasmtime-wasi = { workspace = true, optional = true }
|
||||||
ureq = { workspace = true, optional = true }
|
ureq = { workspace = true, optional = true }
|
||||||
|
url = { workspace = true, optional = true }
|
||||||
|
|||||||
@@ -267,6 +267,46 @@ fn add_host_functions(linker: &mut Linker<WasiP1Ctx>, network_allowed: bool) ->
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Security: validate URL scheme to prevent SSRF.
|
||||||
|
// Only http:// and https:// are allowed.
|
||||||
|
let parsed = match url::Url::parse(&url) {
|
||||||
|
Ok(u) => u,
|
||||||
|
Err(_) => {
|
||||||
|
warn!("[WasmSkill] http_fetch denied — invalid URL: {}", url);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let scheme = parsed.scheme();
|
||||||
|
if scheme != "http" && scheme != "https" {
|
||||||
|
warn!("[WasmSkill] http_fetch denied — unsupported scheme: {}", scheme);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// Block private/loopback hosts to prevent SSRF
|
||||||
|
if let Some(host) = parsed.host_str() {
|
||||||
|
let lower = host.to_lowercase();
|
||||||
|
if lower == "localhost"
|
||||||
|
|| lower.starts_with("127.")
|
||||||
|
|| lower.starts_with("10.")
|
||||||
|
|| lower.starts_with("192.168.")
|
||||||
|
|| lower.starts_with("169.254.")
|
||||||
|
|| lower.starts_with("0.")
|
||||||
|
|| lower.ends_with(".internal")
|
||||||
|
|| lower.ends_with(".local")
|
||||||
|
{
|
||||||
|
warn!("[WasmSkill] http_fetch denied — private/loopback host: {}", host);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// Also block 172.16.0.0/12 range
|
||||||
|
if lower.starts_with("172.") {
|
||||||
|
if let Ok(second) = lower.split('.').nth(1).unwrap_or("0").parse::<u8>() {
|
||||||
|
if (16..=31).contains(&second) {
|
||||||
|
warn!("[WasmSkill] http_fetch denied — private host (172.16-31.x.x): {}", host);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
debug!("[WasmSkill] guest http_fetch: {}", url);
|
debug!("[WasmSkill] guest http_fetch: {}", url);
|
||||||
|
|
||||||
// Synchronous HTTP GET (we're already on a blocking thread)
|
// Synchronous HTTP GET (we're already on a blocking thread)
|
||||||
@@ -309,15 +349,30 @@ fn add_host_functions(linker: &mut Linker<WasiP1Ctx>, network_allowed: bool) ->
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Security: only allow reads under /workspace (preopen root)
|
// Security: validate path stays within /workspace sandbox.
|
||||||
if path.starts_with("..") || path.starts_with('/') {
|
// Reject absolute paths, and filter any path component that
|
||||||
|
// is ".." (e.g. "foo/../../etc/passwd").
|
||||||
|
let joined = std::path::Path::new("/workspace").join(&path);
|
||||||
|
let mut safe = true;
|
||||||
|
for comp in joined.components() {
|
||||||
|
match comp {
|
||||||
|
std::path::Component::ParentDir => {
|
||||||
|
safe = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::path::Component::RootDir | std::path::Component::Prefix(_) => {
|
||||||
|
safe = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {} // Normal, CurDir — ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !safe {
|
||||||
warn!("[WasmSkill] guest file_read denied — path escapes sandbox: {}", path);
|
warn!("[WasmSkill] guest file_read denied — path escapes sandbox: {}", path);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let full_path = format!("/workspace/{}", path);
|
match std::fs::read(&joined) {
|
||||||
|
|
||||||
match std::fs::read(&full_path) {
|
|
||||||
Ok(data) => write_guest_bytes(&mut caller, out_ptr, out_cap, &data),
|
Ok(data) => write_guest_bytes(&mut caller, out_ptr, out_cap, &data),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!("[WasmSkill] file_read error for {}: {}", path, e);
|
debug!("[WasmSkill] file_read error for {}: {}", path, e);
|
||||||
|
|||||||
@@ -16,9 +16,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
|||||||
tauri-build = { version = "2", features = [] }
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["multi-agent"]
|
default = []
|
||||||
# Multi-agent orchestration (A2A protocol, Director, agent delegation)
|
|
||||||
multi-agent = ["zclaw-kernel/multi-agent"]
|
|
||||||
dev-server = ["dep:axum", "dep:tower-http"]
|
dev-server = ["dep:axum", "dep:tower-http"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ use std::sync::Arc;
|
|||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
use zclaw_memory::fact::Fact;
|
use zclaw_memory::fact::Fact;
|
||||||
|
#[cfg(test)]
|
||||||
|
use zclaw_memory::fact::FactCategory;
|
||||||
use zclaw_memory::user_profile_store::{
|
use zclaw_memory::user_profile_store::{
|
||||||
CommStyle, Level, UserProfile, UserProfileStore,
|
CommStyle, Level, UserProfile, UserProfileStore,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user