feat: 拆分 worker 架构并增强 assistant 安全保护
- 新增 daemon/channel/assistant/function/scheduler/logging worker 架构和 JetStream 消息协议 - 修复邮件通知误入 assistant、迁移 checksum、日志 subject 和工具调用无限循环风险 - 新增 assistant 工具轮次熔断、工具结果截断和提示词软收敛规则 - 补充邮件推送关键字、可信发件人配置、升级日志和验证测试
This commit is contained in:
@@ -2,6 +2,7 @@ use axum::extract::{Path, Query};
|
||||
use axum::http::StatusCode;
|
||||
use axum::routing::get;
|
||||
use axum::{Extension, Json, Router};
|
||||
use serde::Serialize;
|
||||
use serde_json::{Value, json};
|
||||
use sqlx::PgPool;
|
||||
|
||||
@@ -17,6 +18,12 @@ pub struct MailRouteState {
|
||||
pub db: PgPool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
struct MailRenderingSettings {
|
||||
trusted_sender: bool,
|
||||
remote_resources_allowed: bool,
|
||||
}
|
||||
|
||||
impl MailRouteState {
|
||||
pub fn new(db: PgPool) -> Self {
|
||||
Self { db }
|
||||
@@ -69,10 +76,12 @@ async fn get_message(
|
||||
Err(error) => eprintln!("同步邮件已读失败 id={id}: {error:#}"),
|
||||
}
|
||||
}
|
||||
let rendering = rendering_settings_for_message(&state.db, &message).await;
|
||||
|
||||
Ok(Json(json!({
|
||||
"success": true,
|
||||
"message": message,
|
||||
"rendering": rendering,
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -107,6 +116,67 @@ async fn mail_config_for_message(
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
async fn rendering_settings_for_message(
|
||||
pool: &PgPool,
|
||||
message: &MailMessage,
|
||||
) -> MailRenderingSettings {
|
||||
let config = match mail_config_for_message(pool, message).await {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
eprintln!("读取邮件渲染配置失败 id={}: {error:#}", message.id);
|
||||
None
|
||||
}
|
||||
};
|
||||
let trusted_sender = config
|
||||
.as_ref()
|
||||
.map(|config| sender_matches_trusted_list(&message.from_label, &config.trusted_senders))
|
||||
.unwrap_or(false);
|
||||
|
||||
MailRenderingSettings {
|
||||
trusted_sender,
|
||||
remote_resources_allowed: trusted_sender,
|
||||
}
|
||||
}
|
||||
|
||||
fn sender_matches_trusted_list(from_label: &str, trusted_senders: &[String]) -> bool {
|
||||
if trusted_senders.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let candidates = sender_candidates(from_label);
|
||||
trusted_senders.iter().any(|trusted| {
|
||||
let trusted_candidates = sender_candidates(trusted);
|
||||
!trusted_candidates.is_empty()
|
||||
&& trusted_candidates
|
||||
.iter()
|
||||
.any(|trusted| candidates.iter().any(|candidate| candidate == trusted))
|
||||
})
|
||||
}
|
||||
|
||||
fn sender_candidates(from_label: &str) -> Vec<String> {
|
||||
let mut values = Vec::new();
|
||||
if let Some(start) = from_label.find('<') {
|
||||
if let Some(end) = from_label[start + 1..].find('>') {
|
||||
push_sender_candidate(&mut values, &from_label[start + 1..start + 1 + end]);
|
||||
}
|
||||
} else {
|
||||
// 没有尖括号时,整个 label 可能就是纯邮箱地址
|
||||
push_sender_candidate(&mut values, from_label);
|
||||
}
|
||||
|
||||
values
|
||||
}
|
||||
|
||||
fn push_sender_candidate(values: &mut Vec<String>, value: &str) {
|
||||
let value = value
|
||||
.trim()
|
||||
.trim_matches(['<', '>', '"', '\'', '(', ')', '[', ']'])
|
||||
.to_ascii_lowercase();
|
||||
if value.contains('@') && !values.iter().any(|existing| existing == &value) {
|
||||
values.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
fn api_not_found(message: impl Into<String>) -> ApiError {
|
||||
(
|
||||
StatusCode::NOT_FOUND,
|
||||
@@ -127,3 +197,32 @@ fn api_internal_error(error: anyhow::Error) -> ApiError {
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::sender_matches_trusted_list;
|
||||
|
||||
#[test]
|
||||
fn matches_trusted_sender_email_from_display_label() {
|
||||
assert!(sender_matches_trusted_list(
|
||||
"Facebook <reminders@facebookmail.com>",
|
||||
&["reminders@facebookmail.com".to_string()]
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trusted_sender_matching_is_case_insensitive_and_exact() {
|
||||
assert!(sender_matches_trusted_list(
|
||||
"Alice <ALICE@example.com>",
|
||||
&["alice@example.com".to_string()]
|
||||
));
|
||||
assert!(sender_matches_trusted_list(
|
||||
"Alice <alice@example.com>",
|
||||
&["Trusted <alice@example.com>".to_string()]
|
||||
));
|
||||
assert!(!sender_matches_trusted_list(
|
||||
"mallory@example.net",
|
||||
&["alice@example.com".to_string()]
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user