feat: planner/worker 态势移出 system 入首轮 user + bench 提示词/调度器优化 + plan 心跳前端对齐 10min
- agent: planner/worker 把易变运行期数据(意图/态势/收尾词)从 system 移到本轮 user 输入, system 每轮/每 session 稳定更利于缓存;移除 worker 的 cross-trace 工具引导段 - web: plan 心跳前端默认/下限对齐后端 10min(600s),清理 300 旧注释 - bench: challenge_review 经验抽取结构化(区分 confirmed/inferred/negative、限制"已穷尽"、 结构化 hint 格式);worker.md 补深度挖掘/目录扫描引导;scheduler 加 FAST_POLL 快轮询 - deps: norma v0.2.8 → v0.2.9
This commit is contained in:
1 parent
aafab015e7
commit
4c33317c6a
10 files changed
+111
-65
No files matched your search
@@ -47,3 +47,6 @@ Thumbs.db
|
||||
|
||||
# 调度器运行/模拟产生的事件日志(测试产物,不提交)
|
||||
*-events.csv
|
||||
|
||||
# bench 调试目录(本地调试产物,不提交)
|
||||
/bench/debug/
|
||||
+11
-10
@@ -256,19 +256,20 @@ func (p *Planner) Plan(ctx context.Context, taskID int64, as *db.AssetStore, ts
|
||||
base := append(tsx.PlannerTools(), actool.DefaultTools()...)
|
||||
tools, def, cleanup := AugmentTools(ctx, "planner", base)
|
||||
defer cleanup()
|
||||
// 关键态势(刚完成的意图 + 预取的完整图)放进 system prompt,而不是 user 输入:
|
||||
// system 段永不被 compaction 压缩,长回合里也保证这份态势始终在场;轮内固定,还能吃到
|
||||
// system 缓存。它落在指令之后、deferred 工具块之前(deferredSystem 保证块在最后)。
|
||||
// 关键态势(刚完成的意图 + 预取的完整图)改放【本轮 user 输入】(见下方 input),system
|
||||
// 只留静态规划正文。move-out 让 system 每轮稳定、更利于缓存;代价是若单轮变长,态势可能
|
||||
// 被 compaction 压缩(planner 单轮通常短,风险低)。situational 会拼进下方 input。
|
||||
situational := renderTriggers(ts, triggers) + renderGraphOverview(tsx.graphOverviewData())
|
||||
// 任务级 deadline / 终局模式(经 ctx 注入,见 taskclock.go)。终局那一轮把任务超时
|
||||
// planner 收尾词作为【本轮操作指令】直接注入 system,让它只做最后目标判定、不产新意图。
|
||||
// planner 收尾词作为【本轮操作指令】拼进本轮 user 输入(随 situational),让它只做最后
|
||||
// 目标判定、不产新意图。
|
||||
tc := taskClockFrom(ctx)
|
||||
if tc.Final {
|
||||
situational += "\n\n【任务终局收尾(本轮特殊指令,覆盖上面的常规规划流程)】:" + resolveTaskTimeoutWrapup("planner")
|
||||
}
|
||||
// 本任务的工作目录 <workDir>/<taskID>,先建好。
|
||||
taskDir := ensureRunDir(p.workDir, taskID, 0)
|
||||
system, boundary := deferredSystem(plannerSystem(goal, taskDir)+situational, def)
|
||||
system, boundary := deferredSystem(plannerSystem(goal, taskDir), def)
|
||||
// planner 无自身墙钟预算;有 deadline 时把 MaxDuration 夹逼到剩余,让在跑的规划轮在
|
||||
// 任务到点时进收尾(因超时→任务超时词,因步数→per-run 词)。
|
||||
maxDur, clamped := clampMaxDuration(tc.DeadlineUnix, 0)
|
||||
@@ -311,15 +312,15 @@ func (p *Planner) Plan(ctx context.Context, taskID int64, as *db.AssetStore, ts
|
||||
opts.Transcript = p.tx
|
||||
opts.SessionID = fmt.Sprintf("exp%d-planner", ts.ID())
|
||||
}
|
||||
// 态势(刚完成的意图 + 完整图)已在上面拼进 system prompt(抗压缩、轮内常驻)。这里
|
||||
// 的 user 输入只留指令 + 跨唤醒待办(todo 是模型自己的规划便签,可再生,放 user 即可)。
|
||||
// 开场白按「本轮有无具体变动」分两种:有变动 → 指向上方【实际变动】块;无变动
|
||||
// 态势(刚完成的意图 + 完整图)现在拼进本轮 user 输入(见下方 input)。user 里还有
|
||||
// 指令 + 跨唤醒待办(todo 是模型自己的规划便签,可再生,放 user 即可)。
|
||||
// 开场白按「本轮有无具体变动」分两种:有变动 → 指向下方【实际变动】块;无变动
|
||||
// (心跳定时巡检 / hint / 恢复等) → 别谎称"图发生了变化",转而提示顺带复查在跑意图。
|
||||
lead := "刚有具体变动(见上方 system 的【本次触发本轮的实际变动】),据此规划下一步:"
|
||||
lead := "刚有具体变动(见下面的【本次触发本轮的实际变动】),据此规划下一步:"
|
||||
if len(triggers) == 0 {
|
||||
lead = "本轮是**定时巡检(心跳到点)/无具体变动信号**的唤醒——图不一定有新变动。顺带复查在跑意图:长时间无进展或跑偏的用 steer_work 纠偏、方向整个错的用 kill_work 止损;再判定目标、决定是否补方向:"
|
||||
}
|
||||
input := lead + "读取上方 system 里的态势,判定目标。**本轮若无未被覆盖的新方向,直接结束即可(生成 0 个意图是正常且常见的,尤其刚派完意图在等 worker 产出时)。绝不要为了“结束本轮”去调 goal_met——goal_met 会【立即结束整个任务】,只在你确认目标已【真正达成】(已拿到目标成果/已确认目标漏洞)时才调;未达成就用 prove_goal 逐个标记、或什么都不调直接结束。**" +
|
||||
input := lead + situational + "\n\n据上面的态势,判定目标。**本轮若无未被覆盖的新方向,直接结束即可(生成 0 个意图是正常且常见的,尤其刚派完意图在等 worker 产出时)。绝不要为了“结束本轮”去调 goal_met——goal_met 会【立即结束整个任务】,只在你确认目标已【真正达成】(已拿到目标成果/已确认目标漏洞)时才调;未达成就用 prove_goal 逐个标记、或什么都不调直接结束。**" +
|
||||
renderPlannerTodos(opts.Todos.List())
|
||||
// 有 deadline 夹逼时加硬 ctx 兜底(软预算 + grace),防单轮卡死绕过轮边界软超时。
|
||||
runCtx := ctx
|
||||
|
||||
+16
-15
@@ -140,7 +140,6 @@ const workerDefaultTmpl = `你是一个授权渗透测试系统的"执行者"(wo
|
||||
- record_fact:把探索【事实/结论】写入探索图并连到意图(传 intent_id)。正向/否定结论、观察、判断用它;**一次探索的多个观察汇总成一条事实**(summary 总结一句话 + detail 放细节),不要一个属性一条。真有多条不同结论才用 facts 数组。**只写你真实看到的**:给 evidence(一行关键证据:命令+关键输出,简洁,别粘大段——细节在 detail)、标 confidence(observed 直接看到 / inferred 推断);**否定结论**(不可注入/端口关闭等)尤其要给证据、证据弱就标 inferred,别让错的否定误导规划者放弃方向。
|
||||
- report_finding:确认漏洞 → 记录(含 PoC,传 intent_id=你领到的意图id)。**只有你在本次运行里真实触发过该漏洞、拿到了可复现的证据(请求/响应或命令输出)才用它。** 严禁把下列当作已确认漏洞上报:仅凭版本号/指纹匹配到某 CVE、仅凭"参数看起来可注入"、仅凭外部漏洞库/更新日志/代码 diff 推断。**不要用查 CVE 库或"对比补丁版本"替代实际触发。** 触发不了但确有嫌疑,就用 record_fact 记一条 confidence=inferred 的事实(描述嫌疑点+为何未能触发),交给规划者派后续意图,别硬记成 finding。
|
||||
- list_assets(查询资产,非探索节点) / asset_neighbors / list_facts(探索事实) / list_findings(漏洞) / node_detail(探索节点 id,非资产 id):按需查上下文。
|
||||
- 【必要时才会使用,大多数上下文都在本次会话中】search_all_worker_traces / list_worker_traces / get_worker_trace:**跨 work 复用信息**(别的 work 执行过程里出现过、却没写进 fact 的东西)。你看不到探索图,但可以:search_all_worker_traces(q) 按关键字搜全部 work 的过程(返回带 intent_id);list_worker_traces 看有哪些 work 跑过;get_worker_trace(intent_id) 看某 work 的步骤摘要、get_worker_trace(intent_id, step_ids=[…]) 取那几步完整内容(一次≤5个)。**仅用于复用他人观察、避免重复劳动——不改变你的任务边界(仍只做你这条意图)。**
|
||||
|
||||
只在授权范围内操作。完成本意图后用一句话总结你做了什么、写回了哪些事实。务实、克制、聚焦这一条意图。`
|
||||
|
||||
@@ -188,9 +187,10 @@ func workerSystem(proxyAddr, runDir string) string {
|
||||
return body + workerTrafficBlock(proxyAddr) + workerArtifactSpec(runDir)
|
||||
}
|
||||
|
||||
// renderIntentTask formats the claimed intent for the worker's SYSTEM prompt: the
|
||||
// intent is the worker's whole job, so — like the planner's situational block — it
|
||||
// belongs in system, where compaction can never drop it during a long run.
|
||||
// renderIntentTask formats the claimed intent for the worker's launch USER message:
|
||||
// the intent is the worker's whole job. It used to live in the system prompt; it now
|
||||
// rides in the first user turn (together with the situational overview) so the system
|
||||
// prompt stays static/role-only — same move as the planner's situational block.
|
||||
// intentAssetIDs pulls the intent's target asset ids out of its payload
|
||||
// (planner's add_intent stores them as a numeric asset_ids array). nil on absence
|
||||
// or malformed payload.
|
||||
@@ -212,7 +212,7 @@ func renderIntentTask(intent *db.Node) string {
|
||||
}
|
||||
|
||||
// renderWorkerGraphOverview folds the global situational snapshot into the worker's
|
||||
// SYSTEM prompt for AWARENESS ONLY. The framing is deliberately strong: the overview
|
||||
// launch USER message for AWARENESS ONLY. The framing is deliberately strong: the overview
|
||||
// must NOT widen the worker's job — it still does only its assigned intent. Its sole
|
||||
// purpose is letting the worker read context (existing facts/assets/hints)
|
||||
// so it avoids redundant work and doesn't re-derive what others already found.
|
||||
@@ -226,8 +226,7 @@ func renderWorkerGraphOverview(data map[string]any) string {
|
||||
return "" // fall back silently: the worker just won't have the global context
|
||||
}
|
||||
return "\n\n【全局探索态势(仅供你了解大局,不是你的任务清单)】:\n" +
|
||||
"下面是整个任务当前的探索概况。给你的**唯一目的**是让你了解全局动态、复用已有结论、避免重复别人已做过的事。\n" +
|
||||
"**开场别做前置普查**:这份概况已够你起步,直接开打领到的意图;list_facts / list_findings / list_worker_traces 只在执行中真缺某条细节时才按需查。\n" +
|
||||
"下面是整个任务当前的探索概况。给你的**唯一目的**是让你了解全局动态。\n" +
|
||||
"**它绝不扩大你的职责边界**:你仍然只做上面领到的那一条意图。看到这里有别的 open 意图 / 未覆盖的点 / 其它可打方向,也**绝不要自己去动手**——那些是别的 worker 的事,由规划者调度。你若发现相关新线索,最多写进 fact 让规划者知道,不要自己追。\n" +
|
||||
string(b)
|
||||
}
|
||||
@@ -258,13 +257,13 @@ func (w *Worker) Execute(ctx context.Context, name string, taskID int64, as *db.
|
||||
tools, def, cleanup := AugmentTools(ctx, "worker", base)
|
||||
defer cleanup()
|
||||
|
||||
// 意图是 worker 的全部任务,放进 system prompt(抗 compaction、整轮常驻),而不是
|
||||
// user 输入——与 planner 把关键态势放 system 一致。落在指令之后、deferred 块之前。
|
||||
// 全局态势(graphOverviewData)也带上,但仅供了解大局;意图放在最后、最醒目的位置。
|
||||
// 本次意图的专属工作目录 <workDir>/<taskID>/i<intentID>,引擎侧先建好。
|
||||
// 意图 + 全局态势改放【启动 user 消息】(见下方 input),system 只留静态角色正文
|
||||
// (段[A]/[B]/[C] + deferred 块)。与 planner 一致:把易变的运行期数据移出 system,
|
||||
// system 每 session 稳定、更利于缓存;代价是长 run 里这条 user 消息可能被 compaction
|
||||
// 压缩。本次意图的专属工作目录 <workDir>/<taskID>/i<intentID>,引擎侧先建好。
|
||||
runDir := ensureRunDir(w.workDir, taskID, intent.ID)
|
||||
overview := renderWorkerGraphOverview(tsx.graphOverviewData())
|
||||
system, boundary := deferredSystem(workerSystem(w.proxyAddr, runDir)+overview+renderIntentTask(intent), def)
|
||||
system, boundary := deferredSystem(workerSystem(w.proxyAddr, runDir), def)
|
||||
// 任务级 deadline(经 ctx 注入)夹逼本 run 的墙钟预算 + 决定收尾词(见 taskclock.go)。
|
||||
tc := taskClockFrom(ctx)
|
||||
maxDur, clamped := clampMaxDuration(tc.DeadlineUnix, w.runTimeout)
|
||||
@@ -326,9 +325,11 @@ func (w *Worker) Execute(ctx context.Context, name string, taskID int64, as *db.
|
||||
emit(r)
|
||||
}
|
||||
}
|
||||
// 意图已在 system 里。user 输入只放启动指令 + 意图锚定资产的原始数据(供 worker 直接
|
||||
// 用,省去开场再查一次 list_assets)。原始 JSON 直接附上,不做提取/格式化。
|
||||
input := "开始执行 system 提示里的这条意图:只做它、只产生事实、做完即停。"
|
||||
// 意图 + 全局态势 + 启动指令 + 意图锚定资产的原始数据都放这条启动 user 消息里。
|
||||
// 意图放最前、最醒目;overview 仅供了解大局。资产原始 JSON 直接附上,不做提取/格式化,
|
||||
// 省去开场再查一次 list_assets。注意:这些运行期数据现在活在 user 消息里,长 run 中
|
||||
// 有被 compaction 压缩的风险(意图是 worker 全部职责,若被压掉需另行 pin,待评估)。
|
||||
input := renderIntentTask(intent) + overview + "\n\n开始执行上面这条意图:只做它、只产生事实、做完即停。"
|
||||
if as != nil {
|
||||
if ids := intentAssetIDs(intent); len(ids) > 0 {
|
||||
if assets, err := as.GetByIDs(ids); err == nil && len(assets) > 0 {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
你是自动化 CTF 跑分系统里的「题目经验复用」agent。**触发条件:任务创建时触发**——每当有新题目任务被创建,你被唤醒一次。你的**唯一职责**:判断这道题以前是否做过,若做过,就把上次积累的可复用经验通过 hint 注入到这个新任务,帮它少走弯路、避免重复劳动。
|
||||
你是自动化 CTF 跑分系统里的「题目经验复用」agent。
|
||||
|
||||
**触发条件**:任务创建时触发——每当有新题目任务被创建,你被唤醒一次。你的**唯一职责**:判断这道题以前是否做过,若做过,就把上次积累的可复用经验注入到这个新任务,帮它少走弯路、避免重复劳动。若没做过,直接退出。
|
||||
|
||||
## 核心概念
|
||||
- 题目的唯一标识是 `unique_code`(题目 id);**当前新任务的信息里包含本题的 unique_code**。
|
||||
@@ -9,15 +11,48 @@
|
||||
1. 从当前任务信息里取出本题的 `unique_code` 和当前任务 id。
|
||||
2. 用 `list_tasks` 列出全部任务,筛出 **unique_code 与本题完全相同** 的历史任务(排除当前这个)。
|
||||
3. **若没有其它同 unique_code 的历史任务(即只有当前这一个)→ 说明是首次做,立即结束本次对话,绝不调用任何多余工具!!**
|
||||
4. 若存在历史任务:用 `get_task_graph` / `list_task_findings` / `list_task_worker_traces` / `get_task_worker_trace` / `search_task_worker_traces` 查看它们的**探索结论、确认的发现、以及 worker 的执行过程**,提炼**可复用的事实/经验**,例如:
|
||||
- 已确认的技术栈/版本/指纹、有效的注入点/参数/端点、可用凭据或绕过手法、解题的关键路径;
|
||||
- 已排除的死路(某参数不可注入、某端口关闭、某方向无果等),让新任务不再浪费时间重试。
|
||||
- 思考是从之前的运行结果中是否有一些其他路可以走,只要能达到目标即可,可以根据已经获取到的事实、结论拓展各种手法。
|
||||
5. 用 `add_task_hint` 把这些经验注入**当前这个新任务**,供规划者据此更快推进。
|
||||
4. 若存在历史任务:用 `get_task_graph` / `list_task_findings` / `list_task_worker_traces` / `get_task_worker_trace` / `search_task_worker_traces` 查看它们的**探索结论、确认的发现、以及 worker 的执行过程**,提炼**可复用的事实/经验**。
|
||||
|
||||
## 硬性规则
|
||||
- **首做即退**:同 unique_code 只有当前一个任务时,立刻退出,**绝不调用多余工具**(省 token、省时间)。
|
||||
- **unique_code 必须完全一致**才算同题;不要把别的题的经验混进来。
|
||||
- **忽略目标地址**:每次重试的 base url / container_addr 会变——注入 hint 时**只写与地址无关的经验**(打法、结论、死路),**不要把上次的具体 URL/IP 当成本次目标**。
|
||||
- hint 要**具体、可操作**:直接写「上次在 X 类接口用 Y 手法拿到 flag」「上次确认 Z 方向不可行,跳过」,而不是空泛的「仔细测试」。
|
||||
- **只做经验复用这一件事**:不要自己去解题、不要创建或暂停任务、不要提交 flag。
|
||||
## 经验抽取原则
|
||||
|
||||
### 1. 区分「事实」与「推测」
|
||||
|
||||
| 类型 | 定义 | 示例 | 标注 |
|
||||
|---|---|---|---|
|
||||
| `confirmed` | 有明确的响应/数据/证据支撑 | "gopher 协议返回「不支持的协议」" | confidence: high |
|
||||
| `inferred` | 基于间接证据或逻辑推断 | ".4:80 曾返回 200,可能是 flag 容器" | confidence: medium |
|
||||
| `negative` | 某方向尝试后无结果 | "路径枚举未发现新端点(仅枚举路径,未测参数名)" | confidence: low-medium,必须标注局限性 |
|
||||
|
||||
### 2. 限制「已穷尽」的使用
|
||||
|
||||
**「已穷尽」是最高等级的结论,必须满足以下全部条件才能使用:**
|
||||
- 至少使用了 **2 种不同方法** 验证(如:路径枚举 + 参数名穷举 + 方法变体)
|
||||
- 每种方法都达到了合理的覆盖深度
|
||||
- 验证过程的证据可追溯
|
||||
|
||||
**若未达到上述标准,必须写「局限性」而非「已穷尽」:**
|
||||
|
||||
| ❌ 错误写法 | ✅ 正确写法 |
|
||||
|---|---|
|
||||
| "internal-api 已穷尽,无更多端点" | "internal-api 路径枚举未发现新端点(仅枚举路径,未测参数名变体)" |
|
||||
| ".4 是死路,不可达" | ".4 当前返回 No route to host,建议作为低优先级方向" |
|
||||
| "MySQL 无法利用" | "MySQL 3306 从 SSRF 侧不可达(仅容器内 localhost 监听),无需重试" |
|
||||
|
||||
### 3. 经验要「可操作」
|
||||
|
||||
每个 hint 必须包含:
|
||||
- **是什么**:结论/发现
|
||||
- **怎么验证的**:关键证据
|
||||
- **有什么局限**:方法盲区
|
||||
- **建议怎么做**:给后续 Agent 的具体操作指引
|
||||
|
||||
## Hint 注入格式
|
||||
|
||||
每个 hint 必须按以下**结构化格式**写入:
|
||||
|
||||
类型: confirmed | inferred | negative
|
||||
置信度: 0.0-1.0
|
||||
摘要: <一句话总结>
|
||||
详情: <详细说明,含证据、方法、局限性>
|
||||
建议: <给后续 Agent 的具体建议>
|
||||
局限性: <这个结论的局限性(如果有)>
|
||||
@@ -1,7 +1,7 @@
|
||||
你是一个授权渗透测试系统的"执行者"(work agent)。你领到【一条意图】(一句话探索方向),唯一职责是:**完成这一条意图、把发现的事实写回知识图谱,然后停止返回。**
|
||||
当前是在CTF场景下。
|
||||
铁律(务必遵守):
|
||||
1. **只做这一条意图**。意图边界就是你的红线。指纹意图就只做指纹识别(curl -I / whatweb / 看响应头与首页),不要顺手去枚举端点、爆破目录、扒 JS 找 API、测漏洞——那些是【别的意图】的事,由规划者去派别的 worker。
|
||||
1. **只做这一条意图**。意图边界就是你的红线。指纹意图就只做指纹识别(curl -I/ 看响应头与首页),不要顺手去枚举端点、爆破目录、扒 JS 找 API、测漏洞——那些是【别的意图】的事,由规划者去派别的 worker。
|
||||
2. **你不负责"探索方向"**。发现了值得继续追的新线索,不要自己接着打;只要把它写回图,规划者会读到、自己生成新意图。生成探索方向是规划者的职责,不是你的。
|
||||
3. **完成意图就立即返回**。意图达成了就停,别因为"任务总目标还没达成"就自己继续往下探——那是规划者下一轮的事。
|
||||
4. **边发现边写回,并且写对地方**。每得出一个结果就立刻写回(别攒到最后,否则步数耗尽全丢)。结果只算写进图里的,活在你脑子/文字里的不算。**两张图分清楚**:
|
||||
@@ -14,16 +14,17 @@
|
||||
- record_fact:把探索【事实/结论】写入探索图并连到意图(传 intent_id)。正向/否定结论、观察、判断用它;**一次探索的多个观察汇总成一条事实**(summary 总结一句话 + detail 放细节),不要一个属性一条。真有多条不同结论才用 facts 数组。**只写你真实看到的**:给 evidence(一行关键证据:命令+关键输出,简洁,别粘大段——细节在 detail)、标 confidence(observed 直接看到 / inferred 推断);**否定结论**(不可注入/端口关闭等)尤其要给证据、证据弱就标 inferred,别让错的否定误导规划者放弃方向。
|
||||
- report_finding:确认漏洞 → 记录(含 PoC,传 intent_id=你领到的意图id)。
|
||||
- list_assets(查询资产,非探索节点) / asset_neighbors / list_facts(探索事实) / list_findings(漏洞) / node_detail(探索节点 id,非资产 id):按需查上下文。
|
||||
- search_all_worker_traces / list_worker_traces / get_worker_trace:**跨 work 复用信息**(别的 work 执行过程里出现过、却没写进 fact 的东西)。你看不到探索图,但可以:search_all_worker_traces(q) 按关键字搜全部 work 的过程(返回带 intent_id);list_worker_traces 看有哪些 work 跑过;get_worker_trace(intent_id) 看某 work 的步骤摘要、get_worker_trace(intent_id, step_ids=[…]) 取那几步完整内容(一次≤5个)。**仅用于复用他人观察、避免重复劳动——不改变你的任务边界(仍只做你这条意图)。**
|
||||
- sleep:循环等待后台命令产出时用(之后再用 TaskOutput 查看部分输出)。
|
||||
- 交互式PTY shell(高优先级):遇到 ssh/telnet、反弹shell 等任何需要"输入-响应"交互的服务,**高优先级**选择 shell_open 开交互式会话,使用对应的命令进行测试比如ssh/telnet。
|
||||
|
||||
|
||||
只在授权范围内操作。完成本意图后用一句话总结你做了什么、写回了哪些事实。务实、克制、聚焦这一条意图。
|
||||
|
||||
【关于目录扫描】:除了使用固定字典fuzz之外,还可以通过各种组合进行生成测试。
|
||||
|
||||
|
||||
请勿探索该任务目标以外的资产,例如同C段ip等,因为是CTF靶场,所以不同题目只是C段不同,你只需要专注本次任务,除了一种情况是多阶段渗透测试,会出现多个地址,但是也是通过本任务找到的关联资产,不属于其他资产。
|
||||
|
||||
对于已知产品,优先使用nuclei运行一遍。
|
||||
对于已知产品,离线的POC知识库也可以使用。
|
||||
【深度挖掘】当出现一些方向时,你需要注意进行深度挖掘,所有异常信息都可能是线索。只有将某个方向的所有手法完全穷尽才能任务该方向已经探索完毕。
|
||||
【重要】对于已知产品,优先使用nuclei运行一遍。;对于已知产品,离线的POC知识库也可以使用,检索到相关POC可以先进行打一遍。
|
||||
flag如果是在文件中的话,通常在/challenge/flag.txt 或者/challenge/目录下其他文件位置
|
||||
+19
-14
@@ -40,7 +40,10 @@ TOTAL_BUDGET = 1080.0 # 槽·分钟总预算 = 3 槽 × 360min(硬天花
|
||||
SLOTS = 3 # 平台硬上限:同时最多 3 个活跃容器
|
||||
DEADLINE_MIN = 360.0 # 6h 硬停
|
||||
SOFT_DEADLINE = 350.0 # 软截止:此后不新起长窗,留 10min 优雅收尾
|
||||
POLL_SEC = 20 # 轮询周期(真实模式)
|
||||
POLL_SEC = 20 # 基础轮询周期(真实模式,空闲时用)
|
||||
FAST_POLL_SEC = float(os.environ.get("FAST_POLL_SEC", "3")) # 有活跃槽解题时的快轮询周期:
|
||||
# 提交 flag/通关后平台是同步生效的,快轮询让"关旧题→补新题"几乎立刻发生,
|
||||
# 把换题延迟从最多一个 POLL_SEC 压到 FAST_POLL_SEC。若平台限流可调大。
|
||||
|
||||
# —— 易题阶段 ——
|
||||
EASY_WALL_CAP = 95.0 # 墙钟到此强制收口、剩余易题 park、全槽转硬
|
||||
@@ -408,7 +411,8 @@ class Scheduler:
|
||||
|
||||
if self._all_done():
|
||||
break
|
||||
self.clock.sleep(POLL_SEC)
|
||||
# 有槽在解题 → 快轮询,让提交/通关后立刻换题;全空闲(等靶场/无题)才回落基础周期省 QPS。
|
||||
self.clock.sleep(FAST_POLL_SEC if any(s.busy for s in self.slots) else POLL_SEC)
|
||||
|
||||
self._shutdown()
|
||||
|
||||
@@ -599,16 +603,19 @@ class Scheduler:
|
||||
else:
|
||||
st, ecode, addrs = self.b.start(code)
|
||||
if st == 409 and ecode == "invalid_state":
|
||||
return # 达 3 活跃上限(或任务将结束):下一轮重试,challenges() 会捕获真结束
|
||||
self._requeue(code) # 达 3 活跃上限(或任务将结束):退回队列下一轮重试,challenges() 会捕获真结束
|
||||
return # ★ code 已被 _pick_next pop 出,不 requeue 会永久蒸发
|
||||
if st == 503 or ecode == "resource_unavailable":
|
||||
return # 靶场资源未就绪:稍后重试
|
||||
self._requeue(code) # 靶场资源未就绪:退回队列稍后重试
|
||||
return
|
||||
if st == 404 and ecode == "challenge_not_found":
|
||||
self.log.emit("放弃", code, "challenge_not_found,跳过")
|
||||
self.given_up.add(code)
|
||||
return
|
||||
if st == 404 and ecode == "task_not_found":
|
||||
raise BenchEnded("task_not_found")
|
||||
if not addrs: # pending/无地址:下一轮重试
|
||||
if not addrs: # pending/无地址:退回队列下一轮重试
|
||||
self._requeue(code)
|
||||
return
|
||||
self.log.emit("启动靶场", code, f"container_addr: {', '.join(addrs)}")
|
||||
# 建任务前删资产:清掉复用 IP 上的旧题残留资产(host=地址去掉端口),避免跨题污染。
|
||||
@@ -714,7 +721,10 @@ class Scheduler:
|
||||
self.easyQ.insert(0, code)
|
||||
|
||||
def _vpn_guard(self, now):
|
||||
"""VPN 断开检测。返回 True=当前冻结、本轮跳过解题。恢复时把停摆时长从各槽预算补偿掉。"""
|
||||
"""VPN 断开检测。返回 True=当前冻结、本轮跳过平台处置(拉题/起停/对账都需 VPN)。
|
||||
任务【不暂停】——大概率只是网络抖动,pause 会 cancel 掉在飞的 LLM/worker 执行、白烧
|
||||
已花 token 且丢当前轮进度,得不偿失;抖动几秒对解题无害,等自然重连即可。
|
||||
恢复时把停摆时长从各槽预算/停滞看门狗计时里补偿掉,避免停摆期误判并触发干净重启。"""
|
||||
ok = self.sim.vpn_ok() if self.simulate else self.b.vpn_ok()
|
||||
if ok:
|
||||
if self.frozen:
|
||||
@@ -725,19 +735,14 @@ class Scheduler:
|
||||
s.started += outage # 停摆不计入该题已跑时长
|
||||
if s.last_progress is not None:
|
||||
s.last_progress += outage # 也不让停摆触发停滞看门狗
|
||||
if not self.simulate and s.task_id:
|
||||
self.a.control(s.task_id, "resume")
|
||||
self.log.emit("VPN恢复", info=f"停摆 {outage:.0f}min:各槽预算已补偿、任务恢复(墙钟仍走)")
|
||||
self.log.emit("VPN恢复", info=f"停摆 {outage:.0f}min:预算已补偿、任务全程未停(墙钟仍走)")
|
||||
self.frozen = False
|
||||
return False
|
||||
# VPN 断
|
||||
# VPN 断:任务不动(照跑),只跳过本轮需 VPN 的平台处置,等自然重连。
|
||||
if not self.frozen:
|
||||
self.frozen = True
|
||||
self.freeze_start = now
|
||||
for s in self.slots:
|
||||
if s.busy and not self.simulate and s.task_id:
|
||||
self.a.control(s.task_id, "pause")
|
||||
self.log.emit("VPN断开", info="冻结各槽预算 + 暂停任务,等待恢复")
|
||||
self.log.emit("VPN断开", info="跳过本轮平台处置、冻结各槽预算;任务不暂停,等待重连")
|
||||
return True
|
||||
|
||||
def _clean_restart(self, s, now):
|
||||
|
||||
@@ -3,7 +3,7 @@ module github.com/Autumn-27/artex
|
||||
go 1.26.3
|
||||
|
||||
require (
|
||||
github.com/Autumn-27/norma v0.2.8
|
||||
github.com/Autumn-27/norma v0.2.9
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/jackc/pgx/v5 v5.10.0
|
||||
github.com/lqqyt2423/go-mitmproxy v1.9.2
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
github.com/Autumn-27/norma v0.2.8 h1:hfNwGUC4Yt2TEwxIlPvhi0g3Xy4ILajNI2HGPbWNwGA=
|
||||
github.com/Autumn-27/norma v0.2.8/go.mod h1:BudWrf7iHl96FgHqFr/HwN3mKYmGjJDt+xL2ZHXfFZw=
|
||||
github.com/Autumn-27/norma v0.2.9 h1:VTt8Oe7XoiV85Dc+QOPu+zQ7IykQRm2aDTEbLEzbBe4=
|
||||
github.com/Autumn-27/norma v0.2.9/go.mod h1:BudWrf7iHl96FgHqFr/HwN3mKYmGjJDt+xL2ZHXfFZw=
|
||||
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
|
||||
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
|
||||
@@ -111,7 +111,7 @@ export default function TasksPage() {
|
||||
const [profiles, setProfiles] = React.useState<LLMProfile[]>([]);
|
||||
const [llmProfile, setLlmProfile] = React.useState<string>(ACTIVE_PROFILE); // sentinel = active
|
||||
const [timeoutMin, setTimeoutMin] = React.useState(""); // 任务级超时(分钟);空/0 = 不限时
|
||||
const [heartbeatMin, setHeartbeatMin] = React.useState("5"); // planner 心跳(分钟);默认5,下限5(与后端一致)
|
||||
const [heartbeatMin, setHeartbeatMin] = React.useState("10"); // planner 心跳(分钟);默认10,下限10(与后端一致)
|
||||
const [seedFirstIntent, setSeedFirstIntent] = React.useState(true); // 创建时下发种子意图,worker 免等首轮 planner 直接开跑
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [query, setQuery] = React.useState("");
|
||||
@@ -174,14 +174,14 @@ export default function TasksPage() {
|
||||
try {
|
||||
const pid = llmProfile === ACTIVE_PROFILE ? undefined : Number(llmProfile);
|
||||
const timeoutSec = Math.max(0, Math.floor(Number(timeoutMin) || 0)) * 60;
|
||||
const heartbeatSec = Math.max(5, Math.floor(Number(heartbeatMin) || 5)) * 60; // 下限 5min,与后端归一一致
|
||||
const heartbeatSec = Math.max(10, Math.floor(Number(heartbeatMin) || 10)) * 60; // 下限 10min,与后端归一一致
|
||||
await api.createTask(description.trim(), goal.trim(), pid, timeoutSec, seedFirstIntent, heartbeatSec);
|
||||
toast.success("任务已创建");
|
||||
setDescription("");
|
||||
setGoal("");
|
||||
setLlmProfile(ACTIVE_PROFILE);
|
||||
setTimeoutMin("");
|
||||
setHeartbeatMin("5");
|
||||
setHeartbeatMin("10");
|
||||
setSeedFirstIntent(true);
|
||||
setOpen(false);
|
||||
load();
|
||||
@@ -311,14 +311,14 @@ export default function TasksPage() {
|
||||
<Input
|
||||
id="heartbeat-min"
|
||||
type="number"
|
||||
min={5}
|
||||
min={10}
|
||||
className="w-40"
|
||||
placeholder="默认 5"
|
||||
placeholder="默认 10"
|
||||
value={heartbeatMin}
|
||||
onChange={(e) => setHeartbeatMin(e.target.value)}
|
||||
/>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
距上轮规划结束/任务开始满该时长且期间无触发,自动触发一轮规划(兜底卡死 + 唤醒去监督在跑的 worker)。下限 5 分钟。
|
||||
距上轮规划结束/任务开始满该时长且期间无触发,自动触发一轮规划(兜底卡死 + 唤醒去监督在跑的 worker)。下限 10 分钟。
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
|
||||
+1
-1
@@ -116,7 +116,7 @@ export const api = {
|
||||
llm_profile_id: llmProfileId ?? null,
|
||||
timeout_seconds: timeoutSeconds ?? 0,
|
||||
seed_first_intent: seedFirstIntent ?? true,
|
||||
plan_heartbeat_seconds: planHeartbeatSeconds ?? 0, // 0 = 后端归一到默认 300(5min)
|
||||
plan_heartbeat_seconds: planHeartbeatSeconds ?? 0, // 0 = 后端归一到默认 600(10min)
|
||||
}),
|
||||
deleteTask: (id: string) => del<{ deleted: number }>(`/tasks/${id}`),
|
||||
controlTask: (id: string, action: "pause" | "resume") => post<{ id: string; paused: boolean }>(`/tasks/${id}/control`, { action }),
|
||||
|
||||
Reference in new issue
Block a user