优化: miniapp渲染(图片/checkbox/代码高亮) + MCP规则共享下沉 + 文档重写 + 节点风格对齐

This commit is contained in:
lxy
2026-08-08 21:39:30 +08:00
parent 9eb2995a74
commit e7228231d1
11 changed files with 587 additions and 167 deletions
+8 -16
View File
@@ -56,9 +56,7 @@ impl Node for ScriptNode {
shell_type: Default::default(),
};
// 命令执行安全:白/黑名单校验(从环境变量读取,逗号分隔命令名)。
// - 白名单非空时:命令首词不在白名单 → 直接拒绝执行
// - 黑名单匹配时:直接拒绝执行
// 命令执行安全:白/黑名单校验(运行时配置优先,回退环境变量/默认黑名单)。
// 命令名取首词(shell 第一段,如 `rm -rf /` 取 `rm`),按 trim + 小写规范化比较。
let cmd_name = command.split_whitespace().next().unwrap_or("").to_lowercase();
if let Some(denied) = check_command_policy(&cmd_name) {
@@ -70,7 +68,7 @@ impl Node for ScriptNode {
anyhow::bail!("脚本命令被策略拒绝: {} (命令: {})", denied, command);
}
// 危险命令告警:匹配已知危险关键词,仅告警不阻止执行
// 危险关键词告警:匹配到仅告警不阻止执行(与硬拒绝的白/黑名单正交)
let dangerous_keywords = ["rm -rf", "DROP TABLE", "Format", "del /f", "shutdown"];
for &kw in &dangerous_keywords {
if command.contains(kw) {
@@ -143,36 +141,30 @@ const DEFAULT_BLACKLIST: &[&str] = &["rm", "del", "format", "shutdown", "mkfs",
/// 命令执行策略校验:从运行时配置 + 环境变量读取白/黑名单。
///
/// 优先级(高→低):
/// 1. 运行时配置(set_script_safety_config,前端设置页写入)
/// 2. 环境变量 DF_SCRIPT_WHITELIST / DF_SCRIPT_BLACKLIST
/// 3. 默认黑名单兜底(DEFAULT_BLACKLIST)
///
/// 黑名单优先于白名单(黑名单匹配总是拒绝,即便同时在白名单)。
/// 命令名比较前 trim + ASCII 小写规范化;空段被忽略。
/// 优先级(高→低):运行时配置(前端设置页写入) > 环境变量 > 默认黑名单兜底。
/// 黑名单优先于白名单(命中黑名单总是拒绝,即便同时在白名单);命令名比较前 trim + 小写。
///
/// 返回 `Some(reason)` 表示拒绝;返回 `None` 表示放行。
fn check_command_policy(cmd_name: &str) -> Option<&'static str> {
// 步骤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
@@ -186,7 +178,7 @@ fn check_command_policy(cmd_name: &str) -> Option<&'static str> {
return Some("命令在黑名单中");
}
// 白名单:非空时命令名须在名单中才放行
// 环境变量白名单非空时,命令名须在名单
if let Ok(whitelist_raw) = std::env::var("DF_SCRIPT_WHITELIST") {
let whitelist: Vec<String> = whitelist_raw
.split(',')
+16 -7
View File
@@ -1,14 +1,14 @@
//! SubflowNode — 嵌套子工作流节点
//! 子工作流节点 — 加载一个子 DAG 并在当前执行上下文中递归执行
//!
//! 加载一个子模板(DagDef)并在当前执行上下文中递归执行。
//! 用于复用通用流程(如"代码审查"可作为任意主流程的子步骤)。
//! 执行结果不直接展开节点,而是返回子 DAG 的 JSON 快照,由 DagExecutor 消费展开。
use async_trait::async_trait;
use df_workflow::dag_def::DagDef;
use df_workflow::node::{Node, NodeContext, NodeOutput, NodeResult, NodeSchema};
use serde::{Deserialize, Serialize};
/// SubflowNode 配置
/// 子工作流配置
#[derive(Debug, Clone, Serialize, Deserialize)]
struct SubflowConfig {
/// 子工作流 DAG 定义(内联 JSON)
@@ -21,13 +21,13 @@ struct SubflowConfig {
/// 最大递归深度(安全阀)
const MAX_DEPTH: u32 = 10;
/// SubflowNode — 嵌套子工作流
/// 子工作流节点
pub struct SubflowNode;
#[async_trait]
impl Node for SubflowNode {
async fn execute(&self, ctx: NodeContext) -> NodeResult {
tracing::info!("SubflowNode 执行: {}", ctx.node_id);
tracing::info!("SubflowNode 执行: node_id={}", ctx.node_id);
let config: SubflowConfig = serde_json::from_value(ctx.config.clone())
.map_err(|e| anyhow::anyhow!("SubflowNode 配置解析失败: {}", e))?;
@@ -84,11 +84,20 @@ impl Node for SubflowNode {
},
"required": ["dag"]
}),
output: serde_json::json!({}),
output: serde_json::json!({
"type": "object",
"properties": {
"subflow": { "type": "boolean" },
"node_count": { "type": "integer" },
"edge_count": { "type": "integer" },
"max_depth": { "type": "integer" },
"dag": { "type": "object" }
}
}),
}
}
fn node_type(&self) -> &'static str {
fn node_type(&self) -> &str {
"subflow"
}
}