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"
|
||||
|
||||
[features]
|
||||
default = ["multi-agent"]
|
||||
# Enable multi-agent orchestration (Director, A2A protocol)
|
||||
multi-agent = ["zclaw-protocols/a2a"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
zclaw-types = { workspace = true }
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
//! ZCLAW Protocols
|
||||
//!
|
||||
//! 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_types;
|
||||
mod mcp_tool_adapter;
|
||||
mod mcp_transport;
|
||||
#[cfg(feature = "a2a")]
|
||||
mod a2a;
|
||||
|
||||
pub use mcp::*;
|
||||
pub use mcp_types::*;
|
||||
pub use mcp_tool_adapter::*;
|
||||
pub use mcp_transport::*;
|
||||
#[cfg(feature = "a2a")]
|
||||
pub use a2a::*;
|
||||
|
||||
@@ -9,7 +9,7 @@ description = "ZCLAW skill system"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
wasm = ["wasmtime", "wasmtime-wasi/p1", "ureq"]
|
||||
wasm = ["wasmtime", "wasmtime-wasi/p1", "ureq", "url"]
|
||||
|
||||
[dependencies]
|
||||
zclaw-types = { workspace = true }
|
||||
@@ -28,3 +28,4 @@ shlex = { workspace = true }
|
||||
wasmtime = { workspace = true, optional = true }
|
||||
wasmtime-wasi = { 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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Security: only allow reads under /workspace (preopen root)
|
||||
if path.starts_with("..") || path.starts_with('/') {
|
||||
// Security: validate path stays within /workspace sandbox.
|
||||
// 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);
|
||||
return -1;
|
||||
}
|
||||
|
||||
let full_path = format!("/workspace/{}", path);
|
||||
|
||||
match std::fs::read(&full_path) {
|
||||
match std::fs::read(&joined) {
|
||||
Ok(data) => write_guest_bytes(&mut caller, out_ptr, out_cap, &data),
|
||||
Err(e) => {
|
||||
debug!("[WasmSkill] file_read error for {}: {}", path, e);
|
||||
|
||||
Reference in New Issue
Block a user