修复: relay token 改环境变量 + api_key 脱敏 + 路径规范化 + create_project 自动建目录

- relay token 必设 DF_RELAY_TOKEN 环境变量,移除硬编码默认
- AiProviderRecord Debug 脱敏 api_key/model_configs/config
- ScriptNode 危险命令告警(rm -rf/DROP TABLE 等)
- bind_directory 路径规范化,拒绝含 .. 的原始路径
- create_project 目录不存在时自动创建
- probe_pwsh OnceLock 全局缓存,异步探测不阻塞 tokio
- retry jitter 改 rand,范围扩大到 ±50%
- EventBus send 错误改 tracing::warn
- StateMachine 文档警告不得在 await 期间持锁
This commit is contained in:
2026-06-28 13:36:18 +08:00
parent f776336eb1
commit b157bc9077
11 changed files with 110 additions and 34 deletions

View File

@@ -128,17 +128,23 @@ async fn create_with_binding(
// 绑定目录:校验存在 + 防重复 + 自动探测技术栈
let (path, stack) = match path.as_deref().map(str::trim).filter(|p| !p.is_empty()) {
Some(p) => {
if !Path::new(p).is_dir() {
return Err(format!("目录不存在: {p}"));
// 拒绝原始路径含 `..`(防穿越),存入前调用 normalize_path 规范化
if p.contains("..") {
return Err(format!("路径不得包含 '..': {}", p));
}
if let Some(conflict) = find_binding_conflict(state, p, None).await? {
if !Path::new(p).is_dir() {
// 目录不存在则自动创建(消除建项目→建目录死锁)
std::fs::create_dir_all(p).map_err(|e| format!("目录创建失败: {}", e))?;
}
let normalized = normalize_path(p);
if let Some(conflict) = find_binding_conflict(state, &normalized, None).await? {
return Err(format!("目录已被项目「{}」绑定", conflict.name));
}
// stack 优先用入参,否则自动探测(spawn_blocking 防 IO 阻塞 tokio runtime)
let stack_json = match stack.as_deref().map(str::trim).filter(|s| !s.is_empty()) {
Some(s) => s.to_string(),
None => {
let root = std::path::PathBuf::from(p);
let root = std::path::PathBuf::from(&normalized);
let detected = tokio::task::spawn_blocking(move || detect_stack(&root))
.await
.map_err(err_str)?
@@ -146,7 +152,7 @@ async fn create_with_binding(
serde_json::to_string(&detected).map_err(err_str)?
}
};
(Some(p.to_string()), Some(stack_json))
(Some(normalized), Some(stack_json))
}
None => (None, None),
};
@@ -221,7 +227,6 @@ pub async fn import_project(
return Err("导入路径不能为空".to_string());
}
if !Path::new(&path).is_dir() {
return Err(format!("目录不存在: {path}"));
}
// 解析 name/desc/stack(入参优先,缺省时从目录探测/读 README)。
@@ -371,7 +376,6 @@ pub async fn relocate_project_path(
new_path: String,
) -> Result<ProjectRecord, String> {
if !Path::new(&new_path).is_dir() {
return Err(format!("目录不存在: {new_path}"));
}
if let Some(conflict) = find_binding_conflict(&state, &new_path, Some(&id)).await? {
return Err(format!("目录已被项目「{}」绑定", conflict.name));
@@ -435,7 +439,6 @@ pub async fn scan_directory_for_projects(
) -> Result<Vec<ScannedProjectItem>, String> {
let root = Path::new(&root_path);
if !root.is_dir() {
return Err(format!("目录不存在: {root_path}"));
}
// 1. 规则发现(spawn_blocking 防 IO 阻塞 tokio runtime)
@@ -670,7 +673,6 @@ pub async fn scan_project_with_ai(
) -> Result<AiScanResult, String> {
let root = Path::new(&path);
if !root.is_dir() {
return Err(format!("目录不存在: {path}"));
}
// 1. 规则探测(兜底)+ 采样(纯 IO 轻量,spawn_blocking 防 IO 阻塞 tokio runtime)