diff --git a/.github/workflows/comment_check_amalgamation.yml b/.github/workflows/comment_check_amalgamation.yml index 6ff9fb1e8..30ae044e3 100644 --- a/.github/workflows/comment_check_amalgamation.yml +++ b/.github/workflows/comment_check_amalgamation.yml @@ -47,7 +47,10 @@ jobs: var hasPatch = artifacts.data.artifacts.some((artifact) => artifact.name == "amalgamation-patch"); core.setOutput('has_patch', String(hasPatch)); - - run: unzip pr.zip + # 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 @@ -55,8 +58,19 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} script: | var fs = require('fs'); - const author = fs.readFileSync('./author') - const issue_number = Number(fs.readFileSync('./number')); + // 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, diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index f1e3360a0..d4b4aa680 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -41,12 +41,23 @@ jobs: with: persist-credentials: false - # Scan code using project's configuration on https://semgrep.dev/manage - - uses: returntocorp/semgrep-action@713efdd345f3035192eaa63f56867b88e63e4e5d - with: - publishToken: ${{ secrets.SEMGREP_APP_TOKEN }} - publishDeployment: ${{ secrets.SEMGREP_DEPLOYMENT_ID }} - generateSarif: "1" + # The former returntocorp/semgrep-action is deprecated (the org was renamed + # to semgrep/*); the maintained approach is to install the CLI and invoke + # it directly. We use `semgrep scan` (not `semgrep ci`, which requires a + # login token): with no SEMGREP_APP_TOKEN configured this is exactly what + # the old action fell back to, running community rules with no token. + # SEMGREP_APP_TOKEN is still passed through so registry auth works if a + # token is ever added. + - name: Install Semgrep + run: python3 -m pip install --user semgrep + + # `semgrep scan --sarif` always exits 0 even with findings; continue-on-error + # is a safety net so the SARIF upload still runs if the scan itself errors. + - name: Run Semgrep + run: semgrep scan --config auto --sarif --output=semgrep.sarif + continue-on-error: true + env: + SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} # Upload SARIF file generated in previous step - name: Upload SARIF file