awesome-copilot 的 GitHub Actions Expert 智能体实战指南:构建安全加固的 CI/CD 工作流
GitHub Actions Expert 是 awesome-copilot 仓库(社区贡献的 GitHub Copilot 指令、智能体、技能与配置集合)中面向 CI/CD 的专业智能体定义,其定位是帮助团队设计安全优先、资源高效、运行可靠的 GitHub Actions 工作流,重点覆盖 Action 提交 SHA 固定(pinning)、OIDC 免密钥认证、permissions 最小权限与供应链安全。读完本文你将掌握一整套可直接落地的 GitHub Actions 安全加固方法论,并了解在 Copilot 生态中如何通过该智能体(agents/github-actions-expert.agent.md)与其配套的 github-actions-hardening、github-actions-efficiency 技能组合实现“从创建到审计再到优化”的闭环。
智能体定位与能力边界
GitHub Actions Expert 是一个通过 YAML frontmatter 声明元数据的自定义智能体,核心描述为"专注安全 CI/CD 工作流、Action 固定、OIDC 认证、最小权限与供应链安全的 GitHub Actions 专家"。
从定义文件头可以看出其被授予的工具能力:
name: 'GitHub Actions Expert'
description: 'GitHub Actions specialist focused on secure CI/CD workflows, action pinning, OIDC authentication, permissions least privilege, and supply-chain security'
tools: ['github/*', 'search/codebase', 'edit/editFiles', 'execute/runInTerminal', 'read/readFile', 'search/fileSearch']
github/*:读写仓库工作流、创建/审阅 PR 等 GitHub 域操作;search/codebase与search/fileSearch:定位.github/workflows/*.yml及各文件依赖关系;edit/editFiles、execute/runInTerminal:产出并验证工作流修改;read/readFile:读取工作流、官方文档与仓库上下文。
其使命一句话概括:设计安全优先、资源高效、可靠自动化的工作流,每条工作流都应遵循最小权限、不可变 Action 引用并集成全面安全扫描。该智能体在设计上假设可读取/搜索整个代码库、可执行终端命令进行本地校验(如 actionlint),这与 docs/README.agents.md 中"通过 VS Code Chat 界面或 CCA 分配使用自定义智能体"的通用激活方式一致。
仓库中的配套资源
在 skills/github-actions-hardening/SKILL.md 中存在一个面向"审计 .github/workflows/*.yml 安全风险"的姊妹技能,它专门推理通用 linter 发现不了的 Actions 威胁模型——不可信输入脚本注入、特权触发器执行 fork 代码、可变 Action 引用、令牌越权——可作为审查/加固时的方法论支撑;skills/github-actions-efficiency/SKILL.md 则关注如何削减 CI 分钟数与成本。若需要更全面的 CI/CD 最佳实践说明,可参考 instructions/github-actions-ci-cd-best-practices.instructions.md。
动工之前:澄清问题清单
智能体在创建或修改工作流前,会按三类主题澄清需求,避免"拍脑袋写 YAML":
| 分类 | 澄清要点 |
|---|---|
| 工作流目的与范围 | 工作流类型(CI、CD、安全扫描、发布管理);触发器(push、PR、schedule、手动)与目标分支;目标环境与云厂商;审批要求 |
| 安全与合规 | 安全扫描需求(SAST、依赖审查、容器扫描);合规约束(SOC2、HIPAA、PCI-DSS);密钥管理与 OIDC 可用性;供应链安全要求(SBOM、签名) |
| 性能 | 预期时长与缓存需求;自托管还是 GitHub 托管运行器;并发要求 |
这三类问题的答案直接决定后续每一条原则的落地方式——例如合规约束会推导出必须生成 SBOM 与签名,而并发要求则决定 concurrency 配置取向。
安全优先三支柱
1. 权限最小化:从 contents: read 开始
默认在 workflow 顶层只授予 contents: read,仅在确有需要的 job 上按需提升,且只授予完成任务的最小权限。
这一点与配套技能 permissions-and-tokens.md 的底层分析完全一致:每次工作流运行都会自动获得一个 GITHUB_TOKEN,其权限范围就是被攻破时的爆炸半径。如果一个工作流完全没有 permissions: 块,它会继承仓库/组织默认值——在较老或宽松的仓库中,该默认值可能是"对大多数 scope 可读/写"。一次注入命令或一个恶意依赖,就能以该令牌推送代码、发布 Release 或批准 PR。
因此推荐"顶层收紧、逐 job 放行"的模式:
# 顶层默认拒绝一切
permissions: {}
jobs:
build:
permissions:
contents: read # 该 job 仅需 checkout
runs-on: ubuntu-latest
steps: [ ... ]
comment:
permissions:
contents: read
pull-requests: write # 该 job 需要发评论,其余一律不给
runs-on: ubuntu-latest
steps: [ ... ]
常见的权限 scope 包括 contents、pull-requests、issues、actions、packages、id-token、deployments、checks、statuses,每个取值为 read、write 或 none。审查时需要标记的高风险项包括:缺失 permissions: 块、出现 permissions: write-all、job 中声明了从未使用的 write scope、把本应只出现在单个 job 的 write 放在了顶层。
2. Action 固定:用完整提交 SHA 替代可变引用
这是该智能体安全理念的核心,原文给出明确规则:
- 一律将 Action 固定到完整长度的提交 SHA,以保证安全性与不可变性,例如:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- 绝不使用可变引用,如
@main、@latest或仅主版本标签(如@v4)。原因是标签可被仓库所有者或攻击者静默移动到指向恶意提交,从而发起供应链攻击,在 CI/CD 管线中执行任意代码。 - 提交 SHA 不可变:一旦确定便无法更改或重定向,能对"到底运行什么代码"提供密码学级保证。
- 在 SHA 旁追加版本注释(如
# v4.3.1),方便人类快速识别固定版本。 - 该规则适用于所有 Action,包括第一方
actions/*;尤其要覆盖你无法控制标签变动的第三方 Action。 - 使用 Dependabot 或 Renovate 自动化 SHA 更新。
skills/github-actions-hardening/references/supply-chain.md 将这条原则上升为分级规则:第三方 Action(任何非 actions/*、github/* 的发布者)必须固定 SHA,使用标签或分支记为 HIGH;@main/@master 无论发布者是谁都视为 HIGH(等同未版本化的"latest");第一方 actions/* 固定 SHA 是加固推荐(仅用标签时记为 LOW);始终保留 # vX.Y.Z 尾注供人与 Dependabot 识别预期版本。其背景陈述也值得注意:现实中已出现过流行 Action 的标签被重新指向窃取密钥代码的真实事件,这正是"禁止可变标签"并非教条而是教训的原因。
3. Secrets:只走环境变量,绝不落日志
- 仅通过环境变量访问 Secrets;
- 绝不记录日志或写入 outputs;
- 生产环境使用环境专属 Secrets;
- 优先 OIDC 而非长期凭证。
配套的 permissions-and-tokens.md 补充了更细的密钥卫生规则:只在需要的 job 中引用 Secrets;绝不在处理 Secrets 的步骤中 echo 或开启 set -x 追踪;不把 Secrets 传入未固定、未审查的第三方 Action;牢记 fork 的 pull_request 运行不会获得任何 Secrets——不要为了"修复"这一点而切换到 pull_request_target(详情见下文触发器一节)。
OIDC:用短期令牌替换长期云凭证
消除长期凭证是云上认证的第一选择:
- AWS:为 GitHub OIDC provider 配置带信任策略的 IAM Role;
- Azure:使用 workload identity federation(工作负载身份联合);
- GCP:使用 workload identity provider(工作负载身份提供方);
- 前提:工作流需要
id-token: write权限。
permissions-and-tokens.md 给出了原因与完整可运行示例:把静态云密钥(如 AWS_ACCESS_KEY_ID)存为仓库 Secrets,意味着泄露之后在手动轮换前永久有效;而 OIDC 下工作流请求的是云端信任的短期令牌,作用域限定到该仓库/分支,几分钟内过期。示例(AWS):
permissions:
id-token: write # 请求 OIDC 令牌所必需
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: aws-actions/configure-aws-credentials@<sha>
with:
role-to-assume: arn:aws:iam::123456789012:role/my-ci-role
aws-region: us-east-1
# 不再需要 AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY Secrets
同一模式适用于 Azure(azure/login)、GCP(google-github-actions/auth)、HashiCorp Vault 等。云端一侧应将信任策略作用域限定到具体仓库乃至具体分支/环境,避免 fork 或其他仓库冒用该角色。
并发控制:按场景选择取消策略
concurrency 是防止构建/部署相互踩踏的关键配置:
- 阻止并发部署:
cancel-in-progress: false(让正在进行的部署跑完,排队后续); - 取消过期的 PR 构建:
cancel-in-progress: true; - 用
concurrency.group控制并行执行粒度。
该要点在 skills/github-actions-efficiency/references/actions.md 中被列为"最常被忽略的低风险浪费来源"第二位——缺少 concurrency 取消机制意味着同一 PR 的多次 push 会各自排队全量执行,白烧 CI 分钟。
触发器与特权模型:理解信任边界
真正的 GitHub Actions 安全核心问题是:外部贡献者能否触发此工作流?若能,它拿到什么令牌与 Secrets? 不同触发器答案完全不同。以下信任矩阵来自 skills/github-actions-hardening/references/triggers-and-privilege.md:
| 触发器 | 谁能触发 | GITHUB_TOKEN |
可用 Secrets | 风险 |
|---|---|---|---|---|
push |
仓库协作者 | 读/写 | 是 | 低——受信作者 |
pull_request(同仓库分支) |
协作者 | 读/写 | 是 | 低 |
pull_request(来自 fork) |
任何人 | 只读 | 无 | 天然低——即使恶意代码也偷不到东西 |
pull_request_target |
任何有 fork 的人 | 读/写 | 是 | 高——在基仓库上下文中运行 |
workflow_run |
在另一工作流之后触发 | 读/写 | 是 | 高 |
issue_comment、issues |
任何人 | 读/写 | 是 | 高 |
其中的"陷阱"在于:fork 的 pull_request 之所以安全,是因为 GitHub 故意削减令牌并扣留 Secrets。维护者发现"fork PR 上 Secrets 不生效"后,常切换到 pull_request_target 以取回 Secrets——结果是把写令牌和全部 Secrets 交到了任意贡献者手中。
pull_request_target 之所以危险,在于它签出的是基仓库的工作流定义(fork 无法改变运行内容),却以完整权限运行。真正的高危场景是工作流随后显式签出fork 的代码并执行它:
# 危险——以写令牌 + Secrets 执行远程代码
on: pull_request_target
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<sha>
with:
ref: ${{ github.event.pull_request.head.sha }} # fork 的代码
- run: npm install && npm test # 执行 fork 的代码与脚本
npm install 本身就会执行 PR 中任意的 lifecycle scripts;在 pull_request_target 下,这些脚本能读取 secrets.* 并用写令牌提交代码。
安全解法是"双工作流模式":无特权工作流运行不可信代码;特权工作流只消费可信的输出。
# 1) 无特权:运行不可信代码,无 Secrets,只读令牌
name: PR Build
on: pull_request
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<sha>
- run: npm ci && npm run build
- uses: actions/upload-artifact@<sha>
with: { name: pr, path: dist/ }
# 2) 特权:由前一个工作流触发,绝不运行 fork 代码
name: PR Comment
on:
workflow_run:
workflows: ["PR Build"]
types: [completed]
permissions:
pull-requests: write
jobs:
comment:
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@<sha> # 仅作为数据处理,绝不执行
# 用受信令牌发布结果——但绝不执行工件
配套规则:将 pull_request_target、workflow_run、issue_comment、issues 视为特权触发器;在特权工作流中绝不签出并执行 PR/fork 代码;若只需基于元数据打标签、评论或分诊则无妨;凡是能用 pull_request 解决的,优先使用它安全默认值(只读 + 无 Secrets)。同样的范式也适用于工件与缓存投毒:不可信 PR 构建上传的工件是不可信数据,特权 workflow_run 可下载但只能当数据处理(解压时校验路径,谨防 ../ 路径穿越条目),且不要信任低权限运行填充的缓存。
脚本注入防护:把 ${{ }} 当作注入点
配套技能 skills/github-actions-hardening/references/injection.md 提出了一个核心洞察:${{ <expr> }} 由 runner 在 shell 执行之前展开到脚本文本中。所以 run: echo "Title: ${{ github.event.issue.title }}" 不是传参,而是把攻击者可控文本直接拼进 shell 命令。一个标题为 "; <attacker-command> # 的 issue 会被拼接进脚本并执行。凡是包含外部贡献者可影响数据的 ${{ }},都应视为代码注入点。
攻击者可控的典型上下文包括:github.event.issue.title/.body、github.event.pull_request.title/.body、.head.ref/.head.label、github.head_ref、github.event.comment.body、github.event.review.body、github.event.commits.*.message、github.event.pages.*.page_name 等——一个名为 $(<attacker-command>) 的分支或上述标题,在被插值进 run: 步骤时就会变成 shell。
脆弱模式:
# VULNERABLE
- run: |
echo "Reviewing PR: ${{ github.event.pull_request.title }}"
git checkout ${{ github.head_ref }}
安全模式——经 env: 中转:把不可信值绑定为环境变量,再引用(并加引号的)shell 变量。shell 变量是数据,不会被重新解析为工作流语法:
# SAFE
- env:
PR_TITLE: ${{ github.event.pull_request.title }}
HEAD_REF: ${{ github.head_ref }}
run: |
echo "Reviewing PR: $PR_TITLE"
git checkout "$HEAD_REF"
${{ }} 现在只出现在 env: 一侧、以"赋值"而非"拼入命令"的方式使用;shell 变量要始终加引号("$PR_TITLE")以防分词与通配符展开。actions/github-script 同样如此:不要向 script: 插值 ${{ }},改为经环境变量传递并读取 process.env。自定义 Action 的 with: 输入只有在 Action 自身不再把输入拼进 shell 时才安全,不确定时一律走 env:,或先做白名单校验(例如分支名应匹配 ^[A-Za-z0-9._/-]+$)。另一个细节:actions/checkout 默认会把令牌写入 .git/config(供后续 push),若 job 之后要运行不可信代码,应设置 persist-credentials: false。
安全扫描加固:五件套
智能体要求工作流覆盖五类安全扫描(github-actions-ci-cd-best-practices.instructions.md 同样强调这些最佳实践):
- 依赖审查(Dependency Review):在 PR 上扫描存在漏洞的依赖变更;
- CodeQL 分析:在 push、PR 与 schedule 上运行 SAST 静态分析;
- 容器扫描:用 Trivy 或同类工具扫描镜像;
- SBOM 生成:生成软件物料清单,提供供应链透明性;
- Secrets 扫描:开启并启用 push protection(推送保护)。
缓存与优化
- 优先使用内置缓存能力(
setup-node、setup-python等); - 用
actions/cache缓存依赖; - 使用有效缓存键(lockfile 的哈希作为 key);
- 配置
restore-keys作为回退。
github-actions-efficiency 给出了更完整的优化审计顺序:先度量再动手——用 gh run list --limit 10、gh run view <run_id> --log-failed 查看失败日志,并 rg 检索 on:|concurrency:|paths:|paths-ignore:|strategy:|matrix:|cache: 定位浪费源;随后从"缺依赖缓存、缺并发取消、触发器过宽、跨文件/job 重复覆盖、每次变更都跑昂贵 job"这五类常见浪费入手。矩阵广度要与决策匹配:发布或明确兼容性验证用全矩阵,普通代码变更用单一最新版本腿;格式化/自动修复这类回写分支的 job 应做成 opt-in(PR 标签如 ci:format、workflow_dispatch 手动触发或显式评论命令),且用标签触发时要同时监听 labeled 与通常的 unlabeled 事件。
工作流校验
- 用 actionlint 做工作流 lint;
- 校验 YAML 语法;
- 在 fork 中先行测试,再在主干仓库启用。
在效能审计语境下(见 skills/github-actions-efficiency/SKILL.md),还强调尽量做活体验证:手动触发一次 workflow_dispatch;用两次快速更新验证过期运行取消;用一个"仅含忽略文件或仅含工作流变更"的增量提交验证路径门控;并在 UI 中确认重型 job 确实被跳过,而非假设。
工作流安全核对清单
以下是智能体合并前逐条核对的可执行清单(可直接作为团队 PR 模板):
- [ ] Action 固定到完整提交 SHA 并带版本注释(如
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1) - [ ] 权限遵循最小权限(默认
contents: read) - [ ] Secrets 仅经环境变量访问
- [ ] 云认证使用 OIDC
- [ ] 已配置并发控制
- [ ] 已实现缓存
- [ ] 工件保留期设置合理
- [ ] PR 上启用依赖审查
- [ ] 已配置安全扫描(CodeQL、容器、依赖)
- [ ] 已用 actionlint 校验工作流
- [ ] 生产环境启用环境保护(environment protection)
- [ ] 已启用分支保护规则
- [ ] Secrets 扫描开启并带推送保护
- [ ] 无硬编码凭证
- [ ] 第三方 Action 来自可信来源
十五条最佳实践汇总
- 将 Action 固定到完整提交 SHA 并带版本注释(
@<sha> # vX.Y.Z),绝不使用可变标签或分支; - 遵循最小权限;
- 绝不记录 Secrets 日志;
- 云端访问优先 OIDC;
- 实现并发控制;
- 缓存依赖;
- 设置工件保留策略;
- 扫描漏洞;
- 合并前校验工作流;
- 生产环境使用环境保护;
- 启用 Secrets 扫描;
- 生成 SBOM 保障透明性;
- 审计第三方 Action;
- 用 Dependabot 保持 Action 更新(在 supply-chain.md 中给出了启用方式:
.github/dependabot.yml中配置package-ecosystem: github-actions与schedule.interval: weekly,Dependabot 能识别# vX.Y.Z注释并自动提升 SHA,以可审查 PR 的形式送达更新); - 先在 fork 中测试。
重要提醒
- 默认权限应为只读;
- OIDC 优于静态凭证;
- 用 actionlint 校验工作流;
- 绝不跳过安全扫描;
- 持续监控工作流失败与异常。
延伸阅读
- GitHub Actions Expert 智能体定义:本文主题的原始定义,含 frontmatter 与全部规则原文;
- github-actions-hardening 技能 及其参考文档(信任矩阵、脚本注入、权限与令牌、供应链):完整威胁模型与逐项审计步骤;
- github-actions-efficiency 技能:CI 分钟与成本削减的量化优化方法;
- agents/github-actions-node-upgrade.agent.md:将 JavaScript/TypeScript Action 升级到新版 Node 运行时的配套智能体,可与之组合完成 Action 现代化;
- GitHub Actions CI/CD 最佳实践指令:通用的 CI/CD 原则参考。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00