From e841bc9332a179afa60fbb6ba5c2727817538c7d Mon Sep 17 00:00:00 2001 From: Autumn-27 <53844290+Autumn-27@users.noreply.github.com> Date: Sat, 8 Aug 2026 12:30:51 -0400 Subject: [PATCH] =?UTF-8?q?feat(goals):=20=E7=9B=AE=E6=A0=87=E8=AE=BE?= =?UTF-8?q?=E5=AE=9A=20agent=20=E6=8F=90=E5=8F=96=E5=B9=B6=E7=99=BB?= =?UTF-8?q?=E8=AE=B0=E6=B5=8B=E8=AF=95=E8=B5=84=E4=BA=A7=E8=8C=83=E5=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DecomposeGoals 新增 as/taskID 参数,有效时复用 add_task_scope 工具挂入本次调用 - 追加 code-owned tail(仅工具挂上时):从目标/描述提取明确的 root_domain/subdomain/ip/cidr 并登记为 task_scope;只登记明确写出的、 拿不准选更精确的;不处理 company(任务刚建公司通常未入库、名字解析不上, 留给 plan 阶段) - MaxTurns 4→6;server createGoals 传入 AssetStore + 数字 taskID Co-Authored-By: Claude Opus 4.8 --- agent/goals.go | 39 +++++++++++++++++++++++++++++++++++---- server/goals.go | 8 +++++++- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/agent/goals.go b/agent/goals.go index cac4d0e..8c7b47a 100644 --- a/agent/goals.go +++ b/agent/goals.go @@ -5,10 +5,10 @@ import ( "encoding/json" "strings" + "github.com/Autumn-27/artex/db" "github.com/Autumn-27/norma/agentcore" acperm "github.com/Autumn-27/norma/permission" actool "github.com/Autumn-27/norma/tool" - "github.com/Autumn-27/artex/db" ) // goalsDefaultTmpl is the built-in EDITABLE body (段 [A]) of the goals-decomposer @@ -31,6 +31,26 @@ const goalsDefaultTmpl = `你是渗透测试目标分解器。你的职责是从 调用 set_goals 提交结果。` +// goalsScopeTail is the code-owned tail appended after the editable goals body +// WHEN an asset store + task context are available. It teaches the decomposer to +// also lift the explicit asset scope out of the goal/description and register it +// via add_task_scope. Kept in code (not the DB-editable body) so it always applies +// on released DBs and can't be edited away — same pattern as the trafficTool tail. +const goalsScopeTail = ` + +**额外职责:登记测试资产范围** +除拆分目标外,你还要从上面的「任务目标 / 任务描述」里识别出**明确给出的测试资产范围**,并调用 add_task_scope 登记为本任务范围(资产测试覆盖度的分母,也是授权边界): +- 完整根域名(如 example.com,其所有子域都在测试范围)→ kind=root_domain,value=example.com +- 单个精确子域名(如 app.example.com,只测这一个)→ kind=subdomain,value=app.example.com +- IP 或网段 → kind=ip / cidr,value=IP 或 CIDR +规则(务必遵守): +- 只登记**目标/描述里明确写出**的范围;严禁臆造或推断未提及的域名/IP。 +- 粒度拿不准时选更**精确**的那种(宁可 subdomain,不要轻易升级为 root_domain)。 +- 只处理域名 / IP / 网段;**不要**登记公司范围(company)——任务刚建立、资产系统里通常还没有这家公司,登记不上,公司级范围交由后续 plan 阶段处理。 +- reason 简述依据来自哪句话,便于审计。 +- 若目标/描述中没有任何明确资产范围,则**不要**调用 add_task_scope。 +先用 add_task_scope 登记范围(如有),再调用 set_goals 提交目标。` + // GoalSpec is one decomposed objective. type GoalSpec struct { Text string `json:"text"` @@ -48,7 +68,10 @@ type GoalSpec struct { // // emit, when non-nil, receives every LLM step (thinking/tool_use/result) with // Worker="planner" so the round-0 goal-decomposition activity is visible in the UI. -func DecomposeGoals(ctx context.Context, c Config, goalText, desc string, emit func(db.Activity)) []GoalSpec { +// +// as + taskID, when non-nil/positive, wire the add_task_scope tool so the +// decomposer can register the explicit asset scope it extracts from the goal. +func DecomposeGoals(ctx context.Context, c Config, goalText, desc string, as *db.AssetStore, taskID int64, emit func(db.Activity)) []GoalSpec { prov, err := c.NewProvider() if err != nil { return nil @@ -79,6 +102,14 @@ func DecomposeGoals(ctx context.Context, c Config, goalText, desc string, emit f // {{.EngagementDescription}} template var — else a prompt that references the var // would inject the description twice. System prompt stays pure static instructions. sys := renderSystem("goals", goalsDefaultTmpl, GoalsVars{}) + tools := []actool.CoreTool{submitTool} + // Wire add_task_scope only when we have a real asset store + task to write to. + // The scope-extraction tail is appended in lockstep so the prompt never asks for + // a tool that isn't present. + if as != nil && taskID > 0 { + tools = append(tools, (&ToolSet{as: as, taskID: taskID}).addTaskScope()) + sys += goalsScopeTail + } userMsg := "任务目标:\n" + goalText if d := strings.TrimSpace(desc); d != "" { userMsg += "\n\n任务描述(背景信息,可能含靶标范围/flag 数量/交战说明;仅供参考,不要臆造其中未提及的内容):\n" + d @@ -94,10 +125,10 @@ func DecomposeGoals(ctx context.Context, c Config, goalText, desc string, emit f captureRun(ctx, agentcore.Options{ Provider: prov, SystemPrompt: []string{sys}, - Tools: []actool.CoreTool{submitTool}, + Tools: tools, PermissionMode: acperm.ModeBypass, DisableBackgroundTasks: true, - MaxTurns: 4, + MaxTurns: 6, }, userMsg, captureEmit) if !captured { return nil diff --git a/server/goals.go b/server/goals.go index 2822737..fcc008a 100644 --- a/server/goals.go +++ b/server/goals.go @@ -3,6 +3,7 @@ package server import ( "context" "log" + "strconv" "strings" "github.com/Autumn-27/artex/agent" @@ -28,7 +29,12 @@ func (s *Server) createGoals(ctx context.Context, t *Task, emit func(db.Activity // splitter (which shredded URLs / made meaningless 2-way splits). var specs []goalSpec if cfg, ok := s.goalsLLMConfig(t); ok { - for _, g := range agent.DecomposeGoals(ctx, cfg, t.Goal, t.Description, emit) { + var as *db.AssetStore + if s.m != nil { + as = s.m.Assets() + } + taskID, _ := strconv.ParseInt(t.ID, 10, 64) + for _, g := range agent.DecomposeGoals(ctx, cfg, t.Goal, t.Description, as, taskID, emit) { if strings.TrimSpace(g.Text) != "" { specs = append(specs, goalSpec{Text: g.Text, VulnClass: g.VulnClass}) }