fix(安全): 修复HTML导出中的XSS漏洞并清理调试日志
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
refactor(日志): 替换console.log为tracing日志系统 style(代码): 移除未使用的代码和依赖项 feat(测试): 添加端到端测试文档和CI工作流 docs(变更日志): 更新CHANGELOG.md记录0.1.0版本变更 perf(构建): 更新依赖版本并优化CI流程
This commit is contained in:
@@ -103,6 +103,22 @@ fn render_markdown(data: &Value) -> String {
|
||||
md
|
||||
}
|
||||
|
||||
/// Escape HTML special characters to prevent XSS
|
||||
fn escape_html(s: &str) -> String {
|
||||
let mut escaped = String::with_capacity(s.len());
|
||||
for ch in s.chars() {
|
||||
match ch {
|
||||
'<' => escaped.push_str("<"),
|
||||
'>' => escaped.push_str(">"),
|
||||
'&' => escaped.push_str("&"),
|
||||
'"' => escaped.push_str("""),
|
||||
'\'' => escaped.push_str("'"),
|
||||
_ => escaped.push(ch),
|
||||
}
|
||||
}
|
||||
escaped
|
||||
}
|
||||
|
||||
/// Render data to HTML
|
||||
fn render_html(data: &Value) -> String {
|
||||
let mut html = String::from(r#"<!DOCTYPE html>
|
||||
@@ -123,11 +139,11 @@ fn render_html(data: &Value) -> String {
|
||||
"#);
|
||||
|
||||
if let Some(title) = data.get("title").and_then(|v| v.as_str()) {
|
||||
html.push_str(&format!("<h1>{}</h1>", title));
|
||||
html.push_str(&format!("<h1>{}</h1>", escape_html(title)));
|
||||
}
|
||||
|
||||
if let Some(description) = data.get("description").and_then(|v| v.as_str()) {
|
||||
html.push_str(&format!("<p>{}</p>", description));
|
||||
html.push_str(&format!("<p>{}</p>", escape_html(description)));
|
||||
}
|
||||
|
||||
if let Some(outline) = data.get("outline") {
|
||||
@@ -135,7 +151,7 @@ fn render_html(data: &Value) -> String {
|
||||
if let Some(items) = outline.get("items").and_then(|v| v.as_array()) {
|
||||
for item in items {
|
||||
if let Some(text) = item.get("title").and_then(|v| v.as_str()) {
|
||||
html.push_str(&format!("<li>{}</li>", text));
|
||||
html.push_str(&format!("<li>{}</li>", escape_html(text)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,10 +163,10 @@ fn render_html(data: &Value) -> String {
|
||||
for scene in scenes {
|
||||
html.push_str("<div class=\"scene\">");
|
||||
if let Some(title) = scene.get("title").and_then(|v| v.as_str()) {
|
||||
html.push_str(&format!("<h3>{}</h3>", title));
|
||||
html.push_str(&format!("<h3>{}</h3>", escape_html(title)));
|
||||
}
|
||||
if let Some(content) = scene.get("content").and_then(|v| v.as_str()) {
|
||||
html.push_str(&format!("<p>{}</p>", content));
|
||||
html.push_str(&format!("<p>{}</p>", escape_html(content)));
|
||||
}
|
||||
html.push_str("</div>");
|
||||
}
|
||||
|
||||
@@ -134,10 +134,10 @@ impl ActionRegistry {
|
||||
max_tokens: Option<u32>,
|
||||
json_mode: bool,
|
||||
) -> Result<Value, ActionError> {
|
||||
println!("[DEBUG execute_llm] Called with template length: {}", template.len());
|
||||
println!("[DEBUG execute_llm] Input HashMap contents:");
|
||||
tracing::debug!(target: "pipeline_actions", "execute_llm: Called with template length: {}", template.len());
|
||||
tracing::debug!(target: "pipeline_actions", "execute_llm: Input HashMap contents:");
|
||||
for (k, v) in &input {
|
||||
println!(" {} => {:?}", k, v);
|
||||
tracing::debug!(target: "pipeline_actions", " {} => {:?}", k, v);
|
||||
}
|
||||
|
||||
if let Some(driver) = &self.llm_driver {
|
||||
@@ -148,7 +148,7 @@ impl ActionRegistry {
|
||||
template.to_string()
|
||||
};
|
||||
|
||||
println!("[DEBUG execute_llm] Calling driver.generate with prompt length: {}", prompt.len());
|
||||
tracing::debug!(target: "pipeline_actions", "execute_llm: Calling driver.generate with prompt length: {}", prompt.len());
|
||||
|
||||
driver.generate(prompt, input, model, temperature, max_tokens, json_mode)
|
||||
.await
|
||||
@@ -346,14 +346,14 @@ impl ActionRegistry {
|
||||
let mut html = String::from("<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>Export</title></head><body>");
|
||||
|
||||
if let Some(title) = data.get("title").and_then(|v| v.as_str()) {
|
||||
html.push_str(&format!("<h1>{}</h1>", title));
|
||||
html.push_str(&format!("<h1>{}</h1>", escape_html(title)));
|
||||
}
|
||||
|
||||
if let Some(items) = data.get("items").and_then(|v| v.as_array()) {
|
||||
html.push_str("<ul>");
|
||||
for item in items {
|
||||
if let Some(text) = item.as_str() {
|
||||
html.push_str(&format!("<li>{}</li>", text));
|
||||
html.push_str(&format!("<li>{}</li>", escape_html(text)));
|
||||
}
|
||||
}
|
||||
html.push_str("</ul>");
|
||||
@@ -364,6 +364,22 @@ impl ActionRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
/// Escape HTML special characters to prevent XSS
|
||||
fn escape_html(s: &str) -> String {
|
||||
let mut escaped = String::with_capacity(s.len());
|
||||
for ch in s.chars() {
|
||||
match ch {
|
||||
'<' => escaped.push_str("<"),
|
||||
'>' => escaped.push_str(">"),
|
||||
'&' => escaped.push_str("&"),
|
||||
'"' => escaped.push_str("""),
|
||||
'\'' => escaped.push_str("'"),
|
||||
_ => escaped.push(ch),
|
||||
}
|
||||
}
|
||||
escaped
|
||||
}
|
||||
|
||||
impl ExportFormat {
|
||||
fn extension(&self) -> &'static str {
|
||||
match self {
|
||||
|
||||
@@ -185,22 +185,22 @@ impl PipelineExecutor {
|
||||
async move {
|
||||
match action {
|
||||
Action::LlmGenerate { template, input, model, temperature, max_tokens, json_mode } => {
|
||||
println!("[DEBUG executor] LlmGenerate action called");
|
||||
println!("[DEBUG executor] Raw input map:");
|
||||
tracing::debug!(target: "pipeline_executor", "LlmGenerate action called");
|
||||
tracing::debug!(target: "pipeline_executor", "Raw input map:");
|
||||
for (k, v) in input {
|
||||
println!(" {} => {}", k, v);
|
||||
tracing::debug!(target: "pipeline_executor", " {} => {}", k, v);
|
||||
}
|
||||
|
||||
// First resolve the template itself (handles ${inputs.xxx}, ${item.xxx}, etc.)
|
||||
let resolved_template = context.resolve(template)?;
|
||||
let resolved_template_str = resolved_template.as_str().unwrap_or(template).to_string();
|
||||
println!("[DEBUG executor] Resolved template (first 300 chars): {}",
|
||||
tracing::debug!(target: "pipeline_executor", "Resolved template (first 300 chars): {}",
|
||||
&resolved_template_str[..resolved_template_str.len().min(300)]);
|
||||
|
||||
let resolved_input = context.resolve_map(input)?;
|
||||
println!("[DEBUG executor] Resolved input map:");
|
||||
tracing::debug!(target: "pipeline_executor", "Resolved input map:");
|
||||
for (k, v) in &resolved_input {
|
||||
println!(" {} => {:?}", k, v);
|
||||
tracing::debug!(target: "pipeline_executor", " {} => {:?}", k, v);
|
||||
}
|
||||
self.action_registry.execute_llm(
|
||||
&resolved_template_str,
|
||||
|
||||
Reference in New Issue
Block a user