| name: Comment Check Amalgamation |
| on: |
| workflow_run: |
| workflows: ["Check amalgamation"] |
| types: |
| - completed |
| |
| permissions: |
| contents: read |
| |
| jobs: |
| comment: |
| if: ${{ github.event.workflow_run.conclusion == 'failure' }} |
| runs-on: ubuntu-latest |
| permissions: |
| contents: read |
| actions: read |
| issues: read |
| pull-requests: write |
| steps: |
| - name: Harden Runner |
| uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 |
| with: |
| egress-policy: audit |
| |
| - name: 'Download artifact' |
| id: download |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 |
| with: |
| script: | |
| var artifacts = await github.rest.actions.listWorkflowRunArtifacts({ |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| run_id: ${{github.event.workflow_run.id }}, |
| }); |
| var matchArtifact = artifacts.data.artifacts.filter((artifact) => { |
| return artifact.name == "pr" |
| })[0]; |
| var download = await github.rest.actions.downloadArtifact({ |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| artifact_id: matchArtifact.id, |
| archive_format: 'zip', |
| }); |
| var fs = require('fs'); |
| fs.writeFileSync('${{github.workspace}}/pr.zip', Buffer.from(download.data)); |
| |
| var hasPatch = artifacts.data.artifacts.some((artifact) => artifact.name == "amalgamation-patch"); |
| core.setOutput('has_patch', String(hasPatch)); |
| # Extract the untrusted PR artifact into a dedicated empty directory and |
| # read only the two expected files by fixed path afterwards. This avoids a |
| # malicious archive overwriting workspace files or escaping via ../ paths. |
| - run: unzip -o pr.zip -d ./pr_artifact |
| |
| - name: 'Comment on PR' |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 |
| with: |
| github-token: ${{ secrets.GITHUB_TOKEN }} |
| script: | |
| var fs = require('fs'); |
| // Both values come from a fork-triggered workflow and are therefore |
| // attacker-controlled. Validate them strictly before use to prevent |
| // Markdown/mention injection and bogus REST API filters. |
| const author = fs.readFileSync('./pr_artifact/author', 'utf8').trim(); |
| if (!/^[A-Za-z0-9-]{1,39}$/.test(author)) { |
| core.setFailed(`Refusing to proceed: untrusted author value '${author}' is not a valid GitHub username.`); |
| return; |
| } |
| const issue_number = Number(fs.readFileSync('./pr_artifact/number', 'utf8').trim()); |
| if (!Number.isInteger(issue_number) || issue_number <= 0) { |
| core.setFailed('Refusing to proceed: untrusted PR number is not a positive integer.'); |
| return; |
| } |
| const opts = github.rest.issues.listForRepo.endpoint.merge({ |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| creator: author, |
| state: 'all' |
| }) |
| let first = true |
| const issues = await github.paginate(opts) |
| for (const issue of issues) { |
| if (issue.number === issue_number) { |
| continue |
| } |
| if (issue.pull_request) { |
| first = false |
| break |
| } |
| } |
| const hasPatch = '${{ steps.download.outputs.has_patch }}' === 'true'; |
| const runUrl = '${{ github.event.workflow_run.html_url }}'; |
| |
| await github.rest.issues.createComment({ |
| issue_number: issue_number, |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| body: '## š“ Amalgamation check failed! š“\nThe source code has not been amalgamated and/or formatted correctly.' |
| + (hasPatch ? '\n\nš A ready-to-apply patch is attached to the [failed workflow run](' + runUrl + ') as the `amalgamation-patch` artifact.' |
| + ' Download it, then apply it locally from the repository root with:' |
| + '\n\n```shell\ngit apply amalgamation.patch\n```\n\n' |
| + 'This does not require installing astyle yourself.' |
| : '') |
| + (first ? '\n\n@' + author + ' Please read and follow the [Contribution Guidelines]' |
| + '(https://github.com/nlohmann/json/blob/develop/.github/CONTRIBUTING.md#files-to-change).' |
| : '') |
| }) |