security: 修复命令注入/路径遍历漏洞 + 收紧 allowedOrigins + 审计日志 + OnceLock 缓存

This commit is contained in:
晴天
2026-03-06 20:51:32 +08:00
parent 7d387a4f94
commit 80197bdc60
10 changed files with 66 additions and 14 deletions

View File

@@ -1,6 +1,6 @@
[package]
name = "clawpanel"
version = "0.5.5"
version = "0.5.6"
edition = "2021"
description = "ClawPanel - OpenClaw 可视化管理面板"
authors = ["qingchencloud"]

View File

@@ -4,6 +4,20 @@ use base64::{engine::general_purpose, Engine as _};
/// 仅在用户主动开启工具后由 AI 调用
use std::path::PathBuf;
/// 审计日志:记录 AI 助手的敏感操作exec / read / write
fn audit_log(action: &str, detail: &str) {
let log_dir = super::openclaw_dir().join("logs");
let _ = std::fs::create_dir_all(&log_dir);
let log_path = log_dir.join("assistant-audit.log");
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
let line = format!("[{ts}] [{action}] {detail}\n");
let _ = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&log_path)
.and_then(|mut f| std::io::Write::write_all(&mut f, line.as_bytes()));
}
/// ClawPanel 数据目录(~/.openclaw/clawpanel/
fn data_dir() -> PathBuf {
super::openclaw_dir().join("clawpanel")
@@ -125,6 +139,8 @@ pub async fn assistant_exec(command: String, cwd: Option<String>) -> Result<Stri
.to_string()
});
audit_log("EXEC", &format!("cmd={command} cwd={work_dir}"));
let output;
#[cfg(target_os = "windows")]
@@ -184,6 +200,7 @@ pub async fn assistant_exec(command: String, cwd: Option<String>) -> Result<Stri
/// 读取文件内容
#[tauri::command]
pub async fn assistant_read_file(path: String) -> Result<String, String> {
audit_log("READ", &path);
let content = tokio::fs::read_to_string(&path)
.await
.map_err(|e| format!("读取文件失败 {path}: {e}"))?;
@@ -202,6 +219,7 @@ pub async fn assistant_read_file(path: String) -> Result<String, String> {
/// 写入文件
#[tauri::command]
pub async fn assistant_write_file(path: String, content: String) -> Result<String, String> {
audit_log("WRITE", &format!("{path} ({} bytes)", content.len()));
if let Some(parent) = PathBuf::from(&path).parent() {
tokio::fs::create_dir_all(parent)
.await

View File

@@ -119,7 +119,7 @@ pub fn create_connect_frame(nonce: String, gateway_token: String) -> Result<Valu
"maxProtocol": 3,
"client": {
"id": "openclaw-control-ui",
"version": "1.0.0",
"version": env!("CARGO_PKG_VERSION"),
"platform": platform,
"deviceFamily": device_family,
"mode": "ui"
@@ -136,7 +136,7 @@ pub fn create_connect_frame(nonce: String, gateway_token: String) -> Result<Valu
"signature": sig_b64,
},
"locale": "zh-CN",
"userAgent": "ClawPanel/1.0.0",
"userAgent": format!("ClawPanel/{}", env!("CARGO_PKG_VERSION")),
}
});

View File

@@ -1,4 +1,5 @@
use std::path::PathBuf;
use std::sync::OnceLock;
pub mod agent;
pub mod assistant;
@@ -15,12 +16,19 @@ pub fn openclaw_dir() -> PathBuf {
dirs::home_dir().unwrap_or_default().join(".openclaw")
}
/// 缓存 enhanced_path 结果,避免每次调用都扫描文件系统
static ENHANCED_PATH_CACHE: OnceLock<String> = OnceLock::new();
/// Tauri 应用启动时 PATH 可能不完整:
/// - macOS 从 Finder 启动时 PATH 只有 /usr/bin:/bin:/usr/sbin:/sbin
/// - Windows 上安装 Node.js 到非默认路径、或安装后未重启进程
///
/// 补充 Node.js / npm 常见安装路径
pub fn enhanced_path() -> String {
ENHANCED_PATH_CACHE.get_or_init(build_enhanced_path).clone()
}
fn build_enhanced_path() -> String {
let current = std::env::var("PATH").unwrap_or_default();
let home = dirs::home_dir().unwrap_or_default();

View File

@@ -115,8 +115,14 @@ fn patch_gateway_origins() {
return;
};
// 放行全部 origin确保 Tauri 正式/开发模式、Web 模式都能连接
let origins = serde_json::json!(["*"]);
// 仅允许 Tauri 应用 + 本地开发服务器的 origin
let origins = serde_json::json!([
"tauri://localhost",
"https://tauri.localhost",
"http://tauri.localhost",
"http://localhost:1420",
"http://127.0.0.1:1420"
]);
if let Some(obj) = config.as_object_mut() {
let gateway = obj

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-config-schema/schema.json",
"productName": "ClawPanel",
"version": "0.5.5",
"version": "0.5.6",
"identifier": "ai.openclaw.clawpanel",
"build": {
"frontendDist": "../dist",