新增: ScriptNode 白名单跨 crate 配置打通(IPC+OnceLock) - 新增 set_script_safety / get_script_safety IPC - ScriptNode.check_command_policy 优先读运行时配置 - 前端设置页保存时同步注入后端运行时 - 核验确认小程序心跳/文件树刷新/项目分页三项已就绪

This commit is contained in:
2026-07-01 12:38:21 +08:00
parent 535525c2f6
commit 2af24f0db8
5 changed files with 114 additions and 27 deletions

View File

@@ -6,6 +6,23 @@ use df_workflow::node::{Node, NodeContext, NodeOutput, NodeResult, NodeSchema};
/// 脚本节点
pub struct ScriptNode;
// ── 运行时白/黑名单(通过 set_script_safety_config 注入,替代纯 env var) ──
use std::sync::OnceLock;
/// 运行时白名单配置(前端设置页写入,优先于环境变量)
static RUNTIME_SCRIPT_WHITELIST: OnceLock<String> = OnceLock::new();
/// 运行时黑名单配置(前端设置页写入,优先于环境变量)
static RUNTIME_SCRIPT_BLACKLIST: OnceLock<String> = OnceLock::new();
/// 注入运行时脚本安全配置(由 Tauri IPC 调用,前端设置页保存后触发)。
/// 空字符串表示「未配置」,回退到环境变量 / 默认行为。
pub fn set_script_safety_config(whitelist: &str, blacklist: &str) {
let wl = whitelist.trim();
let bl = blacklist.trim();
if !wl.is_empty() { let _ = RUNTIME_SCRIPT_WHITELIST.set(wl.to_string()); }
if !bl.is_empty() { let _ = RUNTIME_SCRIPT_BLACKLIST.set(bl.to_string()); }
}
#[async_trait]
impl Node for ScriptNode {
async fn execute(&self, ctx: NodeContext) -> NodeResult {
@@ -124,20 +141,38 @@ impl Node for ScriptNode {
/// 用户设置 `DF_SCRIPT_BLACKLIST` 时覆盖默认(不合并,用户显式控制)。
const DEFAULT_BLACKLIST: &[&str] = &["rm", "del", "format", "shutdown", "mkfs", "dd"];
/// 命令执行策略校验:从环境变量读取白/黑名单,判定给定命令名是否允许执行
/// 命令执行策略校验:从运行时配置 + 环境变量读取白/黑名单。
///
/// 优先级:黑名单优先于白名单(黑名单匹配总是拒绝,即便同时在白名单)。
///
/// - `DF_SCRIPT_WHITELIST`:逗号分隔命令名(如 `git,npm,cargo`);非空时命令名不在其中即拒绝
/// - `DF_SCRIPT_BLACKLIST`:逗号分隔命令名(如 `rm,format,shutdown`);匹配即拒绝。
/// 未设置时使用 `DEFAULT_BLACKLIST`(rm/del/format/shutdown/mkfs/dd)兜底。
/// 优先级(高→低):
/// 1. 运行时配置(set_script_safety_config,前端设置页写入)
/// 2. 环境变量 DF_SCRIPT_WHITELIST / DF_SCRIPT_BLACKLIST
/// 3. 默认黑名单兜底(DEFAULT_BLACKLIST)
///
/// 黑名单优先于白名单(黑名单匹配总是拒绝,即便同时在白名单)。
/// 命令名比较前 trim + ASCII 小写规范化;空段被忽略。
///
/// 返回 `Some(reason)` 表示拒绝(reason 为拒绝原因,用于日志/错误信息);返回 `None` 表示放行。
/// 返回 `Some(reason)` 表示拒绝;返回 `None` 表示放行。
fn check_command_policy(cmd_name: &str) -> Option<&'static str> {
// 黑名单优先:即便同时在白名单,只要命中黑名单就拒绝
// 优先从环境变量读,后续可扩展为从 DB KV 注入(前端设置页写入)
// 步骤1:尝试运行时黑名单(前端设置页写入,优先级最高)
if let Some(raw) = RUNTIME_SCRIPT_BLACKLIST.get() {
let list: Vec<&str> = raw.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()).collect();
if list.iter().any(|c| c.eq_ignore_ascii_case(cmd_name)) {
return Some("命令在黑名单中(运行时配置)");
}
}
// 步骤2:尝试运行时白名单(非空时放行) — 仅当运行时黑名单未命中时才检查
if let Some(raw) = RUNTIME_SCRIPT_WHITELIST.get() {
let list: Vec<&str> = raw.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()).collect();
if !list.is_empty() && !list.iter().any(|c| c.eq_ignore_ascii_case(cmd_name)) {
return Some("命令不在白名单中(运行时配置)");
}
// 运行时白名单非空且命令在其中 → 直接放行(跳过 env var)
if !list.is_empty() {
return None;
}
}
// 步骤3:回退到环境变量(兼容旧部署)
let env_blacklist = std::env::var("DF_SCRIPT_BLACKLIST").ok();
let blacklist: Vec<String> = match env_blacklist {
Some(raw) => raw