diff --git a/.github/workflows/auto-merge-journal.yml b/.github/workflows/auto-merge-journal.yml index 3b52b93..e48d660 100644 --- a/.github/workflows/auto-merge-journal.yml +++ b/.github/workflows/auto-merge-journal.yml @@ -12,6 +12,10 @@ jobs: permissions: contents: write pull-requests: write + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + PR_TITLE: ${{ github.event.pull_request.title }} + PR_SUBJECT: ${{ format('[field-journal] {0}', github.event.pull_request.title) }} steps: - name: Checkout PR @@ -25,7 +29,7 @@ jobs: set -e # 1. 只允许修改 field-journal 目录下的 .md 文件 - CHANGED_FILES=$(gh pr diff ${{ github.event.pull_request.number }} --name-only) + CHANGED_FILES=$(gh pr diff "$PR_NUMBER" --name-only) echo "Changed files:" echo "$CHANGED_FILES" @@ -145,16 +149,16 @@ jobs: - name: Auto-merge if valid if: steps.validate.outputs.valid == 'true' run: | - gh pr merge ${{ github.event.pull_request.number }} \ + gh pr merge "$PR_NUMBER" \ --auto --squash \ - --subject "[field-journal] ${{ github.event.pull_request.title }}" + --subject "$PR_SUBJECT" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Comment on failure if: steps.validate.outputs.valid == 'false' run: | - gh pr comment ${{ github.event.pull_request.number }} \ + gh pr comment "$PR_NUMBER" \ --body "❌ **自动审核未通过** 本 PR 未通过安全检查,无法自动合并。可能的原因: diff --git a/skills/scripts/test-workflow-title-safety.ps1 b/skills/scripts/test-workflow-title-safety.ps1 new file mode 100644 index 0000000..9cf468b --- /dev/null +++ b/skills/scripts/test-workflow-title-safety.ps1 @@ -0,0 +1,38 @@ +#Requires -Version 5.1 +param([string] $WorkflowPath = '') + +$ErrorActionPreference = 'Stop' + +if ([string]::IsNullOrWhiteSpace($WorkflowPath)) { + $WorkflowPath = Join-Path (Split-Path -Parent (Split-Path -Parent $PSScriptRoot)) '.github\workflows\auto-merge-journal.yml' +} + +$text = Get-Content -LiteralPath $WorkflowPath -Raw -Encoding UTF8 +$failures = New-Object System.Collections.Generic.List[string] + +function Check([bool] $Condition, [string] $Message) { + if ($Condition) { + Write-Host "[OK] $Message" -ForegroundColor Green + } else { + Write-Host "[FAIL] $Message" -ForegroundColor Red + [void]$failures.Add($Message) + } +} + +Check ($text -match '(?m)^\s+PR_NUMBER:\s*\$\{\{\s*github\.event\.pull_request\.number\s*\}\}') 'PR number is passed through the environment' +Check ($text -match '(?m)^\s+PR_TITLE:\s*\$\{\{\s*github\.event\.pull_request\.title\s*\}\}') 'PR title is passed through the environment' +Check ($text -match '(?m)^\s+PR_SUBJECT:\s*\$\{\{\s*format\(') 'PR subject is constructed as environment data' +Check ($text -match '(?ms)gh pr merge "\$PR_NUMBER".*--subject "\$PR_SUBJECT"') 'merge command quotes the PR number and subject' +Check ($text -notmatch '(?m)--subject[^\r\n]*github\.event\.pull_request\.title') 'PR title is not interpolated into the merge command' +Check ($text -notmatch '(?m)gh pr (?:diff|merge|comment)\s+\$\{\{') 'GitHub CLI commands do not embed event expressions' + +$maliciousTitle = '$(Write-Host injected) `' + [Environment]::NewLine + '"quoted"; Get-ChildItem > should-not-run' +$subject = '[field-journal] ' + $maliciousTitle +Check ($subject -eq ('[field-journal] ' + $maliciousTitle)) 'shell metacharacters remain plain subject data' + +if ($failures.Count -gt 0) { + exit 1 +} + +Write-Host 'WORKFLOW TITLE SAFETY CHECKS PASSED' -ForegroundColor Green +exit 0