ATK 系统架构与开发文档
第二届腾讯云黑客松 · 智能渗透挑战赛
主语言: Go 1.25 · 依赖: AstrBot + Kali 执行环境
Intro
先总结一下,我这边比赛用minimax+gpt-5.4,个人预算严重不足就没用claude了,这一套搭配开局简单题目和赛前测试题目基本一分钟不到就解出了,后面就卡住了,再后面由于minimax tokenplan的额度限制以及一些原因就没接着打了。整场比赛看下来其实最大的决定因素就是模型能力,也就是说如果用cluade opus搭配网上的各种开源项目,其实能取得的成绩也是不错的。
在架构设计上,通过subagent来实现各类角色已经是既定的主流方案,在长任务场景下,几乎所有的架构都需要一个监督者的角色,这个角色作为独立的第三方来监督其他各agent是否存在思考过度、思维漂移等现象,并且我还引入了summary_agent这个角色,进行复盘,提取当前轮次有效信息做上下文持久化,再将持久化信息传入下一轮中。agent的通信我也采用了一种自定义的Card格式进行传输,这一点在第一届的思路中也有提到。
此外,gpt-5.4也存在一些问题(在我写这篇文章时5.5已经发布了,效果提升很不错),比如上下文腐烂速度相较于其他模型来说非常快,在上下文占用超百分之五十时,模型的性能就会大幅下降,并且5.4还存在一个很致命的点就是过度思考有些严重,并且在我的架构中,负责信息收集的agent把信息传递给5.4时,由于recon侦测到的信息很复杂,它会在众多路径中陷入一个不可行的路径越陷愈深,因此后面我设计了一套评分标准,根据侦测到信息的置信度对每个攻击路径进行打分,优先对分数最高的路径进行尝试,并且在尝试次数达到规定次数后就放弃该路径,但其实效果不是很理想。
一开始设计思路的时候,跟朋友一起聊天,想到了一个好玩的点,关于ai的上下文问题,直接用QQ群聊来作为各个subagent的上下文,我新建了很多QQ群作为不同的分工,这样就可以根据角色来隔绝上下文,并且能够将上下文在qq群中持久化,后面这个也设计成功了,但其实我思考了一会,发现根本就没必要,持久化存储使用本地存储根据目录分割就可以了,前面的尝试可能更多是一个玩着试试的想法。
然后大概说一下我的设计思路吧,我在设计项目之前参考了第一届的几个项目,比较核心的观点就是less is more,这个说法成立的前提是模型的能力够强,准确来说是当模型能力够强时,过多的干扰只会扰乱模型的解决问题的思路。用我的项目来说明,因为我用的是minimax,所以我设计了大量的core agent和swarm agent分工,并针对不同的漏洞类型进行专攻,但如果将这一结构套用在opus上,一定会导致opus的效能大大降低。因此对于后续的迭代优化,我的建议就是不要把agent的角色划分的太细,尤其是对于能力强的模型越少的过度干扰才能发挥真正的作用,要做的只是规范与监督,也就是所谓的harness engine。
后面的就是整个项目的设计思路,对于比赛中这种flag的提交,我是通过监听sse的输出,识别到flag的格式就提取出来进行提交,关于为什么没有代码开源这件事,由于某些不可抗因素,导致代码全部丢失,最后开赛前一天我剩下的只有赛方服务器上的golang编译产物,所以只剩下思路和这篇开发文档了。
目录
- 系统定位与总体架构
- 组件分层
- 核心数据模型
- Agent 体系
- 完整数据流
- Channel 路由系统
- HTTP API 参考
- 配置参考
- 知识系统
- 持久化与存储布局
- Go 包结构
- 开发规范
- 测试策略
- 部署说明
1. 系统定位与总体架构
ATK 是一个多 Agent 并发智能渗透测试系统。系统由三个独立进程组成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| ┌──────────────────────────────────────────────────────────────────┐ │ AstrBot (端口 6185) │ │ LLM 编排平台,管理 14 个 persona,负责思考/推理/委派 │ │ 接受 /run /next /summary /critic 等前缀命令 │ └──────────────────┬───────────────────────────────────────────────┘ │ HTTP POST /api/v1/chat (kickoff) │ HTTP streaming SSE (reply) ┌──────────────────▼───────────────────────────────────────────────┐ │ atk-sidecar (端口 8090) │ │ Go HTTP 服务,负责: │ │ - 与 AstrBot 的所有通信(kickoff/recovery/swarm dispatch) │ │ - Card/Event/Channel 持久化 │ │ - 自动化调度(recovery loop、swarm dispatch、challenge 管理) │ │ - Flag 提交与比赛 API 对接 │ │ - Shell/Python 执行(通过 runner) │ │ - 知识库检索(指纹阶段可主动查询;恢复轮次注入 KNOWLEDGE_HINT) │ └──────────────────┬───────────────────────────────────────────────┘ │ run_shell / run_python (via MCP → HTTP) ┌──────────────────▼───────────────────────────────────────────────┐ │ Kali Linux 执行环境 │ │ 本地模式 (mode=local) 直接执行 shell/python 命令 │ │ Docker 模式 (mode=docker) 在容器内执行 │ └──────────────────────────────────────────────────────────────────┘
|
核心设计原则:
- sidecar 是哑管道 + 自动化控制器,不包含渗透测试逻辑
- 所有渗透决策在 AstrBot Agent 侧(通过 persona system prompt)
- AstrBot Agent 通过 MCP server 调用 sidecar 提供的工具(
run_shell、run_python、read_artifact、upsert_card)
- sidecar 观察 card 流,自动触发 recovery/swarm/flag 提交等自动化行为
2. 组件分层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| atk-sidecar 内部分层:
┌─────────────────────────────────┐ │ httpapi (HTTP 层) │ ← 21 个端点,无业务逻辑 ├─────────────────────────────────┤ │ service (核心业务逻辑层) │ ← 所有自动化调度在此 │ ┌─────────────────────────────┐│ │ │ card_pipeline swarm_disp ││ ← card 处理管道 / 蜂群分发 │ │ recovery branch_ctl ││ ← 自动恢复 / 分支控制 │ │ pioneer_dispatch session_reg││ ← Pioneer 并发 / session 生命周期 │ │ challenge_manager ││ ← 多题并发管理 │ │ astrbot_postprocess ││ ← AstrBot 回复解析,提取 card │ └─────────────────────────────┘│ ├─────────────────────────────────┤ │ 基础设施包 │ │ cards channelbus events │ │ kickoff officialapi runner │ │ knowledge submissions router │ └─────────────────────────────────┘
|
2.1 service 包内文件职责
| 文件 |
行数 |
职责 |
service.go |
1258 |
Service struct 定义、公共方法(UpsertCard、Publish、QueryCards 等)、初始化 |
astrbot_postprocess.go |
1141 |
AstrBot 回复解析:提取 atk_card JSON block、heuristic card 检测、PlanIR 解析 |
branch_controller.go |
1062 |
多路径分支管理:PathCandidateCard 追踪、dominant_branch 锁定、stall 检测 |
recovery.go |
1189 |
Recovery 控制器、recovery kickoff 构建、KNOWLEDGE_HINT 注入、官方 hint 收集 |
card_pipeline.go |
262 |
card upsert 管道:ObserveRecovery/Branch、FlagCandidate 自动提交、SecurityMatcher 触发 |
session_registry.go |
265 |
Session 生命周期(running/stop_requested/stopped/superseded/solved) |
session_control.go |
214 |
session 状态变更:markSolved、stopAllForStage(flag 找到后取消同 stage 其他 session) |
swarm_dispatcher.go |
191 |
HypothesisCard → 对应 vuln 专项 Agent dispatch,含 per-stage 并发上限 |
challenge_manager.go |
197 |
多题并发:轮询比赛 API,自动 start challenge,dispatch commander+pioneer |
pioneer_dispatch.go |
328 |
Pioneer 独立 dispatch 逻辑,使用独立 kickoff store 和 recovery controller |
signal_extract.go |
214 |
runner 输出中提取 flag(正则匹配 flag{...}),生成 FlagCandidateCard |
knowledge_zone.go |
66 |
QueryKnowledge 接口,封装 knowledge.Retriever.QueryScoped |
helpers.go |
117 |
类型转换辅助(stringFromAny、stringSliceFromAny 等) |
3. 核心数据模型
3.1 Card(结构化知识单元)
所有 agent 输出的结构化信息以 Card 形式存储。Card 通过 atk_card fenced JSON block 从 AstrBot 回复中提取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| type Card struct { CardID string CardType string Stage string GroupKey string Role string Title string Summary string Status string Priority string Confidence float64 Tags []string Payload map[string]any CreatedAt time.Time UpdatedAt time.Time }
|
Card 类型完整表:
| CardType |
生产者 |
关键 Payload 字段 |
触发的自动化行为 |
AssetCard |
recon_agent |
url, tech_stack, interesting_params, tool_needs |
→ SecurityMatcher(自动假设匹配) → HypothesisCard |
HypothesisCard |
planner_agent, SecurityMatcher |
vuln_type(必填), hypothesis, preconditions, validation_steps, dominant_branch |
→ swarm_dispatcher dispatch 对应专项 Agent |
ActionCard |
executor_agent |
action_type, target, selected_tool, result, failure_level, path_id, move_id |
→ recovery ObserveCard |
RiskCard |
critic_agent, branchController |
risk_type, failure_level (L0-L5), need_branch_replan, decision |
→ recovery trigger(halt 状态优先级最高) |
FlagCandidateCard |
executor/pioneer/swarm |
candidate(精确 flag 字符串), method |
→ markSessionSolved → stopAllForStage → 自动提交比赛平台 |
GroupDigest |
summary_agent, reflector, system |
facts, dead_paths, retry_ready_context, dominant_branch, required_next_moves |
→ recovery trigger |
EvidenceCard |
reflector |
evidence_items, timeline, source_cards |
存档,发往 evidence_review channel |
PathCandidateCard |
planner_agent, branchController |
path_id, dominant_branch, required_next_moves, move_ids |
branchController 追踪路径状态 |
PlanIRCard |
planner_agent(经 astrbot_postprocess 解析) |
goal, dominant_branch, required_next_moves, stop_conditions |
branchController 锁定分支 |
BranchStateCard |
branchController(system 自动生成) |
dominant_branch, lock_state, stall_state, remaining_moves |
发往 main_control |
ToolCapabilityCard |
system(runner 探测后生成) |
available_tools, kali_tools |
广播给 main_control |
PathScoreCard |
branchController(内部) |
scores |
内部状态管理 |
3.2 Event
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| type Event struct { ID string Type string Stage string Source string GroupKey string Role string Tags []string Status string Impact string Message string CardType string CardID string Payload map[string]any CreatedAt time.Time }
|
Event 通过 events.Bus 发布,存储在 runtime_data/events/*.jsonl,agent 侧不直接消费 event(agent 读 channel messages)。
3.3 Kickoff Envelope
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| type Envelope struct { ID string Source string Trigger string TargetRole string TargetChannel string Sender string CommandPrefix string CommandText string Summary string Stage string SnapshotHash string Snapshot map[string]any Meta map[string]any SessionIDOverride string CreatedAt time.Time Status string }
|
Session ID 命名约定:
| 场景 |
Session ID 格式 |
| 单题 commander |
atk-commander-main-v3(yaml 配置) |
| 单题 pioneer |
atk-pioneer-v1(yaml 配置) |
| 多题 commander |
atk-cmd-{challenge_code_lowercase} |
| 多题 pioneer |
atk-pio-{challenge_code_lowercase} |
| 蜂群 agent |
atk-swarm-{vuln_type}-{challenge_code_lowercase} |
3.4 PublishedMessage(Channel 消息)
1 2 3 4 5 6 7 8 9 10 11
| type PublishedMessage struct { ID string ChannelKey string Stage string Text string Tags []string Card *sidecar.Card Event *sidecar.Event PublishedAt time.Time }
|
4. Agent 体系
4.1 Core Agents(8 个)
| 角色 ID(AstrBot persona_id) |
中文定位 |
主要输出 Card |
工具权限 |
commander_main |
主控、调度、fast_probe |
GroupDigest, HypothesisCard, RiskCard |
run_shell, run_python, read_artifact |
recon_agent |
低成本侦察、攻击面画像 |
AssetCard |
run_shell, run_python, read_artifact |
planner_agent |
攻击路径规划、假设建立 |
HypothesisCard, PathCandidateCard, PlanIRCard |
read_artifact |
executor_agent |
执行批准动作、回传结果 |
ActionCard, FlagCandidateCard |
run_shell, run_python, read_artifact |
critic_agent |
风险审查、失败分级(L0-L5) |
RiskCard |
read_artifact |
summary_agent |
上下文压缩、复盘摘要 |
GroupDigest |
read_artifact |
reflector |
证据归档(solved)/ 经验沉淀(unsolved) |
EvidenceCard, GroupDigest |
read_artifact |
pioneer |
直攻型并发解题(15 步预算) |
FlagCandidateCard, GroupDigest |
run_shell, run_python, read_artifact |
4.2 Swarm Agents(6 个)
| 角色 ID |
专项漏洞 |
预算 |
vuln_type 触发值 |
sqli_agent |
SQL 注入 |
10 步 |
sqli |
ssrf_agent |
SSRF |
10 步 |
ssrf |
auth_bypass_agent |
认证绕过/IDOR |
10 步 |
auth_bypass |
file_inclusion_agent |
文件包含/路径遍历 |
10 步 |
file_inclusion |
rce_agent |
RCE/命令注入 |
10 步 |
rce |
xss_agent |
XSS/客户端注入 |
10 步 |
xss |
蜂群 agent 与主链路并发运行,不通过 transfer_to_*,找到 flag 后 sidecar 自动取消同 stage 其他 session。
4.3 Persona 文件结构
Persona 以 persona/assembled/*.md 作为最终交付物,直接复制到 AstrBot Web。
1 2 3 4 5 6
| 最终交付目录: persona/assembled/commander_main.md persona/assembled/recon_persona.md persona/assembled/summary_persona.md persona/assembled/planner_persona.md ... 共 14 个完整 prompt 文件
|
使用方式:
1 2
| persona/assembled/<role>.md
|
4.4 失败分级体系(L0-L5)
critic_agent 使用,recovery 触发优先级依据:
| 级别 |
含义 |
recovery 行为 |
| L0 |
仅有原始现象,不足以下结论 |
不触发 recovery |
| L1 |
工具/命令/请求本身失败 |
触发 recovery,重试 |
| L2 |
前提条件不成立(认证、会话缺失) |
触发 recovery,补前提 |
| L3 |
外部环境干扰(WAF、限速) |
触发 recovery,绕过策略 |
| L4 |
假设本身被证伪 |
触发 recovery,换分支 |
| L5 |
整体路径设计错误 |
触发 recovery + 注入 reference layer 知识 |
5. 完整数据流
5.1 题目 Kickoff 流程(单题模式)
1 2 3 4 5 6 7 8 9 10 11 12 13
| 官方 API 变更检测(scheduler, 每 10s) ↓ official.FetchStageSnapshot() ↓ 检测到 hash 变化 dispatchKickoff(ctx, snapshot) ├── kickoff.NewEnvelope(cfg.Automation.Kickoff, trigger, snapshot) │ CommandText: "/run [SYSTEM][competition_api] stage=X target_url=Y ..." │ → AstrBot POST /api/v1/chat { session_id: "atk-commander-main-v3" } │ → SSE streaming reply → astrbot_postprocess → extractReplyCards │ └── go dispatchPioneerKickoff(snapshot) ← 并发,独立 goroutine CommandText: "/run [SYSTEM][pioneer] stage=X target_url=Y ..." → AstrBot POST /api/v1/chat { session_id: "atk-pioneer-v1" }
|
5.2 多题并发流程(challenge_manager)
1 2 3 4 5 6 7 8 9 10 11
| challengeManager.tick() ← 每 30s ↓ official.FetchChallengeList() ↓ 发现未解且未 active 的题目(按 difficulty 升序) official.StartChallenge(code) → entrypoints ↓ dispatchChallenge(ch, entrypoints) ├── cmdEnv.SessionIDOverride = "atk-cmd-" + code │ dispatchKickoffEnvelope(cmdEnv) └── pioEnv.SessionIDOverride = "atk-pio-" + code go dispatchPioneerEnvelope(pioEnv)
|
5.3 Swarm Agent 分发流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| planner_agent 输出 HypothesisCard { vuln_type: "sqli" } ↓ AstrBot reply → astrbot_postprocess → extractReplyCards ↓ upsertCard() → card_pipeline ↓ swarmDisp.MaybeDispatch(card) ├── 检查 card.CardType == "HypothesisCard" ├── 检查 vuln_type 在已知 6 种之内("unknown" 不触发) ├── 检查同 stage+vuln_type 未有 active session ├── 检查 stageCount[stage] < MaxConcurrentPerStage (默认 3) └── go dispatch(agentRole="sqli_agent", vulnType="sqli", stage, card) ↓ swarmCfg.TargetRole = "sqli_agent" swarmCfg.TargetChannel = "exec_feedback" snapshot.Normalized = { stage, vuln_type, target_url, hypothesis, validation_steps, ... } env.SessionIDOverride = "atk-swarm-sqli-" + sanitize(stage) loadDomainSkill("sqli") → env.CommandText += "[DOMAIN_SKILL]\n..." dispatchKickoffEnvelope(env)
另一触发路径(SecurityMatcher,专用于指纹到漏洞家族假设的自动匹配): AssetCard 到达 card_pipeline ↓ securityMatcher.MatchBySignals(signals) ↓ 命中高置信条目 自动生成 HypothesisCard { vuln_type: "rce", source: "security/*" } ↓ 同上 swarm dispatch 路径
|
5.4 Recovery 流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| agent 输出 GroupDigest(status=unsolved)或 RiskCard(status=halt) ↓ upsertCard() → recovery.ObserveCard(card) ├── recoveryTriggerFromCard(card) → recoveryTrigger{ Stage, CardType, CardID, Reason, FailureLevel } └── 更新 stageRecoveryState.queued(高优先级 trigger 覆盖低优先级)
dispatchKickoffEnvelope 完成回调 → maybeStartRecoveryRound ↓ recovery.CompleteKickoff(envelope) → returns queued trigger(若有) ↓ buildRecoveryEnvelope(trigger, previous) ├── CommandText 包含: │ - [SYSTEM][recovery_loop] stage=X round=N failure_level=L4 reason=... │ - 恢复策略指令(先 summary → critic → reflector → planner) │ - [RETRY_READY_CONTEXT] 块(最近 GroupDigest 内容) │ - [OFFICIAL_HINT] 块(来自比赛平台 /api/hint) │ └── [KNOWLEDGE_HINT] 块(知识库检索结果,按 trigger/runbook/reference 分层) └── 新 kickoff → AstrBot(同 session)
recovery 轮次限制: - commander: max_rounds = 5(yaml 配置) - pioneer: max_rounds = 2 - swarm: 无 recovery(10 步结束即结束) - competition.auto_hint = true 时:round >= 2 且 hint_viewed=false 才请求 hint(pioneer 不请求)
|
5.5 Flag 提交流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 任意 agent 输出 FlagCandidateCard { candidate: "flag{xxx}", stage: "BwAMWQ..." } ↓ upsertCard() → card_pipeline ├── markSessionSolved(sessionID, "flag candidate captured") │ └── stopAllForStage(stage, exceptSessionID) │ → 取消同 stage 其他 session 的 HTTP context(best-effort) │ → 标记为 stop_requested │ └── maybeAutoSubmitFlagCandidate(card) ├── extractFlagValue(candidate) ← 正则提取 flag{...} ├── 检查 confidence >= min_confidence (默认 0.85) ├── 检查未重复提交 ├── [Competition 模式] official.SubmitCompetitionFlag(stage, flag) │ → POST /api/submit { code: stage, flag: flag } └── [Legacy 模式] official.SubmitFlag(stage, flag, sourceCardID) → POST {flag_submission.endpoint_path}
|
6. Channel 路由系统
6.1 6 个 Channel
| Channel Key |
默认入口 |
读权限角色 |
用途 |
main_control |
commander_main |
summary, planner, critic, reflector, 6 swarm |
主控决策中心,沉淀摘要/计划/风险 |
recon_raw |
recon_agent |
summary, planner, reflector |
侦察原始数据,AssetCard/OBS |
exec_feedback |
executor_agent |
summary, planner, critic, reflector, 6 swarm |
执行反馈,ActionCard/RESULT/FlagCandidateCard |
evidence_review |
reflector |
critic, commander_main, reflector |
证据归档,EvidenceCard/REPORT |
direct_solve |
pioneer |
commander_main, summary |
Pioneer 独立解题通道 |
swarm_feedback |
sqli_agent |
commander_main, summary, planner |
蜂群专项输出汇总 |
6.2 Publish 规则(tag/card_type → channel)
| 匹配条件 |
目标 Channel |
tag: OBS 或 CardType: AssetCard |
recon_raw |
tag: SUMMARY 或 CardType: GroupDigest |
main_control |
tag: PLAN 或 CardType: HypothesisCard |
main_control |
tag: CRITIC 或 CardType: RiskCard |
main_control |
tag: ACTION 或 tag: RESULT |
exec_feedback |
CardType: FlagCandidateCard |
exec_feedback + swarm_feedback |
tag: REPORT 或 CardType: EvidenceCard |
evidence_review |
tag: SKILL |
evidence_review |
tag: SWARM |
swarm_feedback |
tag: OfficialAPI |
main_control |
6.3 Mirror 规则(额外复制到其他 channel)
| 匹配条件 |
额外发往 |
| AssetCard + confidence >= 0.85 |
main_control |
| RiskCard + status=halt |
evidence_review |
| tag: RESULT + impact=high |
main_control |
6.4 Ingress 模式
main_control: command_only,只接受以 /run /next /summary /critic 开头的命令
- 其余 channel:
none,不接受外部写入(只由 sidecar 内部 publish 写入)
6.5 Stage 过滤(多题并发隔离)
1 2 3
| GET /v1/channels/messages?role=summary_agent&channel_key=main_control&stage=BwAMWQ → channelbus.Store.List(filter{Stage: "BwAMWQ"}) → 只返回 PublishedMessage.Stage == "BwAMWQ" 的消息
|
Stage 在 filePublisher.Publish 时自动从嵌入的 Card.Stage 或 Event.Stage 提取,调用方无需显式传递。
7. HTTP API 参考
所有端点均无认证(内网部署,sidecar 信任所有请求)。
| 方法 |
路径 |
说明 |
关键参数 |
| GET |
/healthz |
健康检查 |
— |
| GET |
/dashboard |
Web Dashboard HTML |
— |
| GET |
/v1/dashboard/progress |
Dashboard 数据(JSON) |
— |
| GET |
/v1/channels |
列出所有 channel(按 role 过滤) |
?role= |
| GET |
/v1/channels/messages |
查询 channel 消息 |
?role= &channel_key= &stage= &after_id= &limit= |
| GET |
/v1/cards |
查询 card 列表 |
?card_type= &stage= &group_key= &tag= &limit= |
| GET |
/v1/cards/{type}/{id} |
获取单张 card |
— |
| POST |
/v1/cards/upsert |
插入/更新 card(agent 通过 MCP 调用) |
{card, publish, publish_text, preferred_channels} |
| POST |
/v1/events |
发布事件 |
{event} |
| POST |
/v1/publish |
发布消息到 channel |
{text, tags, card, event, preferred_channels} |
| POST |
/v1/ingress/local |
本地消息入站 |
{channel_key, message, sender} |
| GET |
/v1/ingress/resolve |
解析 channel 入站路由 |
?channel_key= |
| POST |
/v1/official-api/poll |
手动触发官方 API 轮询 |
— |
| POST |
/v1/official-api/submit-flag |
手动提交 flag |
{flag, stage, source_card_id} |
| GET |
/v1/system/kickoffs |
列出 kickoff envelope |
?target_role= &limit= &after_id= |
| POST |
/v1/sessions/{session_id}/stop |
手动停止 session |
— |
| GET |
/v1/knowledge/query |
查询知识库 |
?q= &zone= &zones= &layers= &tags= &limit= |
| POST |
/v1/runner/shell |
执行 shell 命令 |
{command, timeout} |
| POST |
/v1/runner/python |
执行 Python 代码 |
{code, timeout} |
| GET |
/v1/runner/artifacts/{job_id} |
获取执行结果 |
— |
8. 配置参考
配置文件:configs/atk-sidecar.yaml(主配置)
8.1 顶层配置段
1 2 3 4 5 6 7 8 9 10 11 12
| server: storage: scheduler: official_api: automation: challenge_manager: swarm_dispatcher: competition: knowledge: runner: channels: routing:
|
8.2 关键配置项说明
多题并发开关:
1 2 3 4
| challenge_manager: enabled: true poll_interval: "30s" max_concurrent: 3
|
蜂群分发:
1 2 3 4 5
| swarm_dispatcher: enabled: true max_concurrent_per_stage: 3 cooldown: "0s" skills_dir: "skills/domain"
|
比赛平台直连(Competition 模式):
1 2 3 4 5 6
| competition: enabled: true auth_token: "Agent-Token 值" base_url: "https://competition-api.example.com" auto_submit: true auto_hint: true
|
AstrBot 连接(kickoff 部分):
1 2 3 4 5 6 7 8
| automation: kickoff: mode: "astrbot" astrbot: base_url: "http://127.0.0.1:6185" api_key: "abk_xxx" session_id: "atk-commander-main-v3" enable_streaming: true
|
8.3 配置 Struct 索引
| struct 名 |
yaml key |
文件 |
ServerConfig |
server |
config.go:34 |
OfficialAPIConfig |
official_api |
config.go:47 |
AutomationConfig |
automation |
config.go:93 |
PioneerKickoffConfig |
automation.pioneer_kickoff |
config.go:101 |
ChallengeManagerConfig |
challenge_manager |
config.go:107 |
SwarmDispatcherConfig |
swarm_dispatcher |
config.go:113 |
CompetitionConfig |
competition |
config.go:26 |
KnowledgeConfig |
knowledge |
config.go:120 |
KickoffConfig |
automation.kickoff |
config.go:132 |
AstrBotKickoffConfig |
automation.kickoff.astrbot |
config.go:142 |
RoutingConfig |
routing |
config.go:172 |
9. 知识系统
9.1 知识库结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| knowledge/ indexes/ # 索引文件(已构建,直接被 Retriever 加载) zone1_ctf_web/ trigger.jsonl # 触发规则:看到信号 X → 建立分支 Y runbook.jsonl # 操作手册:按步骤执行 A/B/C reference.jsonl # 参考资料:工具、CVE、服务特性 zone2_src_component/ zone3_multi_network_oa/ zone4_ad_pentest/ raw/ # 原始知识文档(供重新构建索引用) security/ # SecurityMatcher 数据(特征匹配型) apache/httpd-path-traversal.md redis/unauthorized-access.md tomcat/manager-default-creds.md
|
9.2 知识层语义
| Layer |
作用 |
触发时机 |
trigger |
信号→分支映射(看到 X,考虑 Y 漏洞) |
所有 recovery 轮次 |
runbook |
分支确认后的步骤手册(如何利用 Y) |
所有 recovery 轮次 |
reference |
工具/CVE/服务特定参考 |
L5_STRATEGIC_FAILURE 或 hard_stall |
Recovery 注入的 [KNOWLEDGE_HINT] 格式:
1 2 3 4 5 6 7 8 9 10 11
| [KNOWLEDGE_HINT] 以下为可选战术参考,不是事实。当前目标的真实观测优先级高于任何知识提示。
[TRIGGER SIGNALS] — 匹配到以下信号时触发对应漏洞分支: - ref=... score=0.82 title=... hint=...
[RUNBOOK] — 如果确认漏洞存在,按以下步骤执行: - ref=... score=0.71 branch=sqli hint=...
[REFERENCE] — 工具/CVE/服务特定参考(仅 L5/hard stall 时包含): - ref=... score=0.65 hint=...
|
9.3 SecurityMatcher
独立于 Retriever,专门处理特征指纹匹配:
- 加载
knowledge/security/**/*.md(YAML frontmatter + Markdown body)
- 特征字段:
signals(版本字符串、服务指纹)、category(漏洞类别)、cve
- 触发:AssetCard 到达 card_pipeline 时,
MatchBySignals(tech_stack + notes + url)
- 命中(score >= 1.0)→ 自动生成 HypothesisCard → 触发 swarm dispatch
9.4 Domain Skills
1 2 3 4 5 6 7 8
| skills/domain/ sqli/SKILL.md # SQL 注入战术手册(~107 行) ssrf/SKILL.md auth-bypass/SKILL.md file-inclusion/SKILL.md rce/SKILL.md xss/SKILL.md web-recon/SKILL.md # Web 侦察技术(recon_agent 使用)
|
Domain skill 已直接内联到对应的 assembled persona system prompt 中。
同时,swarm_dispatcher.go 的 loadDomainSkill() 也会在 dispatch 时动态注入 CommandText(备份路径)。
10. 持久化与存储布局
所有持久化使用本地 JSONL 文件,无数据库依赖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| runtime_data/ # 运行时数据根目录(yaml: storage.root_dir) cards/ <CardType>.jsonl # 每种 card 类型一个文件,按 card_id dedup events/ events.jsonl # 事件流,追加写入 channels/ main_control.jsonl # channel 消息,追加写入 recon_raw.jsonl exec_feedback.jsonl evidence_review.jsonl direct_solve.jsonl swarm_feedback.jsonl system_kickoff/ commander_main.jsonl # kickoff envelope 镜像(commander) pioneer_kickoff/ pioneer.jsonl # kickoff envelope 镜像(pioneer) flag_submissions/ submissions.jsonl # flag 提交记录 runner_artifacts/ <job_id>.txt # shell/python 执行输出
runtime_exec/ # runner 工作目录(临时文件)
|
Card 存储特点:
- 同一 (CardType, CardID) 做 upsert(新版本覆盖旧版本)
- 查询通过
CardFilter{CardType, Stage, GroupKey, Tag, Limit} 过滤
- Channel 消息只追加,不修改;分页通过
AfterID + Limit 实现
11. Go 包结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| atk/ cmd/atk-sidecar/ main.go # 入口:初始化所有组件,启动 HTTP 服务 + 调度 goroutine
internal/sidecar/ types.go # 核心类型:Card, Event, PublishRequest, OfficialSnapshotKickoff id.go # NewID(prefix) — nanoid 生成
cards/store.go # JSONL card 存储(Upsert, List, Get) channelbus/ publisher.go # PublishedMessage 定义 + filePublisher(写 JSONL) store.go # List(ListFilter) — 读 channel 消息,支持 Stage 过滤 config/config.go # 全部 Config struct + Load() + applyDefaults() events/ bus.go # 事件总线(Publish, Subscribe) store.go # 事件 JSONL 持久化 httpapi/ server.go # HTTP 路由注册 + 所有 handler dashboard.go # /v1/dashboard/progress 数据聚合 kickoff/store.go # Envelope 定义 + fileStore/httpStore/astrBotStore 实现 knowledge/ retriever.go # TF-IDF 知识检索(New, Query, QueryScoped) security_matcher.go # 特征指纹匹配(NewSecurityMatcher, MatchBySignals) officialapi/client.go # 比赛/题目平台 API 客户端 planning/ir.go # PlanIR 解析(atk_plan XML-like 结构) router/router.go # 路由规则引擎(ResolvePublish, ResolveIngress) runner/ executor.go # Shell/Python 执行(local/docker 模式) safety.go # 命令安全检查 types.go # RunRequest, RunResult service/ # 核心业务逻辑(见 2.1 节文件职责表) submissions/store.go # Flag 提交记录 JSONL 存储
|
包依赖原则:
service 包依赖所有基础设施包,基础设施包之间不互相依赖
httpapi 只依赖 service,不直接操作 storage
config 包被所有其他包依赖,不依赖任何内部包
12. 开发规范
12.1 Card 输出规范
agent 通过 fenced code block 输出 card,sidecar 自动解析:
1 2 3 4 5 6 7 8 9 10 11 12 13
| ```atk_card { "card_type": "HypothesisCard", // 必填,使用上方表格中的类型名 "card_id": "hypothesis-sqli-1", // 必填,格式: <type>-<描述>[-<序号>] "group_key": "main_control", // 目标 channel(可选,有默认值) "role": "planner_agent", // 产出角色 "summary": "...", "status": "proposed", "priority": "medium", "confidence": 0.75, // 0.0-1.0 "tags": ["PLAN"], "payload": { ... } // 类型专属字段 }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| `stage` 字段由 sidecar 从 envelope 自动填充,agent 通常不需要手动指定。
### 12.2 FlagCandidateCard 规范(关键)
```json { "card_type": "FlagCandidateCard", "card_id": "pioneer-flag-abc123", "group_key": "exec_feedback", "role": "pioneer", "status": "captured", "confidence": 0.95, "tags": ["FLAG", "PIONEER"], "payload": { "candidate": "flag{exact_flag_string_here}", // 只放精确 flag,不加任何额外文字 "method": "SQL union-based injection", "session_id": "atk-pioneer-v1" // 用于 markSessionSolved } }
|
session_id 字段用于 sidecar 的 markSessionSolved。如果 agent 不知道自己的 session_id,sidecar 会 fallback 到 DispatchResult.SessionID。
12.3 Recovery 上下文块规范
sidecar 在 recovery kickoff 的 CommandText 中注入以下块(按顺序):
1 2 3 4 5 6 7 8
| [RETRY_READY_CONTEXT] <最近 GroupDigest 的 retry_ready_context 字段内容>
[OFFICIAL_HINT] <比赛平台 hint>
[KNOWLEDGE_HINT] <知识库检索结果>
|
agent 应优先消费 [RETRY_READY_CONTEXT](最高优先级),[KNOWLEDGE_HINT] 视为可选参考。
12.4 Go 编码约定
- 错误处理:公共方法返回
error,内部 helper 返回值类型或零值
- context 传播:所有 IO 操作必须接受
ctx context.Context,async goroutine 使用 context.Background()
- 并发安全:共享状态用
sync.Mutex(参考 sessionRegistry、swarmDispatcher)
- 日志:使用
s.logger.Printf(),不使用全局 log;agent/component 名作前缀 [challenge_manager] ...
- 空值防御:所有方法第一行检查 receiver 是否为 nil(参考
recoveryController 系列方法)
- 向后兼容:新增可选字段用零值作 no-op(参考
SessionIDOverride、ListFilter.Stage)
12.5 Kickoff CommandText 格式
1 2 3 4 5
| /run [SYSTEM][<source>] stage=<code> target_url=<url> <key1>=<val1> ... <newline> <optional block 1> <newline> <optional block 2>
|
键值对中不能有空格(sidecar 用空格分割为 parts)。多词值用 _ 连接。
13. 测试策略
13.1 现有测试文件
| 文件 |
覆盖内容 |
service/service_test.go |
端到端集成测试:channel routing, card pipeline, recovery, swarm dispatch |
httpapi/competition_flow_test.go |
完整比赛流程:kickoff → card → flag → submit |
kickoff/store_test.go |
AstrBot kickoff command text 构建 |
knowledge/retriever_test.go |
知识检索:zone/layer 过滤、BM25 评分 |
config/config_test.go |
配置加载、角色白名单验证 |
planning/ir_test.go |
PlanIR XML 解析 |
runner/executor_test.go |
Shell/Python 执行、超时、输出裁剪 |
runner/safety_test.go |
危险命令检测 |
13.2 测试原则
service_test.go 使用 httptest.NewServer 模拟 AstrBot,不需要真实 AstrBot
- 每个测试用
t.TempDir() 作为 storage root,测试间完全隔离
- config_test.go 的
allowedRoles map 是新增 agent 角色的契约检查——新增角色后必须更新
13.3 运行测试
1 2 3
| go build ./... go test ./... go test ./internal/sidecar/service/... -v -run TestServiceChannelMessages
|
14. 部署说明
14.1 进程启动顺序
1 2 3 4 5 6
| 1. 启动 Kali 执行环境(如 Docker 容器) 2. 启动 AstrBot(端口 6185) - 确认 14 个 persona 已从 `persona/assembled/*.md` 粘贴到 AstrBot Web - 确认 MCP server(atk-sidecar MCP)已配置 3. 启动 atk-sidecar(端口 8090) ATK_SIDECAR_CONFIG=configs/atk-sidecar.yaml ./atk-sidecar
|
14.2 Persona 配置
1 2 3
|
sed -n '1,40p' persona/assembled/commander_main.md
|
14.3 构建
1
| go build -o atk-sidecar ./cmd/atk-sidecar/
|
14.4 多题并发模式启用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| competition: enabled: true auth_token: "<Agent-Token>" base_url: "https://hackathon-api.example.com" auto_submit: true auto_hint: true
challenge_manager: enabled: true poll_interval: "30s" max_concurrent: 3
scheduler: enabled: false
|
14.5 环境变量
| 变量 |
说明 |
默认 |
ATK_SIDECAR_CONFIG |
配置文件路径 |
configs/atk-sidecar.yaml |
14.6 运行时状态检查
1 2 3 4 5 6 7 8 9 10 11
| curl http://localhost:8090/healthz
curl http://localhost:8090/v1/sessions/atk-commander-main-v3
curl "http://localhost:8090/v1/cards?card_type=FlagCandidateCard&limit=10"
open http://localhost:8090/dashboard
|
附录:完整 Card 字段速查
FlagCandidateCard.payload
1 2 3 4
| candidate string 精确 flag 字符串(仅此,不加任何额外内容) method string 利用方式简述 session_id string 当前 AstrBot session id(用于 markSessionSolved) vuln_type string (蜂群输出时填写)
|
HypothesisCard.payload
1 2 3 4 5 6 7 8 9 10
| vuln_type string 必填,触发 swarm dispatch 的关键字段 hypothesis string 假设描述 preconditions []string 前提条件 validation_steps []string 验证步骤 success_signals []string 成功信号 failure_signals []string 失败信号 dominant_branch string 当前最强主线 required_next_moves []string 必要的下一步动作 path_id string 路径 ID(稳定、跨轮复用) tool_requirements []string 工具需求描述
|
GroupDigest.payload
1 2 3 4 5 6 7 8 9 10 11
| facts []string 已验证事实 unknowns []string 待确认项 priorities []string 优先级 dead_paths []string 已证伪路径 repeat_failures []string 重复失败模式 retry_ready_context []string 下一轮可直接消费的摘要 knowledge_candidates []string 候选知识提示(降噪后) dominant_branch string required_next_moves []string active_move_id string stall_state string "soft_stall" / "hard_stall" / ""
|
RiskCard.payload
1 2 3 4 5 6 7
| risk_type string reason string failure_level string L0-L5 decision string "proceed" / "require_revision_before_execute" / "halt" need_branch_replan bool L4/L5 时通常为 true retry_advice string anti_pattern []string
|
ActionCard.payload
1 2 3 4 5 6 7 8 9 10
| action_type string target string selected_tool string result string failure_level string L1-L4(成功时为空) failure_reason string observed_deltas []string 与预期的差异 path_id string move_id string evidence_gain string
|