feat: 拆分 worker 架构并增强 assistant 安全保护
- 新增 daemon/channel/assistant/function/scheduler/logging worker 架构和 JetStream 消息协议 - 修复邮件通知误入 assistant、迁移 checksum、日志 subject 和工具调用无限循环风险 - 新增 assistant 工具轮次熔断、工具结果截断和提示词软收敛规则 - 补充邮件推送关键字、可信发件人配置、升级日志和验证测试
This commit is contained in:
@@ -23,6 +23,8 @@ interface MailConfig {
|
||||
mailbox: string;
|
||||
notify_account_id: string;
|
||||
notify_user_id: string;
|
||||
notification_subject_keywords: string[];
|
||||
trusted_senders: string[];
|
||||
poll_seconds: number;
|
||||
fetch_limit: number;
|
||||
accept_invalid_certs: boolean;
|
||||
@@ -78,6 +80,8 @@ export function ConfigsPage() {
|
||||
if (!t) return "-";
|
||||
return new Date(t).toLocaleString("zh-CN", { timeZone: "UTC" }) + " UTC";
|
||||
};
|
||||
const fmtList = (values?: string[], empty = "-") =>
|
||||
values && values.length > 0 ? values.join(", ") : empty;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -185,6 +189,12 @@ export function ConfigsPage() {
|
||||
<dd className="text-gray-700 break-all">{item.notify_account_id}</dd>
|
||||
<dt className="text-gray-400">通知用户</dt>
|
||||
<dd className="text-gray-700 break-all">{item.notify_user_id}</dd>
|
||||
<dt className="text-gray-400">推送标题关键字</dt>
|
||||
<dd className="text-gray-700 break-all">
|
||||
{fmtList(item.notification_subject_keywords, "全部推送")}
|
||||
</dd>
|
||||
<dt className="text-gray-400">可信发件人</dt>
|
||||
<dd className="text-gray-700 break-all">{fmtList(item.trusted_senders)}</dd>
|
||||
<dt className="text-gray-400">轮询间隔</dt>
|
||||
<dd className="text-gray-700">{item.poll_seconds}s</dd>
|
||||
<dt className="text-gray-400">抓取上限</dt>
|
||||
@@ -210,4 +220,4 @@ export function ConfigsPage() {
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,12 @@ interface MailMessage {
|
||||
interface MailDetailResponse {
|
||||
success: boolean;
|
||||
message: MailMessage;
|
||||
rendering?: MailRenderingSettings;
|
||||
}
|
||||
|
||||
interface MailRenderingSettings {
|
||||
trusted_sender: boolean;
|
||||
remote_resources_allowed: boolean;
|
||||
}
|
||||
|
||||
function formatBytes(bytes: number): string {
|
||||
@@ -50,7 +56,7 @@ function contentIsHtml(content: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function sandboxedEmailHtml(content: string): string {
|
||||
function sandboxedEmailHtml(content: string, allowRemoteResources: boolean): string {
|
||||
const parsed = new DOMParser().parseFromString(content, "text/html");
|
||||
const styles = Array.from(parsed.head.querySelectorAll("style"))
|
||||
.map((style) => style.textContent || "")
|
||||
@@ -58,8 +64,9 @@ function sandboxedEmailHtml(content: string): string {
|
||||
.join("\n");
|
||||
const body = parsed.body.innerHTML.trim() || content;
|
||||
const styleTag = styles ? `<style>${styles}</style>` : "";
|
||||
const imageSources = allowRemoteResources ? "data: cid: https: http:" : "data: cid:";
|
||||
|
||||
return `<!doctype html><html><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src data: cid: https: http:; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'">${styleTag}</head><body>${body}</body></html>`;
|
||||
return `<!doctype html><html><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${imageSources}; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'">${styleTag}</head><body>${body}</body></html>`;
|
||||
}
|
||||
|
||||
function renderTextContent(content: string) {
|
||||
@@ -119,6 +126,9 @@ export function MailDetailPage() {
|
||||
? new Date(msg.received_at).toLocaleString("zh-CN", { timeZone: "UTC" }) + " UTC"
|
||||
: "-";
|
||||
const htmlContent = contentIsHtml(msg.content);
|
||||
const rendering = data.rendering;
|
||||
const trustedSender = rendering?.trusted_sender === true;
|
||||
const remoteResourcesAllowed = rendering?.remote_resources_allowed === true;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -146,6 +156,13 @@ export function MailDetailPage() {
|
||||
<span className="font-semibold text-gray-700">来源</span>
|
||||
{msg.source_key}
|
||||
</span>
|
||||
<span
|
||||
className={`inline-flex items-center px-2 py-0.5 rounded-md text-xs font-semibold ${
|
||||
trustedSender ? "bg-green-50 text-green-700" : "bg-amber-50 text-amber-700"
|
||||
}`}
|
||||
>
|
||||
{trustedSender ? "可信发件人" : "未信任发件人"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -187,9 +204,14 @@ export function MailDetailPage() {
|
||||
|
||||
{htmlContent ? (
|
||||
<div className="bg-white rounded-xl border border-gray-200 overflow-hidden">
|
||||
{!remoteResourcesAllowed && (
|
||||
<div className="px-4 md:px-5 py-3 text-sm text-amber-700 bg-amber-50 border-b border-amber-100">
|
||||
发件人未命中可信列表,已阻止邮件中的远程图片资源。
|
||||
</div>
|
||||
)}
|
||||
<iframe
|
||||
sandbox=""
|
||||
srcDoc={sandboxedEmailHtml(msg.content)}
|
||||
srcDoc={sandboxedEmailHtml(msg.content, remoteResourcesAllowed)}
|
||||
className="w-full min-h-[420px] border-0 block bg-white"
|
||||
loading="lazy"
|
||||
title="邮件内容"
|
||||
|
||||
Reference in New Issue
Block a user