安全分析技能路由器:为AI打造标准化扫描流程
2026/9/20 19:17:07 网站建设 项目流程

安全分析技能路由器并不是一个通用的 Agent 框架插件,它是一类把安全扫描流程标准化的调度层。在 AI 辅助安全分析的工作场景里,最让人头疼的不是某个扫描工具不会用,而是 AI 调用工具的过程没有标准:它可能跳过资产核验,可能把端口扫描和 Web 扫描混在一起,也可能拿到结果后无法和上一次扫描对比。安全分析技能路由器要解决的,正是这个问题。

这篇文章会围绕“给 AI 一个标准化的安全扫描流程”这条主线展开:先解释技能路由器是什么,再把扫描流程拆成可路由的阶段,接着用 Python 和配置实现一个最小可运行的示例,最后给出验证方法、常见坑、排查路径和生产环境落地建议。适合希望把 LLM 接入安全扫描流程的 DevSecOps 工程师、安全平台开发者和正在做 AI Agent 工程化的开发者阅读。

1. 先理解安全分析为什么需要“技能路由器”

1.1 什么是安全分析技能路由器

“技能路由器”可以理解成一个位于 AI 控制层和安全扫描工具之间的调度组件。它接收来自大模型或用户的意图,把意图翻译成标准化的扫描任务,再根据技能注册表选择合适的执行器,最后把不同工具的原始输出收敛成统一结构。

这个路由器不是简单的 if-else 分发器。它至少承担四件事:

  • 技能发现:系统里有哪些可调用的安全扫描能力。
  • 参数校验:AI 给出的参数是否合法,目标是否在授权范围内。
  • 风险控制:高风险的扫描动作是否需要人工审批或禁止执行。
  • 结果规范化:不同工具的输出格式差异很大,路由器要把它们转成统一模型。

在工程上,它像中间层。没有这层中间层时,AI 直接调用工具,prompt 一变,执行顺序和参数可能完全不可控。加了路由器之后,AI 只负责理解意图和生成参数,真正执行和收敛结果的工作由路由器完成。

1.2 没有标准流程时,AI 做安全扫描会暴露哪些问题

直接让大模型调用扫描工具,看起来效率很高,实际上问题非常集中。常见现象包括:

  1. 流程不固定。同样一句话,有时只扫描端口,有时又自动触发全量漏洞扫描,执行结果无法横向对比。
  2. 工具边界不清晰。Web 应用扫描和网络端口扫描混在一起,一个任务里出现多种风马牛不相及的动作。
  3. 参数不可控。AI 可能生成不合理的端口范围、超时时间或扫描深度,导致扫描长时间不结束。
  4. 回滚困难。扫描过程中一旦出现误操作,缺少中间状态记录,无法定位问题出在哪一步。
  5. 输出没法直接进入报告。Nmap、ZAP、Trivy 的输出格式各不相同,AI 分析时要么截断,要么误读。

这些问题的根因不是 AI 不够聪明,而是安全扫描本身缺少标准化任务边界。安全扫描是一个容易产生副作用的过程,必须让“决策”和“执行”分离,再让“执行”和“报告”分离。

1.3 路由器带来的改变

引入技能路由器之后,AI 侧的工作量反而会减少。AI 不再需要知道工具的具体命令行怎么写,只需要输出标准 JSON,比如“意图是 web_scan,目标是 example.com,风险等级是 medium”。

执行侧的变化更明显:

能力没有路由器有路由器
工具调用分散在 prompt 和代码中集中在技能注册表
参数来源模型自由生成schema 校验后使用
扫描范围依赖模型自觉用 allowed_targets 限制
结果格式每个工具自带格式统一 SkillResult
审计很难追踪决策链路每次路由都有记录

这个设计并不是限制 AI,而是让 AI 的“判断力”用在意图理解和结果解读上,而不是消耗在拼命令和处理工具差异上。

2. 把安全扫描流程拆成可路由的标准步骤

2.1 标准扫描流程的六个阶段

要设计技能路由器,先定义流程。安全扫描流程可以拆成六个阶段,每个阶段路由器都有明确职责。

阶段主要目的路由器责任
1. 请求理解把用户输入转成结构化意图提取扫描类型、目标、约束条件
2. 范围确认确认目标在授权范围内校验 allowed_targets、黑名单、CIDR
3. 技能路由从技能注册表选择可用技能规则匹配或 LLM 决策
4. 隔离执行在沙箱或独立容器中执行扫描设置超时、资源限制、日志采集
5. 结果规范把工具输出转成统一模型字段映射、去重、摘要生成
6. 报告复核输出给人工或 LLM 做结论生成审计记录、标记高风险项

这六个阶段应该作为项目主流程,而不是散落在工具脚本里。每个阶段的产物都要落盘或进入日志,这样后续排查时有据可查。

2.2 技能注册表:工具和技能的边界

很多人会把“技能”和“工具”混为一谈。在设计路由器时,它们是两个概念。

工具是实际执行动作的程序,比如 Nmap 是一个工具,OWASP ZAP 是一个工具,Trivy 也是一个工具。技能则是面向业务场景封装出来的能力单元,一个技能可以调用一个工具,也可以组合多个工具。

例如,“Web 资产基础检查”这个技能,内部可能先做端口发现,再读取 HTTP 响应头,最后检查 TLS 证书信息。这三个动作可以由三个不同工具完成,但对 AI 而言,它只是调用了一个技能。

技能注册表就是描述这些能力单元的元数据。每一条技能记录都要包含:

  • 技能名称和描述
  • 对应工具和命令模板
  • 输入参数 schema
  • 输出结果 schema
  • 超时时间和风险等级
  • 允许访问的目标范围
  • 是否需要人工审批

2.3 统一输入输出协议

标准化流程的核心是统一输入输出协议。输入统一叫ScanContext,输出统一叫SkillResult

ScanContext包含:

  • scan_id:任务唯一标识
  • target:扫描目标
  • scan_type:扫描类型
  • options:扩展参数字典

SkillResult包含:

  • skill_name:技能名称
  • status:success、failed、skipped
  • summary:人工可读的执行摘要
  • findings:规范化后的发现项列表
  • raw_output:原始输出,用于排查
  • error:失败时的错误信息

有了这套协议,路由器不需要关心每个工具返回的是什么格式,业务层也不需要对不同工具做特殊处理。新增一个工具时,只要实现一个技能类,保证输出符合SkillResult即可。

3. 最小实现:用 Python 搭建一个可扩展的技能路由器

下面代码用于说明思路,实际项目要结合自己的包名、文件路径和依赖版本调整。先看目录结构,再逐步补齐核心代码。

3.1 工程目录结构

sec_skill_router/ ├── main.py ├── registry.yaml ├── router.py ├── llm_router.py ├── executor.py └── skills/ ├── __init__.py ├── base.py ├── port_scan.py └── web_common_scan.py

registry.yaml描述技能元数据,router.py负责规则路由,llm_router.py负责基于大模型的路由决策,executor.py负责执行技能并处理超时,skills/base.py定义基类和数据结构。

3.2 定义技能接口和结果模型

# skills/base.py from abc import ABC, abstractmethod from dataclasses import dataclass, field from typing import Any @dataclass class ScanContext: scan_id: str target: str scan_type: str options: dict[str, Any] = field(default_factory=dict) @dataclass class SkillResult: skill_name: str status: str # success | failed | skipped summary: str findings: list[dict[str, Any]] raw_output: str = "" error: str = "" class BaseSkill(ABC): name: str = "base" @abstractmethod def validate(self, context: ScanContext) -> list[str]: """返回参数校验错误列表;为空表示通过""" ... @abstractmethod def execute(self, context: ScanContext) -> SkillResult: """执行实际扫描逻辑,并规范输出""" ...

这样设计的好处是路由器只依赖BaseSkill接口,不依赖具体工具实现。后续新增技能时,只需要继承BaseSkill并实现validateexecute

3.3 用 YAML 描述技能注册表

# registry.yaml version: 1 default_route: human_review skills: - name: web_port_scan tool: nmap type: network_scan risk_level: medium timeout_seconds: 300 allowed_targets: - "*.example.com" input_schema: target: string ports: string output_schema: open_ports: array - name: web_common_scan tool: owasp_zap type: web_scan risk_level: high timeout_seconds: 900 allowed_targets: - "*.example.com" input_schema: target: string scan_profile: string output_schema: alerts: array - name: container_image_scan tool: trivy type: artifact_scan risk_level: low timeout_seconds: 600 allowed_targets: - "registry.internal" input_schema: image: string output_schema: vulnerabilities: array

注册表的作用是让“新增技能”不用改主流程代码,只改配置和新增技能类。risk_level用于控制审批策略,timeout_seconds防止工具卡死,allowed_targets是扫描范围的安全底线。

3.4 先实现规则路由,再引入模型

规则路由的意义是保证基础请求 100% 可预测。它适合处理“端口”“镜像”“Web 漏洞”这类明确关键词。

# router.py from skills.base import BaseSkill, ScanContext class SkillRouter: def __init__(self, registry: dict, skills: dict[str, BaseSkill]): self.registry = registry self.skills = skills def route_by_rules(self, user_request: str, context: ScanContext): request_lower = user_request.lower() if "端口" in user_request or "port" in request_lower: skill = self.skills.get("web_port_scan") return [skill] if skill else [] if "容器" in user_request or "镜像" in user_request or "image" in request_lower: skill = self.skills.get("container_image_scan") return [skill] if skill else [] if "web" in request_lower or "漏洞" in user_request or "http" in request_lower: skill = self.skills.get("web_common_scan") return [skill] if skill else [] return []

规则路由会有覆盖不到的场景。比如用户说“检查一下这个服务有没有暴露不该暴露的端口”,没有出现“端口”两个字时,规则就失效。这时可以交给 LLM 做补充识别,但结果必须回传到校验层。

3.5 用 LLM 做意图识别和技能选择

在接入大模型时,建议不要直接让模型返回“要执行的 shell 命令”,而是让模型返回“技能名称和参数”。下面以 OpenAI 兼容接口为例,实际项目可以替换成私有化模型或其他接口。

# llm_router.py import json from typing import Any import openai def route_with_llm( user_request: str, skill_manifest: list[dict[str, Any]], client: openai.OpenAI, ) -> dict[str, Any]: prompt = ( "你是安全扫描技能路由器。请根据用户请求,从技能清单中选择合适的技能。" "不要执行扫描,不要生成 shell 命令,只输出技能选择结果。\n" f"技能清单:\n{json.dumps(skill_manifest, ensure_ascii=False)}\n" f"用户请求:\n{user_request}\n" "只输出 JSON,格式如下:\n" '{"intent": "web_scan", "skills": ["web_port_scan"], "parameters": {"target": "demo.example.com"}}' ) resp = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], response_format={"type": "json_object"}, ) return json.loads(resp.choices[0].message.content)

这段代码的关键点是约束模型只输出 JSON,并且把技能清单放在 prompt 里。实际生产中,技能清单越长,token 消耗越大,所以对技能描述要做精简,或者先经过规则初筛,再让模型从候选技能中选择。

拿到模型返回后,必须校验skills字段里的每个名称是否真的存在于注册表,以及参数是否通过技能类的validate。模型返回的任何内容都不能直接执行。

4. 标准化扫描流程的运行示例与验证方法

4.1 一条完整调用链路

假设用户输入:“扫描一下 https://demo.example.com 的常见 Web 漏洞。”

主流程可以写成下面的简化代码:

# main.py import uuid from llm_router import route_with_llm from router import SkillRouter from skills.base import ScanContext def run_scan(user_request: str): scan_id = uuid.uuid4().hex[:12] context = ScanContext( scan_id=scan_id, target="https://demo.example.com", scan_type="web", ) router = SkillRouter(registry={}, skills={}) selected_skills = router.route_by_rules(user_request, context) # 规则没有命中时,再由 LLM 补充 if not selected_skills: decision = route_with_llm( user_request, skill_manifest=[], client=llm_client, ) for skill_name in decision.get("skills", []): skill = router.skills.get(skill_name) if skill: selected_skills.append(skill) for skill in selected_skills: errors = skill.validate(context) if errors: print(f"[{scan_id}] validate failed: {errors}") continue result = skill.execute(context) print(f"[{scan_id}] {skill.name}: {result.status}") print(f"[{scan_id}] summary: {result.summary}") print(f"[{scan_id}] findings: {result.findings}")

在这条链路里,scan_id贯穿整个流程。日志输出会类似:

[3f2a9c1b2d01] intent=web_scan, target=https://demo.example.com [3f2a9c1b2d01] route=rule, selected_skills=[web_common_scan] [3f2a9c1b2d01] validate: target in allowed_targets, pass [3f2a9c1b2d01] web_common_scan: success [3f2a9c1b2d01] summary: 共发现 3 个中危告警、1 个低危告警 [3f2a9c1b2d01] findings: [{"severity": "medium", "name": "..."}]

只要日志里能看到 route 决策、validate 结果、技能执行状态和 findings 数量,就说明标准化流程已经跑通。

4.2 如何确认路由结果正确

验证路由器不是只看它能不能启动,还要看决策是否符合预期。推荐用测试用例固定关键行为:

# tests/test_router.py from router import SkillRouter def test_port_request_routes_to_port_scan(): router = build_test_router() context = build_test_context() selected = router.route_by_rules("帮我扫描一下端口", context) assert len(selected) == 1 assert selected[0].name == "web_port_scan"

测试用例至少要覆盖四类场景:

  • 规则命中的场景,结果是否可预测。
  • 规则未命中的场景,是否进入 LLM 决策分支。
  • LLM 返回了不存在的技能名时,是否被丢弃。
  • 参数校验失败时,是否跳过执行并记录错误。

4.3 最小验证检查清单

检查项预期结果
输入合法 URL 的目标路由到对应技能,validate 通过
输入不在 allowed_targets 中的目标拒绝执行并记录原因
LLM 返回不存在的技能名校验失败,不执行任何工具
工具执行超时标记 failed,保留原始日志
多个技能被选中串行执行或按配置并发,统一写审计日志

这套检查清单可以直接用于上线前的自动化回归。

5. 关键参数、选型与常见坑

5.1 路由器关键参数

技能路由器的核心参数不是模型参数,而是流程控制参数。调错这些参数可能导致扫描不可控或不可审计。

参数建议默认值作用误配影响
timeout_seconds300控制单个技能执行时长太短导致扫描中断,太长导致任务堆积
risk_levelmedium决定是否走审批高风险技能被自动执行,存在越权风险
approval_modeautoauto 或 manual设成 auto 后高风险动作缺乏人工确认
max_concurrency1并发技能数量过高会打满目标资源,产生误报
result_max_length2000进入 LLM 的文本上限超长结果被截断,影响分析准确度
retry_times1工具失败重试次数过高会重复扫描,产生大量冗余日志

这些参数建议放在配置文件或配置中心,不要写在代码里。每个扫描任务可以覆盖默认值,但覆盖行为必须记录到审计日志。

5.2 安全扫描工具选型对照

工具选型必须结合团队能力、资产类型和合规要求。下面表格只做思路示例,不代表推荐某一款特定工具。

技能名称常见工具适用场景风险等级
端口发现Nmap确认资产暴露端口medium
Web 应用扫描OWASP ZAP常见 Web 漏洞检查high
容器镜像扫描Trivy镜像依赖漏洞low
静态代码扫描Semgrep代码仓库安全审计low
基线合规检查OpenSCAP系统配置基线校验medium
依赖漏洞检查Grype软件供应链风险low

选型时要优先考虑工具是否容易在容器中运行、是否支持 JSON 输出、是否有稳定的退出码,以及是否允许限定扫描目标范围。

5.3 最常踩的四个坑

第一个坑是路由命中错误。用户说“扫一下服务地址”,规则把“服务”理解成了“Web 漏洞”,结果执行了全量 Web 扫描。原因是规则关键词太宽泛。解决方式是在规则命中后增加确认步骤,或要求 LLM 输出完整 intent 后再匹配。

第二个坑是 AI 幻觉出技能名。模型可能返回一个看起来合理但注册表中不存在的技能,比如web_full_scan。直接执行会报 KeyError,直接忽略又可能导致任务静默失败。解决方式是在丢弃不存在的技能时打印 warning,并让流程进入人工复核。

第三个坑是工具原始输出太长。Nmap 扫描大网段时可能产生几十 MB 输出,直接塞给 LLM 会超 token 上限。解决方式是先在技能层做摘要,只把 findings 中的结构化字段传给后续分析环节,原始输出落盘即可。

第四个坑是扫描范围失控。用户传入scan_type=web,但目标被误解析成整个网段,导致路由器同时触发多个扫描任务。解决方式是目标解析后必须做白名单校验,不允许 CIDR 范围直接进入高并发扫描。

6. 生产环境落地的排查路径与最佳实践

6.1 从日志回溯一次扫描任务

生产环境排查技能路由器问题,核心抓手是scan_id。所有日志、结果文件和审计信息都必须带上 scan_id。一次任务的理想日志链路是:

请求接入 -> 意图识别 -> 范围校验 -> 技能选择 -> 技能执行 -> 结果规范 -> 报告生成

排查时如果发现“扫描结果缺失”,先看技能执行阶段是否生成结果文件;再看结果规范阶段是否因为字段映射报错丢弃数据;最后看报告生成阶段是否因为读取权限遗漏输出。从日志链路倒推,比在代码里盲找更高效。

6.2 常见错误与排查顺序

问题现象可能原因检查方式处理建议
请求没有匹配到任何技能规则和 LLM 都没有命中查看意图识别日志补充技能描述或增加规则关键词
模型返回了不存在的技能技能清单和注册表不一致对比 LLM 输出和 registry.yaml在路由层强制二次校验
扫描工具退出码非零权限不足、依赖缺失查看 raw_output 和 stderr先复现工具命令,再调整权限
结果中大量误报扫描参数过宽或工具版本旧对比已知基线数据升级工具版本,收紧扫描参数
超时后任务没有清理缺少超时终止逻辑检查进程残留执行器用 subprocess 超时或容器销毁
LLM 分析结果和扫描结果不一致喂给模型的 findings 被截断检查 result_max_length增加摘要前置,减少原始文本

排查顺序建议是:先确认输入是否合法,再确认目标是否通过范围校验,接着看路由决策日志,再看技能执行状态,最后才检查模型分析环节。

6.3 生产级技能路由器需要补齐的能力

学习环境里跑通流程后,生产落地还需要补齐以下能力:

  • 配置外置化。技能列表、目标白名单、超时参数全部放到配置中心,不能写死在代码里。
  • 任务隔离。每个扫描任务运行在独立容器或隔离环境中,避免工具互相影响。
  • 审计闭环。LLM 决策、规则命中、参数覆盖、审批记录都要落日志。
  • 人工审批。risk_level 为 high 的技能必须进入人工审批队列。
  • 限流和配额。同一目标同时只能有一个任务,避免扫描风暴。
  • 结果保留策略。原始扫描结果不应无限期保存,要按合规要求设置保留周期。
  • 模型回退。LLM 不可用时,路由器要能回退到规则路由,保证基础任务不中断。

这些能力不是一次性开发完,而是按使用频率逐步补充。第一阶段先保证可审计,第二阶段做隔离和审批,第三阶段再接 CI/CD 和工单系统。

6.4 分阶段落地路线

如果团队第一次做“AI + 安全扫描”,建议不要直接做通用 Agent。先按下面路线推进:

阶段目标关键动作
阶段一规则路由可用固定若干技能,跑通注册表和校验
阶段二LLM 辅助决策规则未命中时使用模型,强制二次校验
阶段三审批和隔离接入审批队列、容器化执行
阶段四闭环集成接入资产中心、漏洞工单、CI 管道

每个阶段都要有明确的验收指标。例如阶段一验收标准是“端口类请求 100% 路由到端口技能”;阶段二验收标准是“模型输出中不存在的技能名 0 次进入执行器”;阶段三验收标准是“高风险扫描 100% 有人工审批记录”。有了指标,路由器的演进才能验证而不是凭感觉。

安全分析技能路由器的本质,是把“AI 很聪明”这件事约束到“流程可预期”的框架里。对新手来说,最有效的练习不是一开始就接大模型,而是先用规则路由跑通一个端口扫描技能,然后再逐步加入 Web 扫描、镜像扫描和 LLM 意图识别。只要保证每次路由决策都可审计、每个技能输入都经过校验、每个扫描结果都按统一结构输出,这套流程就能从 demo 平滑走向生产。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询