From 986d1a937d52b5f49dee2df9708112f13ef72eea Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 18 May 2026 09:41:09 +0800 Subject: [PATCH] fix(ci): run secret surface guard with node --- .gitea/workflows/cd.yaml | 2 +- .gitea/workflows/code-review.yaml | 2 +- docs/LOGBOOK.md | 4 +- scripts/ci/check-gitea-step-env-secrets.js | 54 ++++++++++++++++++++++ scripts/ci/check-gitea-step-env-secrets.rb | 32 ------------- 5 files changed, 58 insertions(+), 36 deletions(-) create mode 100755 scripts/ci/check-gitea-step-env-secrets.js delete mode 100755 scripts/ci/check-gitea-step-env-secrets.rb diff --git a/.gitea/workflows/cd.yaml b/.gitea/workflows/cd.yaml index 5f3b381e..9c405b45 100644 --- a/.gitea/workflows/cd.yaml +++ b/.gitea/workflows/cd.yaml @@ -77,7 +77,7 @@ jobs: - uses: actions/checkout@v4 - name: Guard Workflow Secret Surfaces - run: ruby scripts/ci/check-gitea-step-env-secrets.rb + run: node scripts/ci/check-gitea-step-env-secrets.js # 2026-03-31 ogt: 優化告警格式 - 提高可讀性 - name: Get Commit Info diff --git a/.gitea/workflows/code-review.yaml b/.gitea/workflows/code-review.yaml index e5695d4f..d422f636 100644 --- a/.gitea/workflows/code-review.yaml +++ b/.gitea/workflows/code-review.yaml @@ -31,7 +31,7 @@ jobs: fetch-depth: 50 - name: Guard Workflow Secret Surfaces - run: ruby scripts/ci/check-gitea-step-env-secrets.rb + run: node scripts/ci/check-gitea-step-env-secrets.js - name: Skip Stale Main Push id: stale diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 2174b810..8ef5c07f 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -19,12 +19,12 @@ - `Seed asset_discovery_run (audit)` 維持目前 JSON SQL 寫法,避免舊版 `:'commit_sha'` psql bind syntax 錯誤復發。 - `.gitea/workflows/deploy-alerts.yaml` - deploy key 改用 heredoc 寫入,不再用 `echo "${{ secrets.DEPLOY_SSH_KEY }}"`。 -- 新增 `scripts/ci/check-gitea-step-env-secrets.rb`: +- 新增 `scripts/ci/check-gitea-step-env-secrets.js`: - 掃描 `.gitea/workflows/*.{yml,yaml}`。 - 若任何 step `env:` 或 action `with:` 出現 `${{ secrets.* }}`,直接 fail。 **verification**: -- `ruby scripts/ci/check-gitea-step-env-secrets.rb`:no Gitea step env/with secrets。 +- `node scripts/ci/check-gitea-step-env-secrets.js`:no Gitea step env/with secrets。 - `ruby -e 'require "yaml"; Dir[".gitea/workflows/*.{yml,yaml}"].each { |p| YAML.load_file(p) }; puts "workflow yaml ok"'`:pass。 - workflow `run:` block 經 `${{ ... }}` dummy 替換後 `bash -n`:pass。 - `rg -n ': \$\{\{ secrets\.' .gitea/workflows`:0 matches。 diff --git a/scripts/ci/check-gitea-step-env-secrets.js b/scripts/ci/check-gitea-step-env-secrets.js new file mode 100755 index 00000000..2a692592 --- /dev/null +++ b/scripts/ci/check-gitea-step-env-secrets.js @@ -0,0 +1,54 @@ +#!/usr/bin/env node +/* + * Guard against putting secrets in Gitea step env/with blocks. + * Gitea/act_runner logs may render those blocks before masking is effective. + */ + +const fs = require("fs"); +const path = require("path"); + +const root = path.resolve(__dirname, "../.."); +const workflowDir = path.join(root, ".gitea", "workflows"); +const violations = []; + +for (const fileName of fs.readdirSync(workflowDir).sort()) { + if (!fileName.endsWith(".yml") && !fileName.endsWith(".yaml")) { + continue; + } + + const filePath = path.join(workflowDir, fileName); + const lines = fs.readFileSync(filePath, "utf8").split(/\r?\n/); + let block = null; + + lines.forEach((line, index) => { + const indent = line.match(/^\s*/)[0].length; + const trimmed = line.trim(); + + if (block && trimmed && indent <= block.indent) { + block = null; + } + + const blockMatch = line.match(/^(\s*)(env|with):\s*$/); + if (blockMatch) { + block = { + indent: blockMatch[1].length, + section: blockMatch[2], + }; + return; + } + + if (block && line.includes("${{ secrets.")) { + violations.push(`${filePath}:${index + 1}:${block.section}`); + } + }); +} + +if (violations.length > 0) { + console.error("Gitea workflow exposes secrets through step env/with:"); + for (const violation of violations) { + console.error(` - ${violation}`); + } + process.exit(1); +} + +console.log("no Gitea step env/with secrets"); diff --git a/scripts/ci/check-gitea-step-env-secrets.rb b/scripts/ci/check-gitea-step-env-secrets.rb deleted file mode 100755 index 0172e899..00000000 --- a/scripts/ci/check-gitea-step-env-secrets.rb +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env ruby -# Guard against putting secrets in Gitea step env/with blocks. -# Gitea/act_runner logs may render those blocks before masking is effective. - -require "yaml" - -workflow_glob = File.expand_path("../../.gitea/workflows/*.{yml,yaml}", __dir__) -violations = [] - -Dir[workflow_glob].sort.each do |path| - doc = YAML.load_file(path) - jobs = doc.fetch("jobs", {}) - jobs.each do |job_name, job| - Array(job["steps"]).each_with_index do |step, index| - %w[env with].each do |section| - Hash(step[section]).each do |key, value| - next unless value.to_s.include?("${{ secrets.") - - violations << "#{path}:#{job_name}:step#{index + 1}:#{section}.#{key}" - end - end - end - end -end - -if violations.any? - warn "Gitea workflow exposes secrets through step env/with:" - violations.each { |violation| warn " - #{violation}" } - exit 1 -end - -puts "no Gitea step env/with secrets"