fix(ci): run secret surface guard with node
All checks were successful
Code Review / ai-code-review (push) Successful in 12s
All checks were successful
Code Review / ai-code-review (push) Successful in 12s
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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。
|
||||
|
||||
54
scripts/ci/check-gitea-step-env-secrets.js
Executable file
54
scripts/ci/check-gitea-step-env-secrets.js
Executable file
@@ -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");
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user