openlibing-security-compilation-options-action
安全编译选项扫描插件 - 检测 ELF 文件安全编译选项开启率。
功能
检测构建产物(ELF 二进制文件)的安全编译选项(默认 14 项;其中 stackClash/fstackCheck 依赖编译器记录段 .GCC.command.line/.llvmcmd 识别,构建未开启 -frecord-gcc-switches/-frecord-command-line 或节被 strip 时返回 N/A):
| 选项 key | 说明 |
|---|---|
| bindNow | BIND_NOW 立即绑定(全 RELRO) |
| nx | 堆栈不可执行 |
| pic | 地址无关代码 |
| pie | 位置无关可执行文件 |
| relro | GOT 表保护 |
| rpath | 动态库搜索路径(YES 表示存在不安全搜索路径) |
| sp | 栈保护 / Stack Protector |
| strip | 符号表剥离 |
| fortify | _FORTIFY_SOURCE 宏(编译器生成的 _chk 函数) |
| fvisibility | -fvisibility=hidden 符号可见性隐藏 |
| ftrapv | -ftrapv 有符号整数运算溢出陷阱 |
| stackClash | -fstack-clash-protection 栈冲突保护(依赖编译器记录段,未开启记录或无记录段返回 N/A) |
| fstackCheck | -fstack-check 栈空间越界检查(依赖编译器记录段,未开启记录或无记录段返回 N/A) |
| aslr | ASLR 支持(依赖 PIE) |
为什么
stackClash/fstackCheck可能同时为 N/A:这两个是编译期代码生成选项,产物没有符号级标记,需读取编译器写入的记录段(GCC-frecord-gcc-switches生成.GCC.command.line、Clang-frecord-command-line生成.llvmcmd)才能确认。若构建未开启这两个记录开关,或产物经strip --strip-all/--strip-debug删除了该非分配节,扫描器无从判断,这两项返回 N/A(不计入开启率分母)。若两者同时为 N/A,请检查构建是否添加-frecord-gcc-switches/-frecord-command-line。
按文件内容魔数自动识别并解压包格式(识别与文件名后缀无关):zip 类(.jar/.war/.ear/.whl/.zip)、ar 类(.a 静态库、.deb)、tar 类(.tar/.tar.gz/.tar.bz2/.tar.xz);.run 自解压包按后缀识别后探测内部压缩载荷。
排除范围:.dbg 后缀调试符号文件、编译器探测桩文件(CMakeCCompilerId/CMakeCXXCompilerId/feature_tests,按去后缀基名匹配)、版本控制与依赖缓存目录(.git/.svn/node_modules/__pycache__)。构建中间目录(CMakeFiles 等)不再跳过,其内 ELF 产物按内容识别纳入扫描。
使用方式
场景一:PR 流水线(构建产物后直接扫描,obs-upload 上传)
在 PR 触发的流水线中,构建产物后立即扫描,配合 obs-upload 上传产物到 OBS 用于溯源:
name: build
on:
workflow_dispatch:
pull_request_target: ## 使用 pull_request_target 确保 PR 流水线始终读取默认分支(master)的最新配置文件
branches:
- "*"
pull_request_comment: ## PR 评论事件:当用户在 PR 中发表评论时触发
types: [created]
comments: ["/build-and-scan"]
branches:
- "*"
schedule:
- cron: "0 0 0 * * ?"
permissions:
repository: read
id-token: write
jobs:
build:
name: build
runs-on: ["codearts-hosted", "ubuntu-latest", "x64", "large"]
steps:
- name: Checkout
uses: checkout
with:
ref: ${{ atomgit.event.pull_request.merge_commit_sha || atomgit.event.pull_request.head.sha }}
# 请替换为实际的构建脚本
- name: build
id: build
run: |
echo "===== CMake 构建打包 ====="
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
# 查找主构建产物
ARTIFACT=$(find . -maxdepth 2 -type f \( -name "*.tar.gz" -o -name "*.zip" -o -perm -111 \) | head -1)
if [ -z "$ARTIFACT" ]; then
echo "ERROR: No build artifact found"
exit 1
fi
echo "artifact-path=${ARTIFACT}" >> "$ATOMGIT_OUTPUT"
echo "Found artifact: ${ARTIFACT}"
- name: upload-artifact-to-obs
id: upload-obs
uses: obs-upload
with:
# 请使用实际的端点和 OBS 桶
endpoint: "obs.cn-southwest-2.myhuaweicloud.com"
bucket: "my-package"
access-key: "${{ secrets.OBS_AK }}"
secret-key: "${{ secrets.OBS_SK }}"
artifact-path: "${{ steps.build.outputs.artifact-path }}"
object-prefix: "${{ atomgit.repository }}/${{ atomgit.ref_name }}/${{ atomgit.run_id }}/"
- name: security-compilation-options-action
# 当前插件已上传至开源仓,可直接调用
uses: openlibing/security-compilation-options-action@v1.0.2
with:
artifact-path: "${{ steps.build.outputs.artifact-path }}"
artifact-download-url: "${{ steps.upload-obs.outputs.primary-url }}"
# 可选:只扫描部分项,不传 scan-options 默认扫描 14 项(stackClash/fstackCheck 未开记录时返回 N/A)
# scan-options: "bindNow,nx,pic,pie,relro,rpath,sp,strip"
场景二:Nightly 流水线(构建 + upload-artifact 暂存 + 扫描)
在 Nightly 定时流水线中,构建产物后使用 GitCode 官方 upload-artifact 插件暂存,无需自管 OBS。retention-days 控制产物保留天数,到期自动清理:
name: build-and-scan-nightly
on:
workflow_dispatch:
schedule:
- cron: "0 0 0 * * ?" # 每天北京时间 00:00 执行
permissions:
repository: read
id-token: write
jobs:
build:
name: nightly-build-and-scan
runs-on: ["codearts-hosted", "ubuntu-latest", "x64", "large"]
steps:
- name: Checkout
uses: checkout
# 请替换为实际的构建脚本
- name: build
id: build
run: |
echo "===== CMake 构建打包 ====="
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
# 查找主构建产物
ARTIFACT=$(find . -maxdepth 2 -type f \( -name "*.tar.gz" -o -name "*.zip" -o -perm -111 \) | head -1)
if [ -z "$ARTIFACT" ]; then
echo "ERROR: No build artifact found"
exit 1
fi
echo "artifact-path=${ARTIFACT}" >> "$ATOMGIT_OUTPUT"
echo "Found artifact: ${ARTIFACT}"
# 使用官方 upload-artifact 暂存产物,无需自管 OBS 桶
# retention-days 控制产物保留天数,到期自动清理
- name: upload-artifact
id: upload-artifact
uses: upload-artifact
with:
name: nightly-build-artifact
path: "${{ steps.build.outputs.artifact-path }}"
retention-days: 3
overwrite: true
if-no-files-found: error
# 扫描本地产物文件,artifact-download-url 使用 upload-artifact 输出的制品页面 URL
- name: security-compilation-options-scan
uses: openlibing/security-compilation-options-action@v1.0.2
with:
artifact-path: "${{ steps.build.outputs.artifact-path }}"
artifact-download-url: "${{ steps.upload-artifact.outputs.artifact-url }}"
场景三:Nightly 流水线(扫描指定 OBS 产物)
在 Nightly 定时流水线中,扫描预先存储在 OBS 上的产物包。artifact-path 支持多行指定多个文件,插件逐个扫描并独立上报:
name: security-compilation-options-scan-nightly
on:
workflow_dispatch:
schedule:
- cron: "0 0 0 * * ?"
permissions:
repository: read
id-token: write
jobs:
build:
name: scan-artifacts
runs-on: ["codearts-hosted", "ubuntu-latest", "x64", "large"]
steps:
- name: Checkout
uses: checkout
# ===== 批量下载两个架构的构建产物 =====
- name: download-artifacts
id: download-artifacts
uses: obs-download
with:
endpoint: "obs.cn-north-4.myhuaweicloud.com"
bucket: "my-package"
key: |
cache/master/package_aarch64.tar.gz
cache/master/package_x86_64.tar.gz
path: "./artifacts/"
file-concurrency: "2"
# ===== 扫描指定产物并上传结果 =====
# artifact-path 精确指定只扫描这两个包,避免误扫目录中其他产物
# artifact-download-url 以 / 结尾视为URL前缀,插件自动拼接产物文件名
- name: security-compilation-options-scan
uses: openlibing/security-compilation-options-action@v1.0.2
with:
artifact-path: |
./artifacts/package_aarch64.tar.gz
./artifacts/package_x86_64.tar.gz
触发方式
| 触发方式 | 配置关键字 | 适用场景 |
|---|---|---|
| 手动触发 | workflow_dispatch |
按需验证、首次接入验证 |
| 代码推送触发 | pull_request_target / pull_request_comment |
提PR/PR评论时自动检测(场景一) |
| 定时任务触发 | schedule.cron |
Nightly 流水线,每日定期检测跟踪趋势(场景二/三) |
输入参数
| 参数 | 必填 | 默认值 | 说明 |
|---|---|---|---|
| artifact-path | 是 | - | 构建产物文件路径,支持多行指定多个文件(每行一个路径)。推荐配合构建步骤的 step output 使用 |
| output | 否 | sec-option-result.json | 结果输出文件路径 |
| package-name | 否 | '' | 构建产物压缩包文件名。不传则自动从 artifact-path 推断 |
| artifact-download-url | 否 | '' | 构建产物下载链接。以 / 结尾视为URL前缀,插件自动拼接前缀+产物文件名作为每个包的下载链接(多产物包只需写一次前缀);不以 / 结尾视为完整URL。如未提供下载链接或下载链接已失效/需要认证信息,openlibing 平台将不支持产物下载 |
| scan-options | 否 | '' | 实际扫描项列表(逗号分隔),不传或为空表示扫描默认 14 项(stackClash/fstackCheck 构建未开启 -frecord-gcc-switches/-frecord-command-line 或无记录段时返回 N/A)。仅扫描的项会出现在 overviewData/fileDetails 的 options 字段中,未扫描项 key 缺失 |
| apig-app-key | 否 | '' | 已废弃:APIG 网关 AppKey(AK)。请使用新的 OIDC 认证方式(见下方「上传认证」),无需任何凭证 |
| apig-app-secret | 否 | '' | 已废弃:APIG 网关 AppSecret(SK),与 apig-app-key 配套。请使用新的 OIDC 认证方式,无需任何凭证 |
scan-options 可选 key:bindNow,nx,pic,pie,relro,rpath,sp,strip,fortify,fvisibility,ftrapv,stackClash,fstackCheck,aslr
上传认证
上传功能默认开启,插件使用OIDC 联邦认证,无需任何凭证。已适配 OIDC 的 workflow 示例:
permissions:
id-token: write
输出
每个安全编译选项开启率选项输出 3 个值(仅 scan-options 指定的项或默认 14 项):
| 输出 | 说明 |
|---|---|
| {option}-total-files | 参与计算的文件数(排除 N/A) |
| {option}-yes-count | 满足数 |
| {option}-rate | 开启率(%) |
默认输出的安全编译选项包括:bindNow, nx, pic, pie, relro, rpath, sp, strip, fortify, fvisibility, ftrapv, stackClash, fstackCheck, aslr
概览输出(ATOMGIT_OUTPUT)
扫描完成后,插件会将各产物包在不同扫描项上的开启率通过 ATOMGIT_OUTPUT 输出,便于工作流页面直接查看概览(不含明细)。每个实际扫描项对应一个输出标识(中文名(英文名)),值为 包名 -> 开启率 的映射,开启率为带 % 的百分数或 N/A。输出标识固定为 中文名(英文名),引用时以此为准;内部键(scan-options 输入/排序用)与输出标识的对应关系见下表,仅输出实际扫描到的项。使用方式:${{ steps.<scan-id>.outputs['立即绑定(BIND_NOW)'] }}。
| 输出标识 | 值 |
|---|---|
立即绑定(BIND_NOW) |
{"app.jar":"100%"} |
堆栈不可执行(NX) |
{"app.jar":"95.83%"} |
栈冲突保护(Fstackclash) |
{"app.jar":"N/A"} |
各扫描项的内部键(scan-options 输入/排序用)与输出标识(引用用)对应关系:
| 内部键 | 输出标识(引用用) |
|---|---|
bindNow |
立即绑定(BIND_NOW) |
nx |
堆栈不可执行(NX) |
pic |
地址无关(PIC) |
pie |
随机化(PIE) |
relro |
GOT表保护(RELRO) |
sp |
栈保护(SP) |
rpath |
禁选动态库搜索路径(NO Rpath) |
fortify |
缓冲区溢出保护(FS) |
ftrapv |
整数溢出检查(Ftrapv) |
strip |
删除符号表(Strip) |
aslr |
地址空间布局随机化(ASLR) |
fvisibility |
符号全局不可见(Fvisibility) |
fstackCheck |
栈检查(Fstackcheck) |
stackClash |
栈冲突保护(Fstackclash) |
多个产物包时,同一扫描项标识的值为各包开启率的映射,如 {"app.jar":"100%","lib.so":"80%"}。
平台详情链接输出
上报成功后,插件为每个产物包输出 1 条该产物包在 openLiBing 平台的详情页跳转链接(每包固定 1 行,多产物包按包分别输出);运行日志(core.info)同时打印实际链接便于查看。
输出标识为扫描结果(app.jar)url,示例:
扫描结果(app.jar)url=https://www.openlibing.com/apps/securityOptions/package-detail?recordId=2103041785973575680&projectId=3
未录入 openLiBing 平台的代码仓(repo_info 无匹配)时不输出链接,输出提示行(文案由服务端返回,插件不硬编码):
扫描结果(app.jar)=当前仓未录入openLiBing平台,无法生成跳转链接,请先联系SRE完成代码仓录入。
说明
- 概览输出通过
ATOMGIT_OUTPUT写入。本插件为 Node 自定义 action,写入ATOMGIT_OUTPUT的输出无需在 action.yml 中声明outputs:即可被${{ steps.<scan-id>.outputs.<...> }}引用(中文键使用中括号字符串,如${{ steps.scan.outputs['立即绑定(BIND_NOW)'] }});平台详情链接输出的键同样无需声明即可引用。- 概览值为供工作流页面展示的可读字符串,开启率带
%或为N/A;而「输出」节中core.setOutput输出为纯数值(如{option}-rate=95.83),同名项单位同为%。两者不混用:编程取用建议用core.setOutput的纯数值键,概览值仅作展示。- 实际链接由服务端拼接,同一代码仓关联多个项目时,固定输出查询第一个项目对应的 1 条链接(不输出多链接)。
- 上报失败或链接构建异常时不输出链接与提示;写入失败时跳过输出,不影响上报结果。
依赖
- pyelftools - ELF 文件解析(Python 包,插件自动安装,无需手动配置)