修复: 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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user