ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

GitHub Actions Workflow 提取器 v1 全面解析:用 dcg scan 拦截 CI 中的危险命令

GitHub Actions Workflow 提取器 v1 全面解析:用 dcg scan 拦截 CI 中的危险命令 GitHub Actions Workflow 提取器 v1 全面解析用 dcg scan 拦截 CI 中的危险命令【免费下载链接】destructive_command_guardThe Destructive Command Guard (dcg) is for blocking dangerous git and shell commands from being executed by agents.项目地址: https://gitcode.com/GitHub_Trending/de/destructive_command_guard本文基于 Destructive Command Guarddcg仓库中的 GitHub Actions 提取器 v1 规范 编写并结合 src/scan.rs 源码实现与测试用例进行纵深验证。读者读完本文将完整掌握 dcg 如何从.github/workflows/*.yml工作流文件中精准提取run:块内的 shell 命令用于安全扫描、提取器的上下文感知边界与 v1 已知限制以及如何用dcg scan命令在本地与 CI 中实际落地防护。一、Overview这个提取器解决什么问题GitHub Actions 是 CI/CD 攻击面中非常容易藏匿危险命令的场所rm -rf、git reset --hard、gh secret delete这类命令一旦被写进 workflow 并合入主干就可能在被触发时造成不可逆破坏。dcg 的GitHub Actions extractor正是为此设计——它解析 workflow 文件从steps:数组内的run:块中提取 shell 命令交给与 hook 模式完全相同的模式引擎进行危险命令评估见 README 的 Repository Scanning 章节。提取器遵循一条根本原则prefer silence over false positives宁可不报不可误报这意味着提取器只扫描真正会被执行的命令上下文而不是像 grep 一样对所有字符串做模式匹配。README 中明确强调了这一差异dcg scanunderstands thatrm -rf /in a comment is data, not code它使用理解文件结构的提取器只找出实际会被执行的命令。Extractor IDgithub_actions.steps.run—steps:中run:步骤内的 shell 命令该 ID 是扫描结果归属与后续处理的唯一标识。在源码中它以常量形式出现于 src/scan.rs#L1859并贯穿所有提取路径。二、文件检测什么样的文件会被扫描2.1 支持的路径模式Pattern示例说明.github/workflows/*.yml.github/workflows/ci.yml主要模式.github/workflows/*.yaml.github/workflows/deploy.yaml备用扩展名.github/workflows/**/*.yml.github/workflows/sub/ci.yml嵌套目录判定要求路径必须包含.github/workflows/目录结构文件扩展名必须是.yml或.yaml大小写不敏感。2.2 不会被匹配的情况Pattern原因workflows/ci.yml缺少.github/父目录.github/workflow/ci.yml目录名错误单数 workflow.github/workflows/ci.json扩展名错误action.yml复合 action 文件结构不同.github/actions/my-action/action.yml复合 action2.3 源码实现文件判定逻辑在 src/scan.rs#L1832-L1849 的is_github_actions_workflow_path函数中fn is_github_actions_workflow_path(path: Path) - bool { let Some(ext) path.extension().and_then(std::ffi::OsStr::to_str) else { return false; }; let ext ext.to_ascii_lowercase(); if ext ! yml ext ! yaml { return false; } let components: VecString path .components() .filter_map(|c| c.as_os_str().to_str().map(str::to_ascii_lowercase)) .collect(); components .windows(2) .any(|w| w[0] .github w[1] workflows) }两个关键实现细节值得注意扩展名大小写不敏感ext.to_ascii_lowercase()使得.GITHUB/WORKFLOWS/CI.YML也能被识别目录匹配基于路径组件滑动窗口components.windows(2)在任意深度查找相邻的.githubworkflows组件对这正是嵌套目录.github/workflows/sub/ci.yml也能命中的原因同时action.yml、workflows/ci.yml、.github/workflow/ci.yml均被排除。对应测试位于 src/scan.rs#L5856-L5880 的github_actions_path_detection覆盖了上述所有正例与反例包括大小写变体.GITHUB/WORKFLOWS/CI.YML。在扫描主流程中文件被识别为 GitHub Actions workflow 后会进入 src/scan.rs#L913-L919 调用extract_github_actions_workflow_from_str提取命令与 Shell、Dockerfile、GitLab CI 等提取器并行工作src/scan.rs#L853-L878 的提取器选择逻辑。三、GitHub Actions Workflow 结构与提取边界一个典型的 workflow 文件结构如下name: CI on: [push, pull_request] # Triggers (not extracted) jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 # uses: (not extracted) - name: Build # name: (not extracted) run: npm run build # run: (EXTRACTED) - run: | # Block scalar (EXTRACTED) npm test npm run lint核心结论只有steps:数组内的run:值会被提取为 shell 命令。on:触发器、name:、uses:、env:、with:、secrets:等均属于数据上下文data context不会被提取workflow 级与 job 级的name:、on:、permissions:、concurrency:、defaults:、env:等元数据全部跳过。README 中给出了同样的上下文示例README.md#L1650-L1662run: |内的npm install/npm run build被提取而env.NODE_ENV被跳过。四、支持的 Run 块形式四种4.1 单行 RunFlow Scalar语法run: commandsteps: - run: echo Hello, World! - run: npm install - run: git status行为提取run:之后的命令文本处理 YAML 引号双引号、单引号、无引号去除首尾空白。测试用例对应源码测试run: echo hello→ 提取echo hellorun: echo \quoted\→ 提取echo quoted转义处理run: rm -rf ./build→ 提取rm -rf ./build单引号- run: cmd列表项中的 run→ 提取cmd源码测试github_actions_extractor_extracts_run_steps_onlysrc/scan.rs#L5883-L5907验证了单行 run 的提取包括name: rm -rf /不被提取、run: git status与run: rm -rf ./build被正确提取且行号准确第 8、9 行。github_actions_quoted_run_valuesrc/scan.rs#L7522-L7533验证了带引号的run: rm -rf ./build。4.2 字面块 RunLiteral Block|语法run: |后接缩进内容steps: - run: | echo First line echo Second line npm run build行为识别|YAML 字面块标量指示符捕获下方所有缩进行作为 shell 脚本保留命令之间的换行后续交给 shell 脚本提取器做命令解析含注释剥离等。测试用例多行字面块提取所有命令块内注释由 shell 提取器处理块内空行保留缩进回到run:层级时块结束。源码测试覆盖丰富github_actions_extractor_handles_block_scalar_and_skips_commentssrc/scan.rs#L5910-L5927列表项与steps:键同缩进、块内# rm -rf /注释被跳过、rm -rf ./build被提取github_actions_literal_block_with_commentssrc/scan.rs#L7403-L7418块内 shell 注释不提取github_actions_literal_block_multiple_commandssrc/scan.rs#L7421-L7435多命令块按关键字过滤仅提取含rm的命令github_actions_literal_block_with_empty_linessrc/scan.rs#L7457-L7473空行保留且不影响提取。4.3 折叠块 RunFolded Block语法run: 后接缩进内容steps: - run: echo This is a very long command that spans multiple lines and gets folded into one行为识别折叠块标量指示符换行按 YAML 标准折叠规则转换为空格作为单条长命令处理。测试用例折叠块被识别行折叠正确应用。对应测试github_actions_folded_blocksrc/scan.rs#L7438-L7454。4.4 引用标量Quoted Scalars语法run: command或run: commandsteps: - run: echo \nested quotes\ - run: single quoted: no escapes except 行为双引号处理\n、\t、\、\\等转义单引号仅转义为单个无引号去空白后原样使用。测试用例双引号含\n→ 命令中出现换行双引号含\→ 字面双引号单引号含→ 单引号无引号值直接提取。这些行为在源码unquote_yaml_scalarsrc/scan.rs#L1985-L2023中有精确实现双引号分支逐字符处理\n/\r/\t/\/\\未知转义保留原样单引号分支直接content.replace(, )。规范中的三个示例与源码行为完全对应Input: run: echo \hello\ After unquoting: echo hello Input: run: echo quoted After unquoting: echo quoted Input: run: echo plain After unquoting: echo plain五、上下文感知提取5.1 Steps 数组检测提取器跟踪steps:上下文jobs: test: steps: # -- Enter steps context - run: cmd1 # Extracted - run: cmd2 # Extracted outputs: # -- Exit steps context result: ...行为只有活动steps:块内的run:被提取文档根部或steps:之外的run:被忽略缩进跟踪决定上下文边界。源码测试github_actions_extractor_ignores_run_outside_stepssrc/scan.rs#L5930-L5941验证了文档根部的run: rm -rf /不被提取只有 steps 内的run: echo hello生效。5.2 显式跳过的块以下 step 属性明确跳过Property原因env:环境变量数据非命令with:Action 输入数据非命令secrets:敏感数据绝不提取steps: - run: actual-command # Extracted env: FOO: rm -rf / # Skipped (data context) with: script: rm -rf / # Skipped (action input)测试用例env:块值不提取with:块值不提取secrets:块值不提取。5.3 源码实现机制上下文跟踪是 extract_github_actions_workflow_from_str 的核心状态机涉及三个状态变量steps_indent: Optionusize进入steps:键时记录其缩进作为上下文边界skip_indent: Optionusize遇到env:/with:/secrets:时记录缩进该缩进内的所有行包括嵌套的run:键全部跳过行级处理跳过空行与注释行 → 处理 skip 块 → 判断是否退出 steps 上下文 → 检测新的steps:键 → 检测列表项 → 检测env/with/secrets→ 检测run。关键测试github_actions_extractor_does_not_extract_env_or_with_fieldssrc/scan.rs#L5247-L5267name、env.X、with.args中都含rm -rf /但只有run: echo hello是执行上下文提取结果为空github_actions_extractor_ignores_run_key_inside_env_blocksrc/scan.rs#L5270-L5286env.run: rm -rf /这种藏在 env 里的 run 键也不会被误提取——这是skip_indent机制的典型防护场景github_actions_no_false_positive_on_env/on_with/on_namesrc/scan.rs#L7476-L7519分别验证env.CLEANUP、with.command、name:中出现的危险字符串不会触发误报。六、不提取的行哪些内容永不进入扫描6.1 Step 属性非命令Property示例原因name:name: rm -rf /显示名称非命令uses:uses: actions/checkoutv4Action 引用id:id: build-stepStep 标识符if:if: github.event_name push条件表达式working-directory:working-directory: ./app目录路径continue-on-error:continue-on-error: true布尔标志timeout-minutes:timeout-minutes: 10数值设置6.2 Workflow 元数据Section原因name:workflow显示名称on:触发器配置permissions:权限设置concurrency:并发设置defaults:默认设置env:workflow/job 级别环境变量这些内容属于数据上下文而非执行上下文跳过它们正是 extractor 区别于 grep 的核心价值。七、Shell Override 处理v1 的 Shell 无关行为GitHub Actions 支持shell:指定命令解释器steps: - run: Write-Host Hello shell: pwsh - run: python -c print(hi) shell: pythonv1 当前行为shell:属性不被解析所有run:块一律按 shellbash命令处理。影响PowerShell 脚本被当作 bash 提取Python 内联脚本被当作 shell 命令提取非 bash shell 可能产生误报。测试用例run:shell: bash正常提取run:shell: pwsh仍被提取v1 限制run:shell: python仍被提取v1 限制。这一点在源码的方言dialect映射测试中得到印证github_actions.steps.run被映射为ShellDialect::Unknown注释明确写道 generic/unsupported extractor must not guess通用/不支持的提取器不得猜测方言见 src/scan.rs#L5179-L5185。也就是说提取器保持 shell 无关不会对run:内容所属方言做任何假设这正是 v1 规范Shell Agnostic设计的代码级体现。八、v1 已知限制Unsupported Constructs以下为 v1 已知限制可能产生非预期行为应在未来版本解决。8.1 可复用工作流workflow_call不支持# .github/workflows/reusable.yml on: workflow_call: inputs: command: type: string jobs: build: steps: - run: ${{ inputs.command }}当前行为模板表达式${{ }}按字面提取。影响来自 workflow 输入的动态命令值不会被展开。8.2 复合 ActionsComposite Actions不支持# action.yml (composite action) runs: using: composite steps: - run: dangerous-command shell: bash当前行为名为action.yml的文件不通过文件检测。影响复合 action 中的命令不被扫描。8.3 表达式替换Expression Substitution不支持steps: - run: ${{ github.event.inputs.command }} - run: echo ${{ secrets.SCRIPT }}当前行为表达式按字面${{ ... }}提取。影响来自 inputs、secrets 或 context 的命令不会被展开。8.4 矩阵策略Matrix Strategies部分支持jobs: test: strategy: matrix: cmd: [cmd1, cmd2] steps: - run: ${{ matrix.cmd }}当前行为run: ${{ matrix.cmd }}按字面提取。影响矩阵展开后的命令不会逐个扫描。8.5 Shell Override 解释不支持steps: - run: | import os os.remove(/tmp/file) shell: python当前行为Python 代码被提取并按 bash 扫描。影响Python 特有模式如os.remove不会被 bash 模式匹配。8.6 条件 Run 块部分支持steps: - run: dangerous-command if: github.ref refs/heads/main当前行为无论if:条件如何命令都会被提取。影响条件跳过conditionally-skipped的命令仍会被标记。8.7 多 Job 工作流支持jobs: job1: steps: - run: cmd1 job2: steps: - run: cmd2当前行为所有 job 的 steps 都会被扫描。说明这是 v1 中已正确工作的部分。源码测试github_actions_extractor_extracts_run_steps_only与github_actions_extractor_handles_block_scalar_and_skips_comments中的多 job 结构均验证了这一点。九、实现细节源码级9.1 Steps 上下文跟踪规范中的伪代码流程1. Find steps: key at job level 2. Track indentation of steps: line 3. Within steps block: - Look for list items (- ) at appropriate indent - Check for run: key in each item - Skip env:, with:, secrets: sub-blocks 4. Exit steps context when indentation decreases对应源码 src/scan.rs#L1854-L1983 的实际实现要点steps_indent只在steps:键值后为空或注释时置位src/scan.rs#L1895-L1903即steps: [...]内联序列形式不被跟踪列表项可以出现在与steps:键相同的缩进层级indent steps trimmed_start.starts_with(-)判定见 src/scan.rs#L1910块标量|/通过block_start_line line_no 1记录起始行收集所有缩进大于run:键缩进的行交给extract_shell_script_with_offset_and_id处理src/scan.rs#L1935-L1968普通 run 值先经unquote_yaml_scalar去引号再进入同一 shell 提取通道src/scan.rs#L1970-L1977。9.2 块标量提取对于run: |或run: 记录run:行的缩进层级收集后续所有缩进更大的行传给 shell 脚本提取器shell 提取器负责注释剥离等处理。空行在块内被保留block.push(\n)缩进回到run:键层级即结束块src/scan.rs#L1940-L1956。9.3 YAML 标量去引号unquote_yaml_scalarsrc/scan.rs#L1985-L2023完整实现了规范中的三种输入处理输入处理后echo \hello\echo helloecho quotedecho quotedecho plainecho plain十、测试清单验证实现的完整核对表规范提供了一份完整的验证清单可直接用于实现回归测试路径检测.github/workflows/ci.yml匹配.github/workflows/ci.yaml匹配.github/workflows/sub/ci.yml匹配嵌套.GITHUB/WORKFLOWS/CI.YML匹配大小写不敏感.github/workflows/ci.json不匹配workflows/ci.yml不匹配缺 .github.github/workflow/ci.yml不匹配单数action.yml不匹配单行 Runrun: echo hello提取命令run: quoted command处理双引号run: single quoted处理单引号- run: cmd列表项可用run 值后的行内注释如有块标量 Runrun: |字面块提取所有行run: 折叠块被识别多行内容正确拼接块在正确缩进处结束上下文感知steps:内的run:被提取steps:外的run:不提取多 job 的 steps 全部扫描env:块值跳过with:块值跳过secrets:块值跳过非 Run 属性name: cmd不提取uses: actionv1不提取if: condition不提取id: step-id不提取引用处理含转义序列的双引号含转义的单引号无引号值可用边界情况空 workflow 无命令返回仅含uses:步骤的 workflow 无命令返回关键字过滤限制提取范围块标量中的注释被处理上述清单几乎每一项都能在 src/scan.rs 的测试模块中找到对应断言。此外提取器还通过extractors_handle_crlf_line_endings_identically_to_lf测试src/scan.rs#L5189-L5244验证了 CRLF 与 LF 行尾的提取结果完全一致——Windows 上编辑的 workflow 文件\r\n不会泄漏多余的\r到匹配命令中这对跨平台 CI 防护至关重要。十一、实战落地用 dcg scan 扫描 GitHub Actions workflow11.1 基本命令# 扫描指定路径目录递归展开包括 .github/workflows/ dcg scan --paths scripts/ .github/workflows/ # 扫描暂存区文件git index适合 pre-commit dcg scan --staged # 扫描 git diff 范围 dcg scan --git-diff HEAD~3..HEAD # 扫描时临时启用额外 packs dcg scan --paths scripts/ --with-packs careful_company_running_windows11.2 输出与策略控制--format pretty|json|markdown|sarifdcg scan是唯一能输出真实 SARIF 2.1.0 报告的命令README.md#L744--fail-on error仅当命中灾难级规则error 级时才非零退出适合保守起步--max-file-size BYTES、--max-findings N控制扫描资源--exclude GLOB/--include GLOB细粒度文件过滤。推荐的分阶段落地策略来自 README.md#L1706-L1742# Week 1-2: 仅对灾难级规则失败 dcg scan --staged --fail-on error配合.dcg/hooks.toml保守配置[scan] fail_on error # 仅对高置信灾难规则失败 format pretty # 人类可读输出 redact quoted # 隐藏敏感字符串 truncate 120 # 缩短长命令 [scan.paths] include [ .github/workflows/**, # 先从 CI 配置开始 Dockerfile, # 容器构建 Makefile, # 构建脚本 ] exclude [ target/**, node_modules/**, vendor/**, ]11.3 输出解读.github/workflows/ci.yml:42:5: [ERROR] core.git:reset-hard Command: git reset --hard HEAD Reason: git reset --hard destroys uncommitted changes Suggestion: Consider using git stash first to save changes.File:Line:Col直接指向 workflow 文件中run:的实际位置——这正是 extractor 逐行跟踪行号line_no并在块标量场景记录block_start_line的意义所在。11.4 Pre-commit 集成# 一键安装创建 .git/hooks/pre-commit内部执行 dcg scan --staged dcg scan install-pre-commit # 卸载仅移除带 dcg sentinel 标记的钩子 dcg scan uninstall-pre-commit也可以手动在 hook 中写入dcg scan --staged --fail-on error。十二、版本历史与相关任务VersionDateChanges1.02026-01-16Initial specification本规范属于 Scan Mode Extractors for CI/DevOps Files 系列与 Dockerfile 提取器 v1 规范 同批制定。相关任务包括GitHub Actions workflow 提取器的实现与单元测试。规范中明确该提取器当前状态为 first-pass第一轮实现shell:解析、模板表达式展开、复合 action 支持等均列入未来工作范畴。结语GitHub Actions workflow 提取器 v1 的核心设计哲学可以概括为三句话只提取steps:内run:的可执行上下文对env:/with:/secrets:等数据上下文坚决沉默对无法确定语义的shell:覆盖、模板表达式保持保守不猜。这套prefer silence over false positives的设计配合dcg scan的 pre-commit 与 CI 集成能力让团队能够以极低的误报成本将 CI 配置纳入危险命令防护体系。本文所引用的 规范文档、提取器实现 与 README 扫描章节 可作为深入研究的起点。【免费下载链接】destructive_command_guardThe Destructive Command Guard (dcg) is for blocking dangerous git and shell commands from being executed by agents.项目地址: https://gitcode.com/GitHub_Trending/de/destructive_command_guard创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表