安全分析技能路由器并不是一个通用的 Agent 框架插件它是一类把安全扫描流程标准化的调度层。在 AI 辅助安全分析的工作场景里最让人头疼的不是某个扫描工具不会用而是 AI 调用工具的过程没有标准它可能跳过资产核验可能把端口扫描和 Web 扫描混在一起也可能拿到结果后无法和上一次扫描对比。安全分析技能路由器要解决的正是这个问题。这篇文章会围绕“给 AI 一个标准化的安全扫描流程”这条主线展开先解释技能路由器是什么再把扫描流程拆成可路由的阶段接着用 Python 和配置实现一个最小可运行的示例最后给出验证方法、常见坑、排查路径和生产环境落地建议。适合希望把 LLM 接入安全扫描流程的 DevSecOps 工程师、安全平台开发者和正在做 AI Agent 工程化的开发者阅读。1. 先理解安全分析为什么需要“技能路由器”1.1 什么是安全分析技能路由器“技能路由器”可以理解成一个位于 AI 控制层和安全扫描工具之间的调度组件。它接收来自大模型或用户的意图把意图翻译成标准化的扫描任务再根据技能注册表选择合适的执行器最后把不同工具的原始输出收敛成统一结构。这个路由器不是简单的 if-else 分发器。它至少承担四件事技能发现系统里有哪些可调用的安全扫描能力。参数校验AI 给出的参数是否合法目标是否在授权范围内。风险控制高风险的扫描动作是否需要人工审批或禁止执行。结果规范化不同工具的输出格式差异很大路由器要把它们转成统一模型。在工程上它像中间层。没有这层中间层时AI 直接调用工具prompt 一变执行顺序和参数可能完全不可控。加了路由器之后AI 只负责理解意图和生成参数真正执行和收敛结果的工作由路由器完成。1.2 没有标准流程时AI 做安全扫描会暴露哪些问题直接让大模型调用扫描工具看起来效率很高实际上问题非常集中。常见现象包括流程不固定。同样一句话有时只扫描端口有时又自动触发全量漏洞扫描执行结果无法横向对比。工具边界不清晰。Web 应用扫描和网络端口扫描混在一起一个任务里出现多种风马牛不相及的动作。参数不可控。AI 可能生成不合理的端口范围、超时时间或扫描深度导致扫描长时间不结束。回滚困难。扫描过程中一旦出现误操作缺少中间状态记录无法定位问题出在哪一步。输出没法直接进入报告。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、黑名单、CIDR3. 技能路由从技能注册表选择可用技能规则匹配或 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技能名称statussuccess、failed、skippedsummary人工可读的执行摘要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.pyregistry.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_factorydict) 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并实现validate和execute。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_asciiFalse)}\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( modelgpt-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_idscan_id, targethttps://demo.example.com, scan_typeweb, ) 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[], clientllm_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] intentweb_scan, targethttps://demo.example.com [3f2a9c1b2d01] routerule, 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确认资产暴露端口mediumWeb 应用扫描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_typeweb但目标被误解析成整个网段导致路由器同时触发多个扫描任务。解决方式是目标解析后必须做白名单校验不允许 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 平滑走向生产。