mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-07-07 13:07:57 +02:00
Add files via upload
This commit is contained in:
@@ -23,20 +23,59 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// einoSummarizeUserInstruction:压缩历史时保留渗透测试与用户约束关键信息。
|
// einoSummarizeUserInstruction:压缩历史时保留渗透测试与用户约束关键信息。
|
||||||
const einoSummarizeUserInstruction = `在保持所有关键安全测试信息完整的前提下压缩对话历史。
|
// 结构对齐 Eino 最佳实践(禁止工具、<analysis>+<summary>、<all_user_messages>),章节为安全测试领域化。
|
||||||
|
const einoSummarizeUserInstruction = `关键:仅以纯文本响应。禁止调用任何工具(read_file、exec、grep、glob、write、edit 等)。
|
||||||
|
上述对话中已包含全部待压缩上下文;不要要求用户粘贴历史,不要输出「请提供待压缩的对话历史」等占位/meta 回复。
|
||||||
|
工具调用将被拒绝并浪费唯一一次摘要机会。
|
||||||
|
|
||||||
必须保留:已确认漏洞与攻击路径、工具输出中的核心发现、凭证与认证细节、架构与薄弱点、当前进度、失败尝试与死路、策略决策。
|
你的任务:在保持所有关键安全测试信息完整的前提下压缩对话历史,使后续代理能无缝继续同一授权测试任务。
|
||||||
保留精确技术细节(URL、路径、参数、Payload、版本号、报错原文可摘要但要点不丢)。
|
|
||||||
将冗长扫描输出概括为结论;重复发现合并表述。
|
|
||||||
已枚举资产须保留**可继承的摘要**:主域、关键子域/主机短表(或数量+代表样例)、高价值目标与已识别服务/端口要点,避免后续子代理因「看不见清单」而重复全量枚举。
|
|
||||||
|
|
||||||
用户消息中的约束须精确保留(可摘要表述,但要点不可丢或改写):
|
压缩原则:
|
||||||
- 授权测试目标、范围与禁止项(域名、路径、IP、环境)
|
- 必须保留:已确认漏洞与攻击路径、工具输出核心发现、凭证与认证细节、架构与薄弱点、当前进度、失败尝试与死路、策略决策
|
||||||
- 用户提供的凭证、账号、Cookie、Token(敏感值原文保留)
|
- 保留精确技术细节(URL、路径、参数、Payload、版本号;报错原文可摘要但要点不丢)
|
||||||
|
- 冗长扫描输出概括为结论;重复发现合并表述
|
||||||
|
- 已枚举资产须保留可继承摘要:主域、关键子域/主机短表(或数量+代表样例)、高价值目标、已识别服务/端口要点
|
||||||
|
|
||||||
|
输出格式(严格遵循,仅一轮回复):
|
||||||
|
1. 先输出 <analysis> 块:按时间顺序梳理对话,检查是否涵盖下方各章节要点;analysis 仅供自检,保持简洁(建议 ≤400 字)
|
||||||
|
2. 再输出 <summary> 块:按以下章节写入可继承的压缩报告(无信息处写「无」,禁止留空模板占位符)
|
||||||
|
|
||||||
|
<summary>
|
||||||
|
## 1. 授权范围与约束
|
||||||
|
- 目标/范围/禁止项(域名、路径、IP、环境)
|
||||||
|
- 凭证/认证信息(账号、Token、Cookie;敏感值原文保留)
|
||||||
- 用户指定的方法、工具、优先级与待办
|
- 用户指定的方法、工具、优先级与待办
|
||||||
- 用户明确的否定约束(不要测什么、不要用什么手法)
|
- 否定约束(不测什么、不用什么手法)
|
||||||
|
|
||||||
输出须使后续代理能无缝继续同一授权测试任务。`
|
## 2. 资产与服务枚举摘要
|
||||||
|
- 主域/核心资产、关键子域或主机短表(或数量+代表样例)
|
||||||
|
- 高价值目标、已识别服务/端口要点
|
||||||
|
- 资产状态(存活/可攻/已排除/待验证)
|
||||||
|
|
||||||
|
## 3. 架构与已知薄弱点
|
||||||
|
- 技术栈/部署拓扑/信任边界
|
||||||
|
- 已识别薄弱点列表
|
||||||
|
|
||||||
|
## 4. 已确认漏洞与攻击路径
|
||||||
|
- 漏洞名/CVE、URL/路径、参数/Payload、PoC 要点、影响等级
|
||||||
|
- 攻击链/利用路径(步骤化)
|
||||||
|
|
||||||
|
## 5. 工具核心发现与扫描结论
|
||||||
|
- 各工具结论(概括核心输出,非冗长日志)
|
||||||
|
- 重复发现合并表述
|
||||||
|
|
||||||
|
## 6. 所有用户消息
|
||||||
|
<all_user_messages>
|
||||||
|
- [逐条列出非 tool 结果的用户消息要点;敏感约束与原文措辞尽量保留]
|
||||||
|
</all_user_messages>
|
||||||
|
|
||||||
|
## 7. 当前进度、策略决策与下一步
|
||||||
|
- 当前位置(已完成/进行中/卡点)
|
||||||
|
- 失败尝试与死路(方法、现象/报错摘要、结论)
|
||||||
|
- 策略决策与下一步具体操作(须与最近用户请求及未完成任务一致)
|
||||||
|
</summary>
|
||||||
|
|
||||||
|
提醒:不要调用任何工具;必须基于上文已有对话直接输出 <analysis> 与 <summary>,勿输出 analysis 以外的正文。`
|
||||||
|
|
||||||
// newEinoSummarizationMiddleware 使用 Eino ADK Summarization 中间件(见 https://www.cloudwego.io/zh/docs/eino/core_modules/eino_adk/eino_adk_chatmodelagentmiddleware/middleware_summarization/)。
|
// newEinoSummarizationMiddleware 使用 Eino ADK Summarization 中间件(见 https://www.cloudwego.io/zh/docs/eino/core_modules/eino_adk/eino_adk_chatmodelagentmiddleware/middleware_summarization/)。
|
||||||
// 触发阈值:估算 token 超过 openai.max_total_tokens * summarization_trigger_ratio(默认 0.8)时摘要。
|
// 触发阈值:估算 token 超过 openai.max_total_tokens * summarization_trigger_ratio(默认 0.8)时摘要。
|
||||||
@@ -150,6 +189,7 @@ func newEinoSummarizationMiddleware(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Finalize: func(ctx context.Context, originalMessages []adk.Message, summary adk.Message) ([]adk.Message, error) {
|
Finalize: func(ctx context.Context, originalMessages []adk.Message, summary adk.Message) ([]adk.Message, error) {
|
||||||
|
summary = stripAnalysisFromSummarizationMessage(summary)
|
||||||
out, ferr := summarizeFinalizeWithRecentAssistantToolTrail(ctx, originalMessages, summary, tokenCounter, recentTrailMax)
|
out, ferr := summarizeFinalizeWithRecentAssistantToolTrail(ctx, originalMessages, summary, tokenCounter, recentTrailMax)
|
||||||
if ferr != nil {
|
if ferr != nil {
|
||||||
return nil, ferr
|
return nil, ferr
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package multiagent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cloudwego/eino/adk"
|
||||||
|
"github.com/cloudwego/eino/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
summarizationAnalysisBlockRegex = regexp.MustCompile(`(?is)<analysis>\s*.*?\s*</analysis>`)
|
||||||
|
summarizationSummaryBlockRegex = regexp.MustCompile(`(?is)<summary>\s*(.*?)\s*</summary>`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// stripAnalysisFromSummarizationMessage removes the <analysis> block from a post-processed
|
||||||
|
// Eino summary user message. Analysis helps one-shot generation quality but should not
|
||||||
|
// occupy continuation context after compaction.
|
||||||
|
func stripAnalysisFromSummarizationMessage(msg adk.Message) adk.Message {
|
||||||
|
if msg == nil {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
cloned := *msg
|
||||||
|
if cloned.Content != "" {
|
||||||
|
cloned.Content = stripAnalysisFromSummarizationText(cloned.Content)
|
||||||
|
}
|
||||||
|
if len(cloned.UserInputMultiContent) > 0 {
|
||||||
|
parts := make([]schema.MessageInputPart, len(cloned.UserInputMultiContent))
|
||||||
|
copy(parts, cloned.UserInputMultiContent)
|
||||||
|
// Only the first text part carries model output plus Eino preamble/transcript path.
|
||||||
|
for i := range parts {
|
||||||
|
if parts[i].Type != schema.ChatMessagePartTypeText || parts[i].Text == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
parts[i].Text = stripAnalysisFromSummarizationText(parts[i].Text)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
cloned.UserInputMultiContent = parts
|
||||||
|
}
|
||||||
|
return &cloned
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripAnalysisFromSummarizationText(text string) string {
|
||||||
|
text = strings.TrimSpace(text)
|
||||||
|
if text == "" {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
stripped := strings.TrimSpace(summarizationAnalysisBlockRegex.ReplaceAllString(text, ""))
|
||||||
|
if stripped == "" {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
return stripped
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractSummarizationSummaryBody returns the inner text of the last <summary> block when present.
|
||||||
|
// Used by tests and optional strict compaction paths.
|
||||||
|
func extractSummarizationSummaryBody(text string) (string, bool) {
|
||||||
|
text = strings.TrimSpace(text)
|
||||||
|
if text == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
all := summarizationSummaryBlockRegex.FindAllStringSubmatch(text, -1)
|
||||||
|
if len(all) == 0 || len(all[len(all)-1]) < 2 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
body := strings.TrimSpace(all[len(all)-1][1])
|
||||||
|
if body == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return body, true
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package multiagent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cloudwego/eino/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStripAnalysisFromSummarizationText(t *testing.T) {
|
||||||
|
in := "<analysis>internal notes</analysis>\n\n<summary>\n## 1. 授权\n- example.com\n</summary>"
|
||||||
|
got := stripAnalysisFromSummarizationText(in)
|
||||||
|
if strings.Contains(got, "<analysis>") {
|
||||||
|
t.Fatalf("analysis block should be removed: %q", got)
|
||||||
|
}
|
||||||
|
if !strings.Contains(got, "## 1. 授权") {
|
||||||
|
t.Fatalf("summary body should remain: %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStripAnalysisFromSummarizationMessage_UserInputMultiContent(t *testing.T) {
|
||||||
|
msg := &schema.Message{
|
||||||
|
Role: schema.User,
|
||||||
|
UserInputMultiContent: []schema.MessageInputPart{
|
||||||
|
{
|
||||||
|
Type: schema.ChatMessagePartTypeText,
|
||||||
|
Text: "此会话延续自此前一段因上下文耗尽而终止的对话。\n\n<analysis>draft</analysis>\n<summary>body</summary>\n\n完整记录位于:/tmp/transcript.txt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: schema.ChatMessagePartTypeText,
|
||||||
|
Text: "请从我们中断的地方继续对话,无需向用户提出任何进一步的问题。",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
out := stripAnalysisFromSummarizationMessage(msg)
|
||||||
|
if len(out.UserInputMultiContent) != 2 {
|
||||||
|
t.Fatalf("expected 2 parts, got %d", len(out.UserInputMultiContent))
|
||||||
|
}
|
||||||
|
if strings.Contains(out.UserInputMultiContent[0].Text, "<analysis>") {
|
||||||
|
t.Fatalf("part 0 should drop analysis: %q", out.UserInputMultiContent[0].Text)
|
||||||
|
}
|
||||||
|
if !strings.Contains(out.UserInputMultiContent[0].Text, "<summary>body</summary>") {
|
||||||
|
t.Fatalf("part 0 should keep summary: %q", out.UserInputMultiContent[0].Text)
|
||||||
|
}
|
||||||
|
if out.UserInputMultiContent[1].Text != "请从我们中断的地方继续对话,无需向用户提出任何进一步的问题。" {
|
||||||
|
t.Fatalf("continue instruction part should be unchanged: %q", out.UserInputMultiContent[1].Text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractSummarizationSummaryBody(t *testing.T) {
|
||||||
|
body, ok := extractSummarizationSummaryBody("<analysis>x</analysis><summary> kept </summary>")
|
||||||
|
if !ok || body != "kept" {
|
||||||
|
t.Fatalf("extract summary body: ok=%v body=%q", ok, body)
|
||||||
|
}
|
||||||
|
_, ok = extractSummarizationSummaryBody("plain text only")
|
||||||
|
if ok {
|
||||||
|
t.Fatal("expected false for plain text")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStripAnalysisFromSummarizationText_NoAnalysisUnchanged(t *testing.T) {
|
||||||
|
in := "<summary>only summary</summary>"
|
||||||
|
got := stripAnalysisFromSummarizationText(in)
|
||||||
|
if got != in {
|
||||||
|
t.Fatalf("expected unchanged text, got %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user