feat(goals): 目标设定 agent 提取并登记测试资产范围
- 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 <[email protected]>
This commit is contained in:
1 parent
c396674d2b
commit
e841bc9332
2 files changed
+42
-5
No files matched your search
+35
-4
@@ -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
|
||||
|
||||
+7
-1
@@ -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})
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user