ARTICLE DETAIL

资讯详情

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

Destructive Command Guard Makefile 提取器 v1 规范解读:从 recipe 行提取可执行命令的安全扫描机制

Destructive Command Guard Makefile 提取器 v1 规范解读:从 recipe 行提取可执行命令的安全扫描机制 Destructive Command Guard Makefile 提取器 v1 规范解读从 recipe 行提取可执行命令的安全扫描机制【免费下载链接】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的dcg scan模式内置了一整套面向 CI/DevOps 文件的命令提取器其中 Makefile 提取器负责从构建脚本的 recipe 行制表符开头的 shell 命令中提取可执行命令并送入危险命令匹配引擎。本文以仓库中的 v1 语法覆盖规范为骨架结合 src/scan.rs 的实际实现与单元测试完整讲解该提取器的文件识别规则、recipe 解析算法、变量与续行的处理边界以及全部已知限制帮助你在理解扫描行为的同时也能将其作为实现与测试清单直接复用。概述一个保守优先的 recipe 行提取器Makefile 提取器的目标非常聚焦只分析 Makefile 中以TAB 字符开头的 recipe 行即目标规则下真正会被 shell 执行的命令并把它们提取出来交给后续的危险命令匹配。其设计原则是宁缺毋滥prefer silence over false positives——在无法确定语义时宁可漏报也不制造误报。提取器 ID 为makefile.recipe— recipe 行命令该 ID 会写入每个提取结果供扫描结果消费方评估器、SARIF 输出、统计数据等区分命令来源。注意它与 Dockerfile 提取器dockerfile.run/dockerfile.run.exec见 dockerfile-extractor-v1-spec.md走的是同一套提取框架。文件检测只认精确文件名匹配规则Pattern示例说明MakefileMakefile大小写不敏感makefilemakefile小写变体MAKEFILEMAKEFILE全大写变体源码实现位于 src/scan.rs 的is_makefile_path()fn is_makefile_path(path: Path) - bool { let file_name path.file_name().and_then(std::ffi::OsStr::to_str); file_name.is_some_and(|name| name.eq_ignore_ascii_case(makefile)) }它只取file_name不含目录与字符串makefile做忽略大小写的精确比较因此任何带后缀或扩展名的变体都会被排除。不匹配的文件名Pattern原因Makefile.backup不是精确文件名build.mk.mkinclude 文件暂不支持GNUmakefileGNU 变体暂不支持makefile.inAutoconf 模板不支持Makefile.amAutomake 文件不支持单元测试makefile_path_detectionsrc/scan.rs验证了Makefile/makefile命中而Makefile.backup、build.mk、build.sh全部排除。写 Makefile 时请注意Makefile.bak、Makefile.old这类备份文件不会被扫描。在dcg scan的文件分派循环src/scan.rs中is_makefile_path(file)与 shell、Dockerfile、GitHub Actions、GitLab CI、Azure Pipelines、CircleCI、package.json、Terraform、docker-compose、PowerShell、batch 等检测并列若文件不属于任何已知类型则计入files_skipped并按NoExtractor原因记录跳过不会报错。Makefile 语法速览TAB 是 recipe 行的唯一判据# Comment VARIABLE value # 变量赋值 target: prerequisites # 规则定义 command1 # recipe 行以 TAB 开头 command2 # 另一条 recipe 行关键洞察只有以字面 TAB 字符开头的行才是会执行 shell 命令的 recipe 行。空格开头的行在 Make 中不是合法 recipe会触发 missing separator 错误因此提取器以starts_with(\t)作为唯一入口判据src/scan.rs。支持的 Recipe 格式1. 简单 recipe 行语法TAB shell 命令all: echo Building... rm -rf ./build mkdir build行为以\t开头的行是 recipe 行命令内容为 TAB 之后的全部文本注释处理委托给底层 shell 脚本提取器见下文。测试用例已在 src/scan.rs 的makefile_extractor_extracts_recipe_lines_only中验证\tgit status→ 提取git status\trm -rf ./build→ 提取rm -rf ./build同一目标内多条 recipe 行被逐一提取且行号按原始文件计算该测试断言git status在第 4 行、rm -rf ./build在第 6 行中间的全注释行# rm -rf /被正确跳过。2. recipe 块提取语法连续 TAB 前缀行构成一个 recipe 块clean: rm -rf build rm -rf dist echo Clean complete test: cargo test行为连续的 TAB 行归组为一个块每个目标的 recipe 作为一个 shell 脚本块整体处理空行在续行场景下不打断块取决于是否处于反斜杠续行中非 TAB 前缀行除续行外结束当前块。实现上src/scan.rs外层while扫描 TAB 起始行内层while用prev_continues状态判断是否继续吸收后续行let is_recipe_line line.starts_with(\t); if !is_recipe_line !prev_continues { break; }即当前行没有 TAB 且上一行没有以反斜杠续行时块立即结束——这正是目标边界test:这类非 TAB 行结束块的机制。3. 反斜杠续行语法recipe 行以\结尾则延续到下一行build: gcc -Wall -Werror \ -O2 -g \ -o main main.c deploy: rsync -avz \ --exclude node_modules \ --exclude .git \ ./ server:/app/行为以\结尾的行继续到下一行续行不要求 TAB 前缀这是 Make 的合法语法各段按续行规则拼接用于命令提取反斜杠与换行被替换为拼接。源码中通过line.trim_end().ends_with(\\)设置prev_continuessrc/scan.rs因此续行行即使没有 TAB 也能被吸收进块。测试覆盖makefile_extractor_handles_backslash_continuationssrc/scan.rsgit log \--oneline拼接为git log --oneline行号仍指向第 2 行git所在行makefile_continuation_with_different_indentationsrc/scan.rs续行使用不同缩进仍正确拼接gcc ... -O2 ...;makefile_continuation_multiple_linessrc/scan.rs4 行连续续行make build test deploy正确合并引号内嵌套续行也被支持。4. recipe 中的 shell 注释语法recipe 行中的#开始 shell 注释clean: rm -rf ./build # 删除构建目录 # 整行都是注释 git status # 检查状态行为注释处理委托给底层 shell 脚本提取器引号外的#开始注释模式匹配前剥离注释部分纯注释行不产生任何提取。测试用例行内注释剥离rm -rf /tmp # cleanup→rm -rf /tmp整行注释不提取makefile_no_false_positive_on_commentsrc/scan.rs引号内#保留echo # header→echo # header。Recipe 行与非 Recipe 行被提取的行recipe 行以 TAB 开头且包含 shell 命令的行。Make 的三种 recipe 前缀均会被剥离前缀后提取命令all: echo Building # 前缀仍提取为 echo Building -rm -rf build # - 前缀仍提取为 rm -rf build make subdir # 前缀仍提取为 make subdir前缀含义提取行为无普通原样提取静默不回显提取之后的命令-忽略错误提取-之后的命令即使 dry-run 也执行提取之后的命令注意前缀处理委托给 shell 脚本提取器。单元测试makefile_recipe_prefixessrc/scan.rs用echo silent、-rm -f maybe_missing、make recursive验证三条均被提取。从源码看shell 提取器对/-/前缀不特殊识别而是直接按普通命令词法解析——测试断言的是命令内容包含echo/rm前缀本身不会污染匹配结果。不被提取的行行类型示例原因变量赋值CC gcc非可执行上下文目标定义all: build test规则声明非命令指令.PHONY: all cleanMake 指令非命令includeinclude config.mkMake 指令注释# Build configuration不可执行条件ifeq ($(DEBUG),1)Make 语法非 shell空行空无内容可提取makefile_no_false_positive_on_variablesrc/scan.rs专门验证CLEAN_CMD rm -rf build即使包含rm关键字也不会被提取因为变量赋值行没有 TAB 前缀根本不会进入 recipe 处理路径。变量处理v1 不做任何替换Make 变量可以出现在 recipe 中CLEAN_CMD rm -rf ./build clean: $(CLEAN_CMD) ${CLEAN_CMD} $$HOME/cleanup.sh # shell 变量双 $$v1 提取器不展开任何变量。输入提取结果说明$(CC) -o main main.c$(CC) -o main main.c字面$(CC)${RM} -rf build${RM} -rf build字面${RM}$$HOME/script.sh$$HOME/script.sh字面$$HOME$(shell rm -rf /)$(shell rm -rf /)字面保留不执行影响匹配模式必须针对字面变量语法隐藏在变量背后的危险命令不会触发匹配这是 v1 已知限制保守策略的代价。测试用例$(VAR)、${VAR}、$$var均字面保留$(shell cmd)函数不被求值。这也是模式库设计时必须考虑的约束例如git reset --hard若写成$(GIT) reset --hard$(GIT)字面量会阻断关键字匹配。v1 不支持的结构已知限制以下均为 v1 的明确边界可能产生预期外行为需在未来版本解决1. 变量展开不支持DANGER rm -rf / clean: $(DANGER)当前行为提取字面$(DANGER)而非展开值。影响隐藏在变量后的命令不会触发模式匹配。2. Make 函数不支持FILES : $(wildcard *.c) clean: $(foreach f,$(FILES),rm $(f);) $(shell dangerous-command)当前行为函数被字面提取不求值。影响$(shell ...)内部的命令不会被当作 shell 命令扫描。3. 条件指令不支持ifeq ($(DEBUG),1) clean: rm -rf ./debug else clean: rm -rf ./release endif当前行为ifeq/else/endif不作为指令解析条件块内的 recipe 行仍会被提取。影响两个分支的 recipe 可能都被提取与运行时实际条件无关。4. 模式规则部分支持%.o: %.c $(CC) -c $ -o $当前行为recipe 行按字面$、$提取。影响自动变量$、$、$^等不展开。5. include 指令不支持include common.mk -include optional.mk当前行为不追踪、不扫描 include 文件。影响include 文件中的命令不会被分析。6. 多行变量定义不支持define SCRIPT rm -rf /tmp git reset --hard endef clean: $(SCRIPT)当前行为define/endef块不解析$(SCRIPT)被字面提取。影响多行变量内容不被扫描。7. 替代文件名不支持文件名状态GNUmakefile不匹配未来工作*.mk不匹配include 文件makefile.in不匹配autoconfMakefile.am不匹配automake实现细节块处理与 shell 提取器复用recipe 块处理流程规范定义的五步流程在 src/scan.rs 中逐条对应扫描以 TAB 开头的行把连续 recipe 行收集成一个块内层循环 prev_continues状态处理块内的反斜杠续行trim_end().ends_with(\\)把块交给 shell 脚本提取器做命令解析以makefile.recipe提取器 ID 返回提取结果。复用 shell 脚本提取器提取器委派给extract_shell_script_with_offset_and_id()src/scan.rs获得以下能力注释剥离引号外的#引号感知解析反斜杠续行处理关键字过滤仅提取包含启用关键字的命令。这在.sh文件与 Makefile recipe 之间保证了行为一致性同一套注释、引号、续行语义避免同一命令在两种载体中产生不同提取结果。该函数还负责行号校正与extractor ID 覆写let offset start_line.saturating_sub(1); for cmd in mut extracted { cmd.line cmd.line.saturating_add(offset); cmd.extractor_id extractor_id.to_string(); }由于块被拼接成多行字符串后传给 shell 提取器shell 提取器计算的是块内相对行号这里用start_line - 1的偏移把行号还原到原始 Makefile 中的真实行号保证扫描报告定位准确。特别地这个偏移使用了saturating_sub/saturating_add从源码结构看可以推断是为了避免极端情况下如起始行号为 0/1的整数下溢。关键字过滤与调度extract_makefile_from_str(file, content, enabled_keywords)的第三个参数enabled_keywords是关键字的白名单shell 提取器在extract_shell_command_linesrc/scan.rs中通过contains_any_keyword过滤只有包含启用关键字的行才会产出结果。因此在dcg scan的配置下启用rm、git等危险关键字后Makefile 中不包含这些关键字的普通构建命令如echo、mkdir不会被提取上报从而大幅降低噪音。测试清单以下清单可直接作为实现验证或回归测试的依据对应实现见 src/scan.rs路径检测Makefile匹配makefile匹配大小写不敏感MAKEFILE匹配大小写不敏感Makefile.backup不匹配build.mk不匹配GNUmakefile不匹配Recipe 提取TAB 前缀行提取命令非 TAB 行不提取连续 recipe 行构成块目标定义行不提取变量赋值不提取反斜杠续行两行续行正确拼接多行续行正确拼接无 TAB 的续行行有效recipe 行末尾反斜杠触发续行注释带# comment的 recipe 行剥离注释整行注释 recipe 不提取引号内#保留变量$(VAR)语法字面保留${VAR}语法字面保留$$varshell 变量保留$(shell ...)不求值Recipe 前缀echo hello提取带或不带-rm -rf x提取带或不带-make sub提取带或不带边界情况空 Makefile 不返回命令仅含变量的文件不返回命令仅含注释的文件不返回命令关键字过滤限制提取范围版本历史版本日期变更1.02026-01-16初始规范相关任务阻塞git_safety_guard-dclh实现 Makefile recipe 提取器阻塞git_safety_guard-5rbb.7Makefile 提取器单元测试父任务git_safety_guard-5rbbCI/DevOps 文件的扫描模式提取器总结Makefile 提取器 v1 是一套刻意保守的实现TAB 判据保证只提取真正的可执行 recipeshell 提取器复用保证与.sh解析语义一致行号偏移校正保证报告可定位关键字过滤保证低噪音。它的价值边界同样清晰——变量、函数、条件、include、多行 define 与替代文件名均不处理这意味着依赖变量间接调用危险命令的 Makefile 在当前版本不会被检出。如果你负责在 CI 流水线中为 agent 执行的构建脚本做安全扫描理解这层提取层的行为边界是正确解读dcg scan结果的第一步先确认命令是否被提取再谈匹配是否命中。【免费下载链接】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),仅供参考
返回列表