使用 CodeMender 保护 CI/CD 流水线

1. 准备工作

欢迎参加使用 CodeMender 保护 CI/CD 流水线实操 Codelab。

在现代软件交付中,静态分析工具(例如 CodeQL)对于捕获已知语法模式至关重要,但它们往往会漏掉隐藏在业务逻辑深处的细微漏洞,并生成干扰性强、未经证实的警报。

CodeMender(CLI 可执行文件:cm)可与现有 SAST 工具协同运行,通过部署 Google 的 Gemini 赋能的安全推理智能体来增强 CI/CD 流水线,从而:

发现复杂的业务逻辑缺陷:超越僵化的静态模式规则,推理应用上下文和架构数据流。

通过自我利用验证大幅减少假正例:在提醒开发者之前,先在沙盒环境中动态证明可利用性。

自主修复已验证的发现结果:合成代码补丁,根据测试套件进行验证以防止回归,并提交可供审核的拉取请求。

在此 Codelab 中,您将使用 GitHub Actions 和无密钥 Google Cloud 工作负载身份联合 (WIF) 将 CodeMender 集成到 OWASP Juice Shop 代码库中。

前提条件

  • 建议:基本熟悉 CodeMender 概念(在使用 CodeMender 保护应用安全中介绍)
  • 基本了解 GitHub Actions 工作流、触发器和 YAML 语法
  • 基本熟悉 Git 命令和拉取请求审核工作流
  • 基本了解 Google Cloud IAM 概念(服务账号、角色和 API)
  • 熟悉常见的 Web 应用漏洞(例如 SQL 注入和 XSS)

学习内容

  • 如何通过工作负载身份联合 (WIF) 在 GitHub Actions 和 Google Cloud 之间配置零信任无密钥身份验证
  • 如何使用 CodeMender 和 gemini-3.8-flash 构建自动化漏洞扫描工作流
  • 如何使用 OASIS SARIF v2.1.0 和原生 GitHub Actions 作业摘要发布可执行的安全报告
  • 如何构建自主修复工作流,以生成经过测试验证的代码补丁并提交拉取请求供人工审核
  • 如何强制执行自动化质量硬性限制 (--fail-on) 和流水线缓存,以实现高速持续安全

所需条件

  • 启用了结算功能且拥有 Project Owner/Editor 权限的 Google Cloud 项目
  • 一个 GitHub 账号,具有创建代码库、配置 Secret 和管理 Action 的权限(您将在第 3 步中使用 GitHub CLI 创建 OWASP Juice Shop GitHub 代码库的分支)。

2. 了解 CodeMender CI/CD 架构

在现代企业 DevSecOps 中,AI 赋能的安全防护不能仅仅是手动桌面工具。它必须充当自动化守门人(阻止不安全的拉取请求)和自主共同开发者(直接向 Git 提出经过验证的补丁)。

为此,您将实现由 Google Cloud Workload Identity Federation (WIF) 和 Gemini Enterprise 推理模型提供支持的双工作负载身份联合架构。

双工作流架构

CodeMender 自主 CI/CD 流水线架构

该流水线并未将检测和修复功能塞进一个庞大的脚本中,而是将安全防护分为两个解耦的专用工作流:

工作流

主要触发器

核心责任

质量硬性限制

1. 漏洞扫描(codemender-scan.yml)

拉取请求、主分支推送、每周 Cron、手动分派

• 针对 PR 运行快速差异化 AST 扫描(约 15 秒),针对推送运行快速基准扫描 (–quick),并按计划并发运行深度审核• 通过 cm health –strict 预先验证就绪情况• 生成原生 Actions 作业摘要,并将 OASIS SARIF 上传到 GitHub“Security”(安全)标签页

如果检测到新引入的高危或严重漏洞,则阻止 PR 合并 (–fail-on)

2. Autonomous Remediator(codemender-remediate.yml)

手动触发器 (workflow_dispatch)、夜间 Cron

• 从扫描工件 (cm report import) 和 state.db 恢复状态• 使用 Gemini 推理功能合成补丁• 保持干净的工作区边界,并提交可供审核的拉取请求

在提交 PR 之前确保生产环境差异清晰明了

三大核心企业设计原则

在后续部分中构建这些工作流时,请牢记以下三大基础架构支柱:

🔑 零信任无密钥身份验证和预检验证:

GitHub Secret 中不会存储任何长期有效的 Google Cloud 服务账号 JSON 密钥。运行器会生成一个短期有效的 OIDC 令牌,该令牌通过 Google Cloud STS 交换为有效期为 1 小时的临时凭证,在 Git 检出时设置 persist-credentials: false,并运行 cm health --strict 以验证凭证、网络可达性和 IAM 权限,然后再使用模型使用模式。

⚡ 自适应扫描路由和旧版噪音抑制:

CodeMender 不会在每个拉取请求上运行缓慢的完整代码库审核,而是将每个 CI 事件路由到最佳扫描模式:拉取请求会在 15 秒内触发对修改后的文件和 1 跳调用方的差异 AST 分析 (--diff="origin/$BASE_REF");主分支推送会运行单会话快速基准扫描 (--quick);每周计划会运行并发逐文件深度审核(默认 cm find .)。拉取请求未触及的预先存在的代码库漏洞会被标记为 [LEGACY / UNTOUCHED];它们会显示在 SARIF 报告中以供查看,但不会阻止拉取请求合并,也不会触发自动修复。

🛡️ 弹性非阻塞安全可观测性:

即使 --fail-on 以代码 1 退出以阻止不安全的 PR,使用 steps.scan.outputs.completed == 'true' 和 hashFiles('results.sarif') != '' 保护报告导出和 SARIF 上传步骤,也能确保 OASIS SARIF 提醒和作业摘要仍然发布,同时在运行被取消或因设置或容量错误(10–60)而中止时,干净利落地跳过导出和上传。

3. 在 Cloud Shell 中进行身份验证并准备目标代码库

如需建立无密钥身份验证并运行自动化安全流水线,您需要拥有一个可访问的目标 GitHub 代码库。在此 Codelab 中,您将使用 OWASP Juice Shop(一个故意留有漏洞的 Node.js Web 应用)作为开箱即用的安全演练场。

您将使用 Google Cloud Shell 中预安装的 GitHub CLI (gh) 向 GitHub 进行身份验证、自动捕获用户详细信息,并通过一个简化的流程准备代码库。

第 1 步:打开 Cloud Shell 并对 GitHub CLI 进行身份验证

打开 Google Cloud 控制台并激活 Cloud Shell(顶部导航栏中的图标 >_)。

对 GitHub CLI (gh) 进行身份验证,以配置 Git 凭证管理:

gh auth login --hostname github.com --git-protocol https --web

按照屏幕上的说明操作:

复制 Cloud Shell 中输出的 8 字符一次性代码(例如 1234-ABCD)。

按 Enter 键即可在浏览器中打开 https://github.com/login/device。

粘贴代码,然后点击 Authorize github(授权 GitHub)。

第 2 步:动态导出变量并创建 OWASP Juice Shop 的分支

在 Cloud Shell 中运行以下命令,以自动捕获您的 GitHub 用户名并准备代码库:

# 1. Automatically capture your GitHub username (customize GITHUB_REPO if desired)
export GITHUB_ORG=$(gh api user -q .login)
export GITHUB_REPO="${GITHUB_REPO:-juice-shop}"
export GITHUB_REPOSITORY="${GITHUB_ORG:+${GITHUB_ORG}/${GITHUB_REPO}}"
echo "Authenticated as: ${GITHUB_REPOSITORY}"

# 2. Fork on GitHub server using GITHUB_REPO as the fork name
gh repo fork juice-shop/juice-shop --fork-name "${GITHUB_REPO}" --default-branch-only --clone=false 2>/dev/null || true

# 3. Shallow clone your fork (downloads only ~15 MB in 3-5 seconds, skipping heavy upstream git history)
[ -d ~/"${GITHUB_REPO}" ] || git clone --depth=1 "https://github.com/${GITHUB_REPOSITORY}.git" ~/"${GITHUB_REPO}" || \
 (sleep 2 && git clone --depth=1 "https://github.com/${GITHUB_REPOSITORY}.git" ~/"${GITHUB_REPO}")
cd ~/"${GITHUB_REPO}"

# 4. Confirm you are in the repository directory
git status

4. 通过 gcloud 配置 Google Cloud 工作负载身份联合

在此步骤中,您将在 Google Cloud Shell 中使用 gcloud CLI 启用所需的 API、创建专用的 CI/CD 服务账号、设置工作负载身份池,并与您的 GitHub 代码库 (${GITHUB_REPOSITORY}) 建立 OIDC 信任。

第 1 步:设置 Google Cloud 环境变量

在 Cloud Shell 终端(克隆的代码库工作区内)中,捕获有效的 Google Cloud 项目 ID,验证 GitHub 代码库变量,并定义 WIF 资源名称:

# Capture active Google Cloud Project ID and verify GitHub repository
export PROJECT_ID=$(gcloud config get-value project)
export GITHUB_USER=$(gh api user -q ".login")
export GITHUB_REPO="${GITHUB_REPO:-$(git remote get-url origin 2>/dev/null | sed -E 's#^.*/##; s#\.git$##')}"
export GITHUB_REPO="${GITHUB_REPO:-juice-shop}"
export GITHUB_REPOSITORY="${GITHUB_USER:+${GITHUB_USER}/${GITHUB_REPO}}"

# Define WIF and Service Account resource names
export WORKLOAD_IDENTITY_POOL="codemender-github-pool"
export WORKLOAD_IDENTITY_PROVIDER="codemender-github-provider"
export SERVICE_ACCOUNT_NAME="codemender-ci-runner"

if [ -z "${PROJECT_ID}" ] || [ -z "${GITHUB_REPOSITORY}" ]; then
 echo "ERROR: PROJECT_ID ('${PROJECT_ID}') or GITHUB_REPOSITORY ('${GITHUB_REPOSITORY}') is empty. Check 'gcloud config get-value project' and 'gh auth status'."
else
 echo "Project ID:        ${PROJECT_ID}"
 echo "GitHub Repository: ${GITHUB_REPOSITORY}"
fi

第 2 步:启用所需的 Google Cloud API

启用 IAM API、Security Token Service API、IAM Credentials API、Agent Platform API 和 Resource Manager API:

gcloud services enable \
 iam.googleapis.com \
 iamcredentials.googleapis.com \
 sts.googleapis.com \
 aiplatform.googleapis.com \
 cloudresourcemanager.googleapis.com \
 --project=${PROJECT_ID}

第 3 步:创建专用 CI/CD 服务账号

创建一个专用于 CI/CD 流水线执行的最小权限 Google Cloud 服务账号(如果已存在,则安全地重复使用):

export SERVICE_ACCOUNT_EMAIL="${SERVICE_ACCOUNT_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"

gcloud iam service-accounts describe "${SERVICE_ACCOUNT_EMAIL}" \
 --project="${PROJECT_ID}" >/dev/null 2>&1 || \
gcloud iam service-accounts create "${SERVICE_ACCOUNT_NAME}" \
 --display-name="CodeMender CI/CD Pipeline Runner" \
 --project="${PROJECT_ID}"

echo "Service Account Email: ${SERVICE_ACCOUNT_EMAIL}"

第 4 步:授予 Agent Platform IAM 权限

向服务账号授予 roles/aiplatform.user (Agent Platform User) 角色,以便其能够与 Gemini Enterprise Agent Platform 通信:

gcloud projects add-iam-policy-binding ${PROJECT_ID} \
 --member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
 --role="roles/aiplatform.user"

第 5 步:创建工作负载身份池

创建工作负载身份池,以管理 GitHub Actions 身份断言(如果该池已存在于您的项目中,则会自动恢复并启用它):

POOL_STATE=$(gcloud iam workload-identity-pools list \
 --location="global" \
 --show-deleted \
 --project="${PROJECT_ID}" \
 --filter="name ~ /${WORKLOAD_IDENTITY_POOL}$" \
 --format="value(state)")

if [ -z "${POOL_STATE}" ]; then
 gcloud iam workload-identity-pools create "${WORKLOAD_IDENTITY_POOL}" \
   --location="global" \
   --display-name="CodeMender GitHub Pool" \
   --project="${PROJECT_ID}"
else
 [ "${POOL_STATE}" = "DELETED" ] && gcloud iam workload-identity-pools undelete "${WORKLOAD_IDENTITY_POOL}" \
   --location="global" \
   --project="${PROJECT_ID}"
 gcloud iam workload-identity-pools update "${WORKLOAD_IDENTITY_POOL}" \
   --location="global" \
   --no-disabled \
   --project="${PROJECT_ID}"
fi

第 6 步:使用 GitHub OIDC 创建工作负载身份提供方

在池中创建(或更新)OIDC 提供方,绑定颁发者网址 https://token.actions.githubusercontent.com,并将 GitHub 令牌断言映射到 Google STS 属性:

PROVIDER_STATE=$(gcloud iam workload-identity-pools providers list \
 --workload-identity-pool="${WORKLOAD_IDENTITY_POOL}" \
 --location="global" \
 --show-deleted \
 --project="${PROJECT_ID}" \
 --filter="name ~ /${WORKLOAD_IDENTITY_PROVIDER}$" \
 --format="value(state)")

if [ -z "${GITHUB_REPOSITORY}" ]; then
 echo "ERROR: GITHUB_REPOSITORY is empty. Re-run Step 1 before configuring the OIDC provider."
elif [ -z "${PROVIDER_STATE}" ]; then
 gcloud iam workload-identity-pools providers create-oidc "${WORKLOAD_IDENTITY_PROVIDER}" \
   --workload-identity-pool="${WORKLOAD_IDENTITY_POOL}" \
   --location="global" \
   --display-name="CodeMender GitHub Provider" \
   --issuer-uri="https://token.actions.githubusercontent.com" \
   --attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner" \
   --attribute-condition="assertion.repository == '${GITHUB_REPOSITORY}'" \
   --project="${PROJECT_ID}"
else
 [ "${PROVIDER_STATE}" = "DELETED" ] && gcloud iam workload-identity-pools providers undelete "${WORKLOAD_IDENTITY_PROVIDER}" \
   --workload-identity-pool="${WORKLOAD_IDENTITY_POOL}" \
   --location="global" \
   --project="${PROJECT_ID}"
 gcloud iam workload-identity-pools providers update-oidc "${WORKLOAD_IDENTITY_PROVIDER}" \
   --workload-identity-pool="${WORKLOAD_IDENTITY_POOL}" \
   --location="global" \
   --issuer-uri="https://token.actions.githubusercontent.com" \
   --attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner" \
   --attribute-condition="assertion.repository == '${GITHUB_REPOSITORY}'" \
   --no-disabled \
   --project="${PROJECT_ID}"
fi

第 7 步:允许 GitHub 代码库模拟服务账号

绑定 roles/iam.workloadIdentityUser 角色,以允许源自代码库的令牌模拟 CI 运行器服务账号,并验证政策绑定:

# Retrieve the canonical Workload Identity Pool resource ID
export POOL_ID=$(gcloud iam workload-identity-pools describe "${WORKLOAD_IDENTITY_POOL}" \
 --project="${PROJECT_ID}" \
 --location="global" \
 --format="value(name)")

if [ -z "${POOL_ID}" ] || [ -z "${GITHUB_REPOSITORY}" ]; then
 echo "ERROR: POOL_ID ('${POOL_ID}') or GITHUB_REPOSITORY ('${GITHUB_REPOSITORY}') is empty. Re-run Step 1 and Step 5."
else
 # Bind Workload Identity User role to the principalSet of your repository
 gcloud iam service-accounts add-iam-policy-binding "${SERVICE_ACCOUNT_EMAIL}" \
   --project="${PROJECT_ID}" \
   --role="roles/iam.workloadIdentityUser" \
   --member="principalSet://iam.googleapis.com/${POOL_ID}/attribute.repository/${GITHUB_REPOSITORY}"

 # Verify the principalSet binding on the Service Account
 gcloud iam service-accounts get-iam-policy "${SERVICE_ACCOUNT_EMAIL}" \
   --project="${PROJECT_ID}"
fi

第 8 步:检索 GitHub 密钥的配置值

检索工作负载身份提供方的完整资源名称,并将其存储在环境变量中,以便在下一步中进行自动注册:

# Retrieve full Workload Identity Provider name
export WORKLOAD_PROVIDER_NAME=$(gcloud iam workload-identity-pools providers describe ${WORKLOAD_IDENTITY_PROVIDER} \
 --workload-identity-pool=${WORKLOAD_IDENTITY_POOL} \
 --location="global" \
 --project=${PROJECT_ID} \
 --format="value(name)")

echo "=========================================================="
echo "WIF CONFIGURATION READY:"
echo "=========================================================="
echo "GCP_WORKLOAD_IDENTITY_PROVIDER: ${WORKLOAD_PROVIDER_NAME}"
echo "GCP_SERVICE_ACCOUNT:            ${SERVICE_ACCOUNT_EMAIL}"
echo "=========================================================="

5. 配置 GitHub 代码库 Secret 和工作流权限

现在,Google Cloud WIF 已预配完毕,您将配置 GitHub 代码库密钥,并授予必要的自动化工作流权限,以便进行自动安全扫描和自动修复拉取请求。

您可以使用 GitHub CLI (gh) 直接从 Cloud Shell 配置这些设置,从而完全避免手动复制和粘贴长资源名称。

第 1 步:使用 GitHub CLI 设置代码库密钥

在 Cloud Shell 终端中,运行以下命令,使用活跃环境变量安全地设置两个必需的代码库 Secret:

# 1. Set the Workload Identity Provider resource name
gh secret set GCP_WORKLOAD_IDENTITY_PROVIDER \
 --body "${WORKLOAD_PROVIDER_NAME}" \
 --repo "${GITHUB_REPOSITORY}"

# 2. Set the CI/CD Service Account email
gh secret set GCP_SERVICE_ACCOUNT \
 --body "${SERVICE_ACCOUNT_EMAIL}" \
 --repo "${GITHUB_REPOSITORY}"

# 3. Verify the secrets are registered
gh secret list --repo "${GITHUB_REPOSITORY}"

第 2 步:为自动 PR 授予工作流权限

默认情况下,GitHub Actions 工作流以只读权限运行,无法创建拉取请求。在第 9 步中,CodeMender 将自主生成并提交经过验证的修复拉取请求。

授予写入权限,并直接通过 GitHub API 启用拉取请求创建功能:

[ -d .git ] || cd ~/"${GITHUB_REPO:-juice-shop}"
export GITHUB_REPOSITORY="${GITHUB_REPOSITORY:-$(git remote get-url origin 2>/dev/null | sed -E 's#^.*github\.com[:/]##; s#\.git$##')}"

gh api \
 --method PUT \
 -H "Accept: application/vnd.github+json" \
 /repos/${GITHUB_REPOSITORY}/actions/permissions/workflow \
 -f default_workflow_permissions="write" \
 -F can_approve_pull_request_reviews=true

第 3 步:停用继承的上游 Juice Shop 工作流

复刻 OWASP Juice Shop 会带入 16 个上游 CI 工作流(例如 PR Compliance Check、CI/CD Pipeline 和 CodeQL Scan),以及一个上游贡献者 PR 模板,该模板会自动关闭针对 master 的 PR。通过 GitHub CLI 停用所有非 CodeMender 工作流,并在本地移除上游文件,以便您的派生项目严格运行 CodeMender 工作流:

[ -d .git ] || cd ~/"${GITHUB_REPO:-juice-shop}"
export GITHUB_REPOSITORY="${GITHUB_REPOSITORY:-$(git remote get-url origin 2>/dev/null | sed -E 's#^.*github\.com[:/]##; s#\.git$##')}"

# 1. Disable all inherited non-CodeMender workflows in GitHub Actions (and CodeQL default setup if active)
for WF_ID in $(gh workflow list --all --limit 100 --repo "${GITHUB_REPOSITORY}" --json id,path \
 -q '.[] | select((.path | startswith(".github/workflows/")) and (.path | test("codemender-") | not)) | .id'); do
 gh workflow disable "${WF_ID}" --repo "${GITHUB_REPOSITORY}" 2>/dev/null || true
done
gh api -X PATCH "/repos/${GITHUB_REPOSITORY}/code-scanning/default-setup" \
 -f state=not-configured >/dev/null 2>&1 || true

# 2. Remove upstream workflow files and PR template from the local workspace
for f in .github/workflows/*; do
 case "$f" in *codemender-*) ;; *) rm -f "$f" ;; esac
done
rm -f .github/PULL_REQUEST_TEMPLATE.md

6. 使用 SARIF 构建自动漏洞扫描工作流

在此步骤中,您将编写主要持续安全扫描工作流 (.github/workflows/codemender-scan.yml),该工作流会在分支推送时运行基准扫描,在拉取请求时运行差异门控,并将 SARIF 导出到 GitHub 代码扫描。

第 1 步:下载 CodeMender 扫描工作流

将 codemender-scan.yml 从起始代码库下载到 .github/workflows/:

[ -d .git ] || cd ~/"${GITHUB_REPO:-juice-shop}"
mkdir -p .github/workflows
curl -fsSL https://raw.githubusercontent.com/edwardc-gcp/codemender-github-actions/main/.github/workflows/codemender-scan.yml \
 -o .github/workflows/codemender-scan.yml

第 2 步:提交、推送并触发 CI 扫描

提交工作流更改,并直接推送到代码库的主分支(Juice Shop 为 master,或 main):

# Stage workflow changes, commit, and push to primary branch
git add -A .github
git commit -m "ci: add CodeMender SARIF security scanning workflow"
git push origin $(git branch --show-current)

在浏览器中:

前往您复刻的 GitHub 代码库:https://github.com/YOUR_GITHUB_USER/juice-shop(例如 https://github.com/octocat/juice-shop)。

点击顶部导航栏中的操作标签页。

在左侧边栏的 Workflows(工作流)下,选择 CodeMender Security Scan & SARIF Export(CodeMender 安全扫描和 SARIF 导出)。

触发或查看工作流执行:

自动触发:如果列出了由推送触发的自动运行,请直接点击该运行以监控实时执行情况。

手动按需触发器 (workflow_dispatch):如果尚未开始任何运行,请点击右侧的 Run workflow ▾(运行工作流 ▾)按钮,确保已选择 Branch: master(不勾选 deep_scan 复选框,以便运行快速 --quick 基准扫描),然后点击绿色的 Run workflow(运行工作流)按钮。

点击进入正在执行的运行作业,然后选择 Autonomous Code Vulnerability Scan,实时查看工作流的启动过程。

codemender-scan.yml 内部:事件感知型扫描路由

在云端运行 --quick 基准扫描(约 3-5 分钟)期间,请检查 codemender-scan.yml(第 118-155 行)如何将每个 GitHub 事件路由到运行器内的专用扫描模式:

# Excerpt from .github/workflows/codemender-scan.yml
if [ "${{ github.event_name }}" = "pull_request" ]; then
 cm find . \
   --diff="origin/${{ github.base_ref }}" \
   --diff-workers=16 \
   --fail-on="CRITICAL,HIGH" \
   --sandbox=false --yes --bypass-warning || status=$?
elif [ "${{ github.event_name }}" = "schedule" ] || [ "${{ github.event.inputs.deep_scan }}" = "true" ]; then
 cm find . \
   --deep-workers=8 \
   --sandbox=false --yes --bypass-warning || status=$?
else
 cm find . \
   --quick \
   --sandbox=false --yes --bypass-warning || status=$?
fi

Hardened Runner Setup(第 27-117 行):跳过不受信任的外部复刻和 dependabot[bot] PR,使用 persist-credentials: false 检出,缓存 ~/.local/bin/cm 和 ~/.codemender/state.db,并在消耗使用模式配额之前运行 cm health --strict。

无头沙盒边界 (--sandbox=false --yes --bypass-warning):停用 Linux 用户命名空间隔离(在无特权的 GitHub Actions 容器内受限),同时强制执行工作区路径边界 (project_paths) 和 command_policy.yaml。

退出代码保护的 SARIF 和工件导出(第 147-194 行):仅当 cm find 以 0(干净)或 1(达到 --fail-on 阈值)退出时,才会设置 steps.scan.outputs.completed=true。这可确保在 --fail-on 阻止 PR 时,SARIF 报告(cm report -f sarif,包含 CVSS 严重程度评分)、$GITHUB_STEP_SUMMARY 表 (cm report -f md) 和 codemender-findings.json 工件仍会发布,同时在出现设置或容量错误 (10–60) 时跳过导出,从而避免空 SARIF 文件关闭现有提醒。

7. 检查 GitHub 代码扫描提醒和安全信息中心

基准推送工作流完成后,GitHub 会处理上传的 SARIF 文件,并填充 Actions 运行摘要和代码库“Security”(安全)标签页。

第 1 步:在 GitHub Actions 上检查原生作业摘要

每次工作流运行都会直接在 GitHub Actions 运行概览中附加一个 Markdown 摘要表 (cm report -f md):

在 GitHub 代码库中,点击操作标签页。

选择已完成的 CodeMender Security Scan & SARIF Export 运行(基准分支推送审核在发布 SARIF 发现结果后会显示绿色 ✅ 勾号)。

向下滚动到 🛡️ CodeMender Security Scan Summary 部分,查看发现结果明细:

🔒 CodeMender 安全报告

摘要

发现结果总数:28

严重程度

计数

🔴 严重

6

🟠 高

16

🟡 中

6

状态

计数

🔴 未解决

28

发现结果

ID

严重程度

Confidence

状态

修复

文件

标题

c1b96b9a

🔴 严重

100%

未解决

—

routes/userProfile.ts

通过在用户个人资料中注入服务器端代码来远程执行代码

cd47b38a

🔴 严重

100%

未解决

—

routes/login.ts

用户身份验证路由中的 SQL 注入

a47a9e45

🔴 严重

100%

未解决

—

routes/b2bOrder.ts

B2B 订单处理中通过沙盒逃逸实现远程代码执行

第 2 步:探索集中式代码扫描信息中心

在 GitHub 代码库的顶部导航栏中,点击安全性和质量(或安全性)标签页。

在左侧边栏的 Findings(发现结果)下,选择 Code scanning(代码扫描)。

如果多个工具处于活跃状态,请将工具过滤为 CodeMender (is:open branch:master tool:CodeMender),以检查与原生 CVSS 严重级别标记映射的提醒:

严重程度徽章

漏洞标题

检测到的位置

分支

🔴 严重

通过在用户个人资料中注入服务器端代码来远程执行代码

routes/userProfile.ts:58

master

🔴 严重

用户身份验证路由中的 SQL 注入

routes/login.ts:34

master

🔴 严重

B2B 订单处理中通过沙盒逃逸实现远程代码执行

routes/b2bOrder.ts:21

master

点击 SQL Injection in User Authentication Route(用户身份验证路由中的 SQL 注入)(routes/login.ts:34),查看 Gemini 合成的受污染数据流和根本原因分析。

8. 使用差异扫描功能阻止不安全的拉取请求

基准扫描在 OWASP Juice Shop 中发现了 28 个既有漏洞。如果使用传统的静态分析器,这 28 个旧发现结果会导致未来的每个拉取请求都失败,直到整个积压问题得到修复,这会迫使团队停用合并屏蔽功能。

现在,您将测试 CodeMender 的 PR 差异 AST 门控(--diff + --fail-on="CRITICAL,HIGH")的实际效果:打开一个引入新漏洞的拉取请求,观察 CodeMender 如何隔离增量、在约 15-25 秒内阻止合并,并对确切的违规代码行进行注释。

第 1 步:打开一个引入命令注入漏洞的拉取请求

在 Cloud Shell(克隆的代码库工作区内)中,创建一个功能分支,添加包含操作系统命令注入漏洞的新 Express 路由,然后针对您的分支代码库发起拉取请求:

[ -d .git ] || cd ~/"${GITHUB_REPO:-juice-shop}"
export GITHUB_REPOSITORY="${GITHUB_REPOSITORY:-$(git remote get-url origin 2>/dev/null | sed -E 's#^.*github\.com[:/]##; s#\.git$##')}"
PRIMARY_BRANCH=$(git symbolic-ref --short refs/remotes/origin/HEAD | sed 's@^origin/@@')

# Reset any previous demo PR/branch so this step can be re-run cleanly
gh pr close feat/diagnostics --repo "${GITHUB_REPOSITORY}" --delete-branch 2>/dev/null || true
git checkout "${PRIMARY_BRANCH}"
git checkout -B feat/diagnostics

# Introduce an Express endpoint vulnerable to OS Command Injection (CWE-78)
cat << 'EOF' > routes/diagnostics.ts
import { execSync } from 'node:child_process'
import { type Request, type Response } from 'express'

export function systemDiagnostics () {
 return (req: Request, res: Response) => {
   const host = String(req.query.host ?? '127.0.0.1')
   const output = execSync(`ping -c 1 ${host}`).toString()
   res.status(200).json({ status: 'ok', output })
 }
}
EOF

git add routes/diagnostics.ts
git commit -m "feat: add network diagnostics endpoint"
git push -f -u origin feat/diagnostics

gh pr create \
 --repo "${GITHUB_REPOSITORY}" \
 --base "${PRIMARY_BRANCH}" \
 --head feat/diagnostics \
 --title "feat: add network diagnostics endpoint" \
 --body "Adds a ping diagnostics handler."

第 2 步:验证差异化合并门控和内嵌 PR 注解

打开终端输出末尾显示的拉取请求网址。

滚动到拉取请求会话页面底部的检查复选框,然后查看自主代码漏洞扫描:

增量速度:cm find . --diff="origin/${PRIMARY_BRANCH}" 不会扫描代码库中的所有 680 多个文件(约 3-5 分钟),而是将分析范围严格限定在 routes/diagnostics.ts(以及任何 1 跳调用方),在约 15-25 秒内完成扫描。

旧有技术债务隔离和合并阻止:Juice Shop 的 28 个未改动的旧有漏洞均未导致检查失败,只有新引入的 CRITICAL 命令注入触发了 --fail-on="CRITICAL,HIGH",并以状态代码 1(红色 ❌)退出,从而阻止了 PR 合并!

点击拉取请求顶部的 Files changed(已更改的文件)标签页:

由于工作流的退出代码保护机制 (steps.scan.outputs.completed == 'true') 在 --fail-on 以代码 1 退出时仍会导出并上传 results.sarif,因此 GitHub 代码扫描会在 routes/diagnostics.ts 中的 execSync 下方直接呈现内嵌的 CodeMender — Critical 安全注释!

第 3 步:清理测试 PR 并返回到主分支

将 Cloud Shell 工作区切换回主分支,并关闭不安全的测试拉取请求,然后再继续进行自主修复:

[ -d .git ] || cd ~/"${GITHUB_REPO:-juice-shop}"
export GITHUB_REPOSITORY="${GITHUB_REPOSITORY:-$(git remote get-url origin 2>/dev/null | sed -E 's#^.*github\.com[:/]##; s#\.git$##')}"
PRIMARY_BRANCH=$(git symbolic-ref --short refs/remotes/origin/HEAD | sed 's@^origin/@@')
git checkout "${PRIMARY_BRANCH}"
gh pr close feat/diagnostics --repo "${GITHUB_REPOSITORY}" --delete-branch
git branch -D feat/diagnostics 2>/dev/null || true

9. 构建自主修复和自动 PR 工作流

现在,拉取请求关卡可防止新的漏洞进入代码库,您将部署自主修复 (.github/workflows/codemender-remediate.yml) 来主动修复现有漏洞并打开可供审核的拉取请求。

第 1 步:下载修复工作流文件

CodeMender 提供两种修复模板,具体取决于代码库的公开范围:

公共/开源代码库 (codemender-remediate.yml):负责任披露模式 - 链接到 GitHub 代码扫描 SARIF 提醒,而不会公开披露原始漏洞利用向量。

私有/企业代码库 (codemender-remediate-private-repo.yml):内部审核证据模式 - 将验证工件(PLAN.md、REPORT.md、exploit.sh)嵌入可收起的 PR 部分,同时将其从 Git 提交中排除。

将公开安全修复工作流下载到您的代码库中:

mkdir -p .github/workflows
curl -fsSL https://raw.githubusercontent.com/edwardc-gcp/codemender-github-actions/main/.github/workflows/codemender-remediate.yml \
 -o .github/workflows/codemender-remediate.yml

第 2 步:提交并触发自主修复工作流

提交修复工作流并将其推送到主分支:

git add .github/workflows/codemender-remediate.yml
git commit -m "ci: add CodeMender autonomous remediation workflow"
git push origin $(git branch --show-current)

在浏览器中,前往 GitHub 代码库的 Actions(操作)标签页。

在左侧边栏的工作流下,选择 CodeMender Autonomous Security Remediation & Auto-PR。

点击右侧绿色的 Run workflow(运行工作流)下拉菜单:

Use workflow from(使用以下分支的工作流):确保已选择默认分支 (master)。

Specific Finding ID or prefix to fix(要修复的具体发现结果 ID 或前缀):输入在第 7 步中记下的 routes/login.ts 的 8 字符前缀(例如 cd47b38a)。如果将此字段留空,系统会对所有未解决的“严重”和“高”发现结果运行批量修复(约 10-15 分钟);如果针对单个 ID,则会在约 45-60 秒内完成。

点击弹出式窗口中的绿色运行工作流按钮,然后打开正在执行的运行。

通过“运行工作流”下拉菜单触发 CodeMender 自主安全修复工作流

codemender-remediate.yml 内部:自主补丁合成

在 CodeMender 于云端合成补丁期间(约 30-45 秒),检查 codemender-remediate.yml(第 111-184 行)如何在运行器中编排修复流程:

零扫描恢复 (cm report import):从扫描工作流下载 codemender-findings.json 工件并将其导入工作区 (cm report import -f codemender-findings.json),仅在没有先前工件时才回退到 cm find . --quick。

无头补丁合成 (cm fix):将 project_paths 固定到 $GITHUB_WORKSPACE,通过管道传输 < /dev/null 以防止交互式挂起,并在批处理循环期间传递 --no-reset,以便在发现结果之间保留累积修复:

# Executed inside the GitHub Actions runner (codemender-remediate.yml)
cm fix "${SPECIFIC_TARGET}" --sandbox=false --yes --bypass-warning < /dev/null

清理生产提交(第 155-252 行):在 peter-evans/create-pull-request@v8 打开分支 (codemender/auto-remediation-${{ github.run_id }}) 之前,通过 .git/info/exclude 清除 .cm_project、.exploit/ 和临时文件,严格提交生产代码更改:

--- a/routes/login.ts
+++ b/routes/login.ts
@@ -28,8 +28,8 @@ module.exports = function login () {
-    models.sequelize.query(`SELECT * FROM Users WHERE email = '${req.body.email || ''}' AND password = '${security.hash(req.body.password || '')}' AND deletedAt IS NULL`, { model: models.User })
+    models.sequelize.query('SELECT * FROM Users WHERE email = :email AND password = :password AND deletedAt IS NULL', {
+      replacements: { email: req.body.email || '', password: security.hash(req.body.password || '') },
+      model: models.User
+    })

第 3 步:在 GitHub 上查看新打开的拉取请求

前往代码库中的 Pull requests(拉取请求)标签页,然后打开新的拉取请求(例如 🛡️ [CodeMender] Fix: SQL Injection in User Authentication Route)。

点击 Files changed(文件已更改),确认仅修改了 routes/login.ts,并排除了所有内部运行时元数据。

查看直接链接到 GitHub 代码扫描 (SARIF) 提醒的 PR 说明,以便维护者签核。

10. 排查极端情况和私有代码库问题

未启用 GitHub Advanced Security (GHAS) 的私有代码库

公共 GitHub 代码库可免费、无限制地使用 GitHub 代码扫描 SARIF 提取功能。但是,如果您将此工作流部署到没有有效 GitHub Advanced Security (GHAS) 许可的私有企业代码库,则 upload-sarif 步骤将失败,并显示以下错误:

HTTP 403: Advanced Security must be enabled for this repository to use code scanning.

解决方法:回退到独立的互动式 HTML 报告

CodeMender 的内置 HTML 报告生成器 (cm report -f html) 可创建独立的交互式安全信息中心,其中包含严重程度指标卡片、带有行号的语法突出显示代码段,以及不依赖任何外部 Web 的内嵌补丁差异查看器。

如需在没有 GHAS 许可的情况下支持私有代码库,请更新 .github/workflows/codemender-scan.yml 以归档 HTML 报告工件,而不是上传 SARIF:

生成独立的 HTML 报告:在 Export Security Reports & Publish Job Summary 步骤中,使用 cm report -f html > codemender-audit-report.html 导出 HTML 信息中心以及 Markdown 摘要。

将报告归档为工作流制品:将 Upload SARIF Report to GitHub Code Scanning (github/codeql-action/upload-sarif@v4) 步骤替换为配置为上传 codemender-audit-report.html 的 actions/upload-artifact@v4(例如,在制品名称 codemender-security-report 下)。

在任何浏览器中本地检查:直接从 GitHub Actions 工作流运行摘要页面下载 codemender-security-report.zip,将其解压缩,然后在任何网络浏览器中打开 codemender-audit-report.html 进行可视化分类。

私有代码库自主修复,并嵌入审核证据

在私有企业代码库中,工程和安全团队需要全面了解根本原因分析、漏洞利用概念验证和修复计划,而无需使用外部工具信息中心。不过,将漏洞利用脚本 (exploit.sh) 或内部 Markdown 文件(PLAN.md、LOG.md)提交到代码库分支会污染 Git 历史记录,并可能触发其他安全扫描器的误报。

为解决此问题,请部署专用的私有代码库修复工作流 (codemender-remediate-private-repo.yml):

curl -fsSL https://raw.githubusercontent.com/edwardc-gcp/codemender-github-actions/main/.github/workflows/codemender-remediate-private-repo.yml \
 -o .github/workflows/codemender-remediate.yml
git add .github/workflows/codemender-remediate.yml
git commit -m "ci: switch to private repository remediation workflow with embedded audit evidence"
git push origin $(git branch --show-current)

私有代码库修复的工作原理

动态 PoC 验证 (cm verify):在生成修复方案之前,合成并测试可行的漏洞利用,以确认可利用性。如果验证将不可利用的发现结果标记为DISMISSED,则会自动跳过 cm fix。

嵌入式 PR 审核可折叠块:将 REPORT.md、PLAN.md 和 exploit.sh 提取到 /tmp/codemender-evidence/ 中,并直接在 <details open> 可折叠块内将其格式化为拉取请求说明,以便审核者一键检查。

工件归档:通过 actions/upload-artifact@v4 归档完整的证据文件夹。

原始 Git 提交:清除所有运行时元数据和 .exploit 目录,以便拉取请求 Git 提交严格包含生产代码更改。

工作负载身份联合 (WIF) 问题排查核对清单

默认情况下,google-github-actions/auth@v2 会在设置期间写入本地联合凭证配置文件,而实际的 OAuth 2.0 令牌交换会在 cm health --strict 或 cm find 启动时发生。使用以下核对清单诊断 HTTP 400 或 HTTP 403 身份验证错误:

错误症状

常见根本原因

验证和修复

HTTP 403(iam.serviceAccounts.getAccessToken 被拒)

服务账号上缺少 roles/iam.workloadIdentityUser 绑定、GCP_SERVICE_ACCOUNT 密钥不正确或 iamcredentials.googleapis.com 未启用。

确认 GCP_SERVICE_ACCOUNT 与 codemender-ci-runner@.iam.gserviceaccount.com 匹配,然后运行下面的 IAM 政策检查,以验证 principalSet 绑定。

HTTP 400(unauthorized_client 或属性条件被拒)

OIDC –attribute-condition 代码库名称(或大小写)与触发工作流的 GitHub 代码库不匹配,或者 WIF 提供方已被停用。

重新运行第 4 步(第 1 步和第 6 步),确保 ${GITHUB_REPOSITORY} 与 GitHub 上的 owner/repo 大小写完全一致,并应用 –no-disabled。

HTTP 400(令牌交换时出现 invalid_grant)

OIDC 提供方上的 –issuer-uri 格式不正确或缺少 attribute.repository=assertion.repository 映射。

确认 –issuer-uri 已严格设置为 https://token.actions.githubusercontent.com(不带尾部斜杠)。

设置后立即出现暂时性 HTTP 403

对于新创建的绑定,全局 IAM 和工作负载身份联合传播可能需要 1-5 分钟。

等待 1-2 分钟,然后在 GitHub Actions 中点击重新运行失败的作业。

如需在 Cloud Shell 中检查 CI/CD 服务账号上的实时 Workload Identity 绑定,请运行以下命令:

gcloud iam service-accounts get-iam-policy "${SERVICE_ACCOUNT_EMAIL}" \
 --project="${PROJECT_ID}" \
 --flatten="bindings[].members" \
 --filter="bindings.role=roles/iam.workloadIdentityUser"

确认输出将 roles/iam.workloadIdentityUser 绑定到 principalSet://iam.googleapis.com/<POOL_ID>/attribute.repository/<GITHUB_USER>/<GITHUB_REPO>:

---
bindings:
 members: principalSet://iam.googleapis.com/.../attribute.repository/<OWNER>/<REPO>
 role: roles/iam.workloadIdentityUser

CI/CD 预检和 CLI 退出代码问题排查

CodeMender 命令(cm health --strict、cm find、cm verify 和 cm fix)会返回确定性退出代码,以便 CI/CD 流水线能够立即区分安全发现结果与设置或服务错误:

退出代码

类别

常见根本原因和解决方法

0

成功

健康检查通过或扫描完成,未发现任何阻碍性问题。

1

发现结果/一般

–fail-on 在 PR 上检测到阻塞性 CRITICAL 或 HIGH 漏洞(预期合并关口评估失败)。

10

credentials

WIF OAuth/ADC 令牌交换失败(400/401)。请验证 GCP_WORKLOAD_IDENTITY_PROVIDER、GCP_SERVICE_ACCOUNT 以及上述 roles/iam.workloadIdentityUser 绑定。

20

config

.codemender/config.yaml 或 ~/.codemender/config.yaml 语法格式不正确。

30

reachability

运行器无法访问 aiplatform.googleapis.com(DNS、代理、服务中断或 VPC Service Controls)。

40

authorization

服务账号在目标项目中缺少 roles/aiplatform.user,或者 cloudresourcemanager.googleapis.com/aiplatform.googleapis.com 已停用。

50

timeout

预检 cm health 检查超出了 30 秒的截止期限。

60

capacity

代理已达到其并发活跃任务限制;请稍等片刻后重新运行工作流。

如需了解其他运行时错误(例如 Git diff 基础分支解析 [fetch-depth: 0]、资源传播延迟或 GitHub Actions 拉取请求创建权限),请参阅官方 CodeMender CI/CD 问题排查指南。

11. 清理资源

为防止 Google Cloud 和 GitHub 环境中持续消耗资源或清理测试配置,请执行以下清理步骤。

第 1 步:通过 gh CLI 移除 GitHub 代码库 Secret 并停用工作流

使用 GitHub CLI (gh) 彻底删除代码库 Actions 密钥并停用 CodeMender 工作流:

# Delete the repository Actions secrets
gh secret delete GCP_WORKLOAD_IDENTITY_PROVIDER --repo "${GITHUB_REPOSITORY}" 2>/dev/null || true
gh secret delete GCP_SERVICE_ACCOUNT --repo "${GITHUB_REPOSITORY}" 2>/dev/null || true

# Disable the CodeMender workflows to prevent unwanted automatic triggers
gh workflow disable codemender-scan.yml --repo "${GITHUB_REPOSITORY}" 2>/dev/null || true
gh workflow disable codemender-remediate.yml --repo "${GITHUB_REPOSITORY}" 2>/dev/null || true

第 2 步:停用或删除 Google Cloud 工作负载身份联合资源

工作负载身份池、提供方和服务账号在不交换令牌时不会产生闲置费用。选择与您的工作流匹配的清理模式:

选项 A - 暂停访问权限(如果您计划在此项目中重新运行,建议使用此选项):立即停用 OIDC 令牌交换,而不会触发 Google Cloud IAM 的 30 天软删除保留期或服务账号 uniqueId 传播延迟:

gcloud iam workload-identity-pools providers update-oidc "${WORKLOAD_IDENTITY_PROVIDER}" \
 --workload-identity-pool="${WORKLOAD_IDENTITY_POOL}" \
 --location="global" \
 --disabled \
 --project="${PROJECT_ID}"

选项 B - 完全拆解:删除 WIF 提供方、池(在 DELETED 状态下保留 30 天)和专用服务账号:

# Delete Workload Identity Provider
gcloud iam workload-identity-pools providers delete "${WORKLOAD_IDENTITY_PROVIDER}" \
 --workload-identity-pool="${WORKLOAD_IDENTITY_POOL}" \
 --location="global" \
 --project="${PROJECT_ID}" \
 --quiet

# Delete Workload Identity Pool
gcloud iam workload-identity-pools delete "${WORKLOAD_IDENTITY_POOL}" \
 --location="global" \
 --project="${PROJECT_ID}" \
 --quiet

# Delete CI/CD Service Account
gcloud iam service-accounts delete "${SERVICE_ACCOUNT_EMAIL}" \
 --project="${PROJECT_ID}" \
 --quiet

12. 恭喜

恭喜!您已完成使用 CodeMender 保护 CI/CD 流水线 Codelab!

您已成功使用 Google Cloud 工作负载身份联合、CodeMender 和 GitHub Actions 设计、构建并自动化了一个生产级零信任 DevSecOps 流水线。

所学内容

零信任无密钥身份验证:通过工作负载身份联合 (WIF) 将临时 GitHub OIDC 令牌绑定到 Google Cloud IAM,从而消除了存在风险的长期服务账号 JSON 密钥。

PR 差异化 AST 扫描和旧版降噪:将持续扫描范围限定为修改后的文件和依赖项 (--diff="origin/$BASE_REF"),隔离旧版技术债务 ([LEGACY / UNTOUCHED]),使开发者反馈环保持在 15 秒以内。

多格式报告和原生作业摘要:结合使用 OASIS SARIF v2.1.0(用于集中式 GitHub 代码扫描)和原生 Markdown (cm report -f md >> $GITHUB_STEP_SUMMARY)(用于在 CI 工作流运行页面上直接提供即时执行可见性)。

人机协同审核的自主修复:从被动提醒通知转变为主动修复流水线,可自主合成建议的代码补丁、根据回归测试套件进行验证,并打开包含审核工件的审核拉取请求,供工程师签核。

自动化质量硬性控制:实施严格的分支保护和合并控制 (--fail-on),防止危险漏洞进入生产环境。

制品驱动的状态转移和工作区隔离:使用 cm report import 在 CI 作业之间无缝转移发现结果,同时使用 .git/info/exclude 保留原始 Git 历史记录并防止凭证泄露。

开发者思维方式转变

传统 DevSecOps 实践

CodeMender 自主 CI/CD 流水线

被动警报转储:扫描器会输出原始终端日志或静态错误列表,将分类负担完全转移给开发者。

Active Co-developer:合成经过验证的代码补丁,并打开可供合并的拉取请求,供工程师签核。

假正例率高:静态 AST 启发式方法会标记数千个理论上的漏洞,但无法证明其可利用性。

2 层级漏洞利用验证:动态沙盒测试生成 (cm verify) 可确认实际漏洞,从而消除假正例。

Secret 存储风险:存储在 CI Secret 中的长期服务账号 JSON 密钥存在意外泄露和凭证蔓延的风险。

零信任 WIF:通过按需与 Google Cloud STS 交换的临时 OIDC 令牌进行无密钥身份验证。

单体式重新扫描:每次提交都会触发完整代码库审核,从而延迟 CI 工作流并浪费昂贵的 token。

事件感知型 AST 路由:拉取请求会执行 15 秒的差异化 AST 扫描 (–diff),而 Cron 运行会执行深度审核。

代码库历史记录被污染:临时漏洞利用脚本和攻击日志直接提交到代码库分支。

企业级工作区健全度:攻击证据会清晰地嵌入到 PR 说明和工作流工件中,从而保持代码提交的纯净度。

后续步骤

🚀 继续 CodeMender 学习之旅:

第 1 部分:使用 CodeMender 保护应用安全 - 掌握自主代码库漏洞发现、PoC 漏洞利用验证和本地补丁合成。

本地左移和无服务器云原生 CI/CD:探索官方 CI/CD 指南中有关 Google Cloud Build (cloudbuild.yaml) 和本地 Git 预提交钩子 (cm find . --diff --staged) 的参考实现。

📖 探索文档和资源:

Google Cloud CodeMender CI/CD 集成官方指南

Google Cloud CodeMender 官方文档

CodeMender GitHub Actions Starter Template Repository

Google Cloud 工作负载身份联合文档

💡 应用于您自己的代码库:

复刻入门工作流,使用 CodeMender 保护组织的 Python、Go、Java、Rust 和 Node.js 代码库!