技术速递|借助 GitHub Security Lab Taskflow Agent 的 AI 支持漏洞分流
Taskflow 是一种 YAML 文件,用于描述我们希望借助 LLM 完成的一系列任务。通过这种方式,我们可以为不同任务编写提示词,并让任务之间形成依赖关系。seclab-taskflow-agent 框架负责按顺序执行这些任务,并将一个任务的结果传递给下一个任务。例如,在审计 CodeQL 告警结果时,我们首先需要获取代码扫描结果。随后,对于每一条结果,我们可能会有一系列需要检查的任务。例如,
作者:Man Yue Mo
排版:Alan Wang
了解我们如何使用新发布的 GitHub Security Lab Taskflow Agent,对 GitHub Actions 和 JavaScript 项目中的各类漏洞进行分流与分类处理。
安全告警的分流工作往往非常重复,因为误报通常源于一些对人工审计员来说很明显、但难以用格式化代码模式表达的特征。而大语言模型(LLM)在匹配这类传统工具难以处理的模糊模式方面表现出色,因此 GitHub Security Lab 一直在尝试使用 LLM 来对安全告警进行分流。为此,我们采用了近期发布的 GitHub Security Lab Taskflow Agent AI 框架,发现其效果非常显著。
💡 了解更多内容,并查看如何启用该 Agent,可参考我们之前的博客文章。
在本文中,我们将介绍这些分流任务流,展示实际效果,并分享一些实用经验,帮助你为漏洞分流或其他安全研究工作流开发属于自己的任务流。
通过使用本文中描述的任务流,我们在短时间内对大量代码扫描告警进行了分流,自 8 月以来发现了许多(约 30 个)真实世界中的漏洞,其中不少已经被修复并公开。在分流过程中,LLM 仅被授予了基础的文件获取和搜索能力。除了使用 CodeQL 生成告警之外,我们没有使用任何其他静态或动态代码分析工具。
虽然本文主要展示了我们如何使用 LLM 任务流来分流 CodeQL 查询结果,但这一通用流程的本质,是通过 LLM + Taskflow 来实现自动化。如果你的工作流程符合以下条件,那么它就非常适合采用这种方式:
-
你的任务包含大量重复步骤,并且每一步都有清晰、明确的目标。
-
其中一些步骤需要在代码中识别逻辑或语义层面的特征,这些特征对传统编程方式来说难以准确识别,但对人工审计员来说相对容易。尝试用代码识别它们,往往会导致大量补丁式的启发规则、糟糕的的正则表达式等。(这些正是 LLM 自动化的绝佳应用场景!)
如果你的项目满足上述条件,就可以使用 LLM + Taskflow 将这些优势场景自动化,同时借助 MCP Server 来完成那些更适合传统编程方式处理的任务。
seclab-taskflow-agent 和 seclab-taskflows 两个仓库都是开源的,任何人都可以基于它们开发 LLM 任务流来完成类似的工作。在本文结尾,我们还将分享一些在实践中总结出的开发经验与技巧。
Taskflow 简介
Taskflow 是一种 YAML 文件,用于描述我们希望借助 LLM 完成的一系列任务。通过这种方式,我们可以为不同任务编写提示词,并让任务之间形成依赖关系。seclab-taskflow-agent 框架负责按顺序执行这些任务,并将一个任务的结果传递给下一个任务。
例如,在审计 CodeQL 告警结果时,我们首先需要获取代码扫描结果。随后,对于每一条结果,我们可能会有一系列需要检查的任务。例如,我们可能需要检查该告警是否可以被不受信任的攻击者触达,以及是否存在认证检查。这些都会成为我们在 taskflow 文件中定义的一组任务。
我们使用任务而不是一个大型提示,是因为 LLM 的上下文窗口有限,而复杂的多步骤任务往往无法被正确完成。一些步骤经常会被遗漏,因此使用 taskflow 来组织任务可以避免这些问题。即使对于拥有更大上下文窗口的 LLM,我们也发现 taskflow 依然非常有用,它为我们提供了一种控制和调试任务的方式,同时也有助于完成规模更大、更复杂的任务。
seclab-taskflow-agent 还可以异步执行类似批量 “for loop” 的任务。在审计告警时,我们通常希望对每条告警应用相同的提示和任务,但告警细节各不相同。seclab-taskflow-agent 允许我们创建模板化提示,在执行任务时遍历告警列表,并为每条告警替换其特定细节。
从代码扫描告警到报告的分流 taskflow
GitHub Security Lab 会定期针对一组选定的开源仓库运行一组 CodeQL 查询。对这些告警进行分流的过程通常非常重复,并且对于某些告警,误报的原因往往高度相似,也很容易被识别出来。
例如,在对 GitHub Actions 的告警进行分流时,误报往往来自于已经设置的某些检查,这些检查确保只有仓库维护者才能触发存在漏洞的工作流,或者该存在漏洞的工作流在配置中已被禁用。这些访问控制检查形式多样,且不存在易于匹配的代码模式,因此像 CodeQL 这样的静态分析工具很难检测到它们。然而,具备代码语义基础知识的人类审计员通常可以很容易地识别这些检查,因此我们期望 LLM 也能够识别这些访问控制检查,并消除误报。
在数个月的时间里,我们主要使用 Claude Sonnet 3.5,针对少量 CodeQL 规则测试了我们的 taskflow。我们识别出了多个真实且可被利用的漏洞。这些 taskflow 并不会执行“端到端”的分析,而是生成一份包含所有细节和结论的漏洞报告,以便我们能够快速验证结果。我们并未指示 LLM 通过构造利用代码来验证结果,也没有为其提供任何运行时环境来测试其结论。然而,即便缺少自动化验证步骤,结果依然保持了较高的准确性,我们也能够快速移除 CodeQL 查询中的误报。
这些规则的选择基于我们自身在分流此类告警方面的经验,以及这些任务是否能够被表述为清晰、明确、适合 LLM 理解的指令。
通用 taskflow 设计
Taskflow 通常由多个任务组成,这些任务被划分为几个不同阶段。在第一阶段,任务会收集与告警相关的各类信息。随后,这些信息会被传递到审计阶段,在该阶段中,LLM 会基于我们以往分流告警的经验,查找常见的误报原因。在审计阶段之后,会使用收集到的信息生成一份漏洞报告。
在实际的 taskflow 中,信息收集阶段和审计阶段有时会合并为一个任务,有时则会拆分为多个任务,这取决于任务的复杂程度。
为了确保生成的报告包含足够的信息供人工审计员做出判断,我们增加了一个额外步骤,用于检查报告是否具备正确的格式并包含正确的信息。之后,会创建一个 GitHub Issue,等待进一步审核。
创建 GitHub Issue 不仅便于我们审查结果,也为后续扩展分析提供了一种方式。在审查和检查这些 Issue 之后,我们经常会发现一些在审计阶段遗漏的误报原因。此外,如果 agent 判断某条告警是有效的,但人工审查者不同意,并发现其因某个此前 agent 未知的原因而实际上是误报,人工审查者可以将该原因记录为告警驳回原因或 Issue 评论。当 agent 未来分析类似案例时,它将能够感知这些存储在 Issue 和告警驳回原因中的历史分析,将新的信息纳入其知识基础中,从而更有效地检测误报。
信息收集
在这一阶段,我们会指示 LLM(示例见下方分流示例章节)收集与告警相关的信息,这些信息会结合威胁模型以及人类对该类告警的一般认知。例如,对于 GitHub Actions 告警,它会查看 GitHub 工作流文件中设置了哪些权限、哪些事件会触发该工作流、该工作流是否被禁用等。这些通常是相互独立的任务,并遵循简单、明确的指令,以确保所收集信息的一致性。例如,检查 GitHub 工作流是否被禁用,会通过 MCP server 调用 GitHub API 来完成。
为了确保所收集信息的准确性并减少幻觉,我们要求 LLM 提供精确的源代码引用,包括文件名和行号,以支持其收集到的信息:
你应当包含不受信任代码被调用的位置行号,以及在注释中包含被调用的不受信任代码或包管理器。
每个任务都会将其收集到的信息存储在审计笔记中,这些笔记类似于对某条告警的持续记录。任务完成后,这些笔记会被序列化并存储到数据库中,后续任务完成时可以继续在其基础上追加内容。

通常,每个信息收集任务都是相互独立的,不需要读取其他任务的笔记。这有助于每个任务专注于自身的范围,而不被此前收集的信息干扰。
最终的结果是一个与告警关联的“信息集合”,以笔记的形式传递给审计任务。
审计 issue
在这一阶段,LLM 会遍历已收集的信息,并执行一系列特定检查,以剔除最终被判定为误报的告警结果。例如,在分流 GitHub Actions 告警时,我们可能已经收集了触发存在漏洞工作流的事件信息。在审计阶段,我们会检查这些事件是否可以被攻击者触发,或者是否在特权上下文中运行。经过该阶段后,许多对人工审计员来说显而易见的误报都会被移除。
决策与报告生成
对于通过审计阶段的告警,下一步是使用收集到的信息生成漏洞报告,并说明在审计阶段作出决策的理由。同样地,在提示词中,我们会非常精确地规定报告的格式以及所需包含的信息。特别是,我们希望报告简明扼要,同时包含足够的信息以便我们验证结果,例如精确的代码引用和代码块。
生成的报告会使用前几个阶段笔记中收集的信息,仅在需要时从源代码中获取必要的代码片段。该阶段不会执行进一步的分析。任务的高度严格和精确,有助于进一步减少幻觉。
报告校验与 Issue 创建
报告生成后,我们会指示 LLM 检查报告,以确保其包含所有相关信息,并且信息之间保持一致:
检查报告是否包含所有必要信息:
-该条件仅在包含告警的工作流是可复用 action 且不存在高权限触发器时适用。
你应使用 gh_actions 工具箱中的相关工具进行检查。如果不满足该条件,则忽略此检查。 在这种情况下,检查报告是否包含列出受影响 action 使用者的章节。
如果不存在受影响的 action 使用者且不存在高权限触发器,则将该告警标记为无效,并使用 alert_id 和 repo 删除键为 {{
RESULT_key }} 的 memcache 条目。
缺失或不一致的信息通常意味着幻觉或其他误报原因(例如无法追踪攻击者可控的输入)。在这些情况下,我们会驳回该报告。
如果报告包含所有信息且保持一致性,我们便会创建一个 GitHub Issue 来跟踪该告警。
Issue 审查与仓库特定知识
在上一步中创建的 GitHub Issue 包含验证该问题所需的全部信息,包括代码片段以及文件和行号引用。这相当于一个“检查点”和信息摘要,使我们能够方便地继续扩展分析。
事实上,在创建 Issue 之后,我们经常会发现某些仓库特定的权限检查或清洗逻辑会使该问题成为误报。我们可以通过创建新的 taskflow,在提示中加入仓库特定知识,对这些 Issue 进行复审。一种我们尝试过的方法是收集仓库中的告警驳回原因,并指示 LLM 在审查 GitHub Issue 时考虑这些驳回原因。这使我们能够移除那些因仓库特定原因导致的误报。
在这种情况下,LLM 在考虑了记录于告警驳回原因中的自定义 check-run 权限检查之后,能够将该告警识别为误报。
分流示例与结果
在本节中,我们将给出一些 taskflow 在实际中的示例,特别是用于分流 GitHub Actions 和 JavaScript 告警的 taskflow。
GitHub Actions 告警
我们分流的具体 Actions 告警包括:在特权上下文中检出不受信任代码,以及代码注入。
这些查询的分流过程具有高度相似性。例如,它们都需要检查工作流触发事件、存在漏洞的工作流权限,以及追踪工作流调用者。主要差异在于对漏洞细节的本地分析:对于代码注入,需要分析注入代码是否被清洗、表达式如何求值、输入是否真正可控(例如,pull request ID 通常不太可能导致代码注入问题);对于不受信任检出,则需要判断在检出之后是否存在有效的代码执行点。
由于这些 taskflow 中有大量相同的元素,我们以代码注入的分流 taskflow 作为示例。需要注意的是,由于这些 taskflow 具有很强的共性,我们大量使用了 seclab-taskflow-agent 中的可复用特性,例如可复用的提示和任务。
在针对这些规则手动分流 GitHub Actions 告警时,我们通常会因为以下原因遇到误报:
-
存在漏洞的工作流未在特权上下文中运行。这由触发该工作流的事件决定。例如,由
pull_request_target触发的工作流运行在特权上下文中,而由pull_request事件触发的工作流则不是。通常只需查看工作流文件即可判断这一点。 -
存在漏洞的工作流在仓库中被显式禁用。这一点可以通过查看仓库中的工作流设置轻松确认。
-
存在漏洞的工作流显式限制了权限且未使用任何 secrets,在这种情况下,可获取的权限非常有限。
-
漏洞特定问题,例如在代码注入场景中用户输入无效或清洗器缺失,在不受信任检出场景中不存在有效的代码执行点。
-
存在漏洞的工作流是可复用工作流,但无法从任何在特权上下文中运行的工作流中被触达。
在很多情况下,分流这些告警需要进行大量类似上述所列的简单但繁琐的检查,而只要满足其中任意一个条件,就可以很快将某条告警判定为误报。因此,我们基于这些标准来建模我们的分流 taskflow。
因此,在信息收集阶段和审计阶段,我们的 action 分流 taskflow 由以下任务组成:
-
工作流触发分析:该阶段同时执行信息收集和审计。它首先收集触发存在漏洞工作流的事件,以及该工作流中使用的权限和 secrets,同时检查该工作流是否在仓库中被禁用。所有信息均仅限于该存在漏洞的工作流本身。这些信息会被存储在持续更新的笔记中,随后序列化为数据库条目。由于该任务较为简单,且仅涉及对存在漏洞工作流的检查,因此还会基于工作流触发条件执行初步审计,以移除一些明显的误报。
-
代码注入点分析:这是另一个仅分析存在漏洞工作流的任务,将信息收集与审计合并为一个任务。该任务会收集代码注入点的位置以及被注入的用户输入信息,并执行本地审计,以检查用户输入是否构成有效的注入风险,以及是否存在清洗器。
-
工作流用户分析:该任务执行一次简单的调用者分析,用于查找存在漏洞的工作流的调用者。由于该步骤可能需要检索并分析大量文件,因此被拆分为两个主要任务,分别执行信息收集和审计。在信息收集任务中,会获取存在漏洞的工作流的调用者,并将其触发事件、权限以及 secrets 的使用情况记录在笔记中。随后,在审计任务中使用这些信息来判断该存在漏洞的工作流是否可以被攻击者触达。
这些任务都会依次应用于告警,并在每个步骤中根据任务中定义的标准过滤掉误报。
在完成信息收集和审计阶段后,我们的笔记通常会包含如下信息:触发存在漏洞工作流的事件、涉及的权限和 secrets,以及(如果是可复用工作流)使用该工作流的其他工作流及其触发事件、权限和 secrets。这些信息将构成漏洞报告的基础。作为一项合理性检查,为确保当前收集的信息完整且一致,在创建报告之前会使用 review_report 任务来检查是否存在缺失或不一致的信息。
随后,使用 create_report 任务生成漏洞报告,该报告将作为 GitHub Issue 的基础。在创建 Issue 之前,我们会再次确认报告包含所需的全部信息,并符合我们要求的格式。缺失信息或不一致通常意味着某些步骤失败或出现幻觉,因此这些情况会被拒绝。
下图展示了 triage_actions_code_injection taskflow 的主要组成部分:

随后,我们使用 create_issue_actions taskflow 创建 GitHub Issue。如前所述,创建的 GitHub Issue 包含足够的信息和代码引用,能够快速验证漏洞,同时也作为当前分析的总结,使我们可以基于该 Issue 继续深入分析。下方展示了一个由 LLM 创建的 Issue 示例。
特别地,我们可以将 GitHub Issue 和告警驳回原因作为引入仓库特定安全措施并进一步分析的手段。为此,我们使用 review_actions_injection_issues taskflow,首先从仓库中收集告警驳回原因,然后将这些驳回原因与 GitHub Issue 中描述的告警进行比对。在这一过程中,我们直接以 Issue 作为起点,并指示 LLM 审计该 Issue,检查是否有任何告警驳回原因适用于当前问题。由于 Issue 已包含该告警的所有相关信息和代码引用,LLM 能够结合 Issue 和告警驳回原因,进一步推进分析并发现更多误报。下方展示了一个基于驳回原因被否决的告警示例。
下图展示了 Issue 创建与审查 taskflow 的主要组成部分:

JavaScript 告警
与对 GitHub Actions 告警进行分流类似,我们也对 JavaScript / TypeScript 语言的代码扫描告警进行了分流,但覆盖范围相对较小。在 JavaScript 领域,我们主要分流的是客户端跨站脚本的 CodeQL 规则告警(js/xss)。
与 GitHub Actions 告警相比,客户端 XSS 告警在来源、汇点以及数据流方面具有更高的多样性。
用于分析这些 XSS 漏洞的提示侧重于帮助负责分流的人做出有根据的判断,而不是替他们直接做决定。具体做法是突出显示那些看起来可能使告警可被攻击者利用的因素,以及更重要的——哪些因素很可能阻止该潜在问题被利用。除此之外,任务流的整体结构与 GitHub Actions 告警章节中描述的方案基本一致。
在手动分流 XSS 告警时,我们经常会因为以下原因识别出误报:
-
使用了自定义或 SAST 工具无法识别的清洗器(例如基于正则表达式的处理),工具无法验证其有效性。
-
报告的来源在实际中很可能不可达(例如,需要攻击者直接从 Web 服务器发送消息)。
-
不受信任的数据流入了潜在危险的汇点,但其输出仅以不可被利用的方式使用。
-
SAST 工具不了解不受信任数据最终使用位置的完整上下文。
基于这些误报情况,我们对相关任务流中的提示,甚至是当前使用的 personality 进行了扩展和调整。如果你在某个项目中遇到特定类型的误报,对其进行审计并扩展提示是有意义的,这样可以确保这些误报被正确标记(同样地,如果某些来源或汇点在你的项目中并不被视为漏洞,也应在提示中体现)。
最终,在执行 triage_js_ts_client_side_xss 和 create_issues_js_ts 这两个任务流之后,告警会生成对应的 GitHub Issue,例如:

这是一个值得继续跟进的示例告警(最终被确认是真实存在的漏洞,可通过使用 javascript: URL 进行利用)。而对于被任务流 agent 判定为误报的告警,其对应的 Issue 会被打上 “FP”(false positive)的标签,例如:

Taskflows 开发经验分享
在本节中,我们将分享在开发这些 taskflow 过程中积累的一些经验,以及我们认为在 taskflow 开发中非常有用的实践。希望这些经验能帮助其他人构建属于自己的 taskflow。
使用数据库存储中间状态
在开发由多个任务组成的 taskflow 时,我们有时会在后续阶段的任务中遇到问题。这些问题可能是一些常见的软件问题,例如 API 调用失败、MCP 服务器缺陷、与 prompt 相关的问题、token 限制,或配额问题等。
通过保持任务足够小,并将每个任务的结果存储到数据库中,我们避免了在发生失败时重复执行耗时较长的任务。当某个 taskflow 中的任务失败时,我们只需从失败的任务开始重新运行 taskflow,并复用数据库中已保存的前序任务结果。这不仅在任务失败时为我们节省了时间,也帮助我们隔离各个任务的影响,并可以以前一个任务生成的数据库结果为起点,对单个任务进行调优。
将复杂任务拆分为更小的任务
在开发告警分流相关的 taskflow 时,我们当时使用的模型并不擅长处理大上下文和复杂任务。当尝试在同一个上下文中执行多个复杂任务时,经常会遇到诸如任务被跳过、指令未被正确执行等问题。
为了解决这一问题,我们将任务拆分为更小、相互独立的任务,每个任务都从一个全新的上下文开始。这种方式有效降低了上下文窗口的大小,缓解了我们遇到的诸多问题。
一个典型的例子是使用模板化的 repeat_prompt 任务,它会遍历一组任务列表,并为每个任务启动一个新的上下文。通过这种方式,相比在同一个 prompt 中处理整个列表,我们能够确保列表中的每一个任务都被执行,同时将每个任务的上下文控制在最小范围内。
这种设计的一个额外好处是,我们可以以更细粒度对 taskflow 进行调试和优化。通过将任务拆分得足够小,并将每个任务的结果存储到数据库中,我们可以轻松地抽取 taskflow 的某一部分并单独运行。
尽可能将任务委派给 MCP 服务器
在最初进行信息检查和收集(例如从源码中获取 workflow 触发条件)时,我们直接在 prompt 中加入指令,认为 LLM 能够自行从源码中提取这些信息。虽然这种方式在大多数情况下可行,但由于 LLM 的非确定性特性,我们也观察到了一些不一致的结果。
例如,LLM 有时只记录了触发 workflow 的部分事件,或者在判断某个触发条件是否以特权上下文运行 workflow 时得出不一致的结论。
鉴于这些信息和检查都可以通过程序方式轻松完成,我们最终在 MCP 服务器中实现了相应的工具,用于收集信息并执行这些检查,从而获得更加一致、可靠的结果。
通过将那些可以轻松程序化完成的任务迁移到 MCP 服务器工具中,而将更复杂的逻辑推理任务(例如查找权限检查)留给 LLM,我们既发挥了 LLM 的能力,又保证了结果的一致性。
使用可复用的 taskflow,在多个 taskflow 中统一应用调整
在开发告警分流 taskflow 的过程中,我们意识到许多任务可以在不同的 taskflow 之间共享。为了确保在一个 taskflow 中进行的调整可以同步应用到其他 taskflow,并减少大量的复制粘贴工作,我们需要一种方式对 taskflow 进行重构,提取可复用的组件。
为此,我们引入了可复用任务和可复用 prompt 等功能。借助这些特性,我们能够在不同的 taskflow 之间复用任务,并一致性地应用修改。
在多个 taskflow 中统一配置模型
随着 LLM 的持续演进和新版本的频繁发布,我们很快意识到需要一种方式在多个 taskflow 中统一更新模型版本。因此,我们增加了模型配置功能,使我们能够跨 taskflow 切换和更新模型。这在需要升级模型版本,或希望使用不同模型重新运行 taskflow 进行实验时尤为有用。
结语
在本文中,我们展示了如何为 seclab-taskflow-agent 创建 taskflow,以分流代码扫描告警。
通过将分流过程拆解为精确、具体的任务,我们能够利用 LLM 自动化大量重复性工作。通过在提示中设定清晰、明确的标准,并要求 LLM 提供包含代码引用的精确答案,LLM 能够按指示完成任务,同时将幻觉控制在最低水平。这使我们能够在无需动态验证告警的情况下,利用 LLM 的能力进行告警分流,并大幅减少误报。
最终,我们在运行这些分流 taskflow 后,从 CodeQL 告警中发现了约 30 个真实世界漏洞。
文中讨论的 taskflow 已发布在我们的仓库中,我们也非常期待看到你基于它们构建的内容!近期我们还在 AI 辅助代码审计和漏洞挖掘领域进行了更多实验,敬请期待后续内容。
👉 获取 GitHub Security Lab Taskflow Agent 的配置指南
免责声明
-
当我们使用这些 taskflow 来报告漏洞时,研究人员都会在提交报告之前,对所有生成的输出进行仔细人工审查。我们强烈建议你也采取同样的做法。
-
请注意,运行这些 taskflow 可能会触发大量工具调用,从而很容易消耗掉大量配额。
-
这些 taskflow 可能会创建 GitHub Issues。在对他人的仓库运行之前,请务必征得仓库所有者的同意,并保持必要的克制与尊重。
更多推荐


所有评论(0)