From 9fe2f153d521a9e62426a11e07bbc788ad4e989a Mon Sep 17 00:00:00 2001 From: Lauren Tan Date: Mon, 24 Mar 2025 18:16:13 -0400 Subject: [PATCH 1/2] [scripts] Verify artifact integrity when downloading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Uses https://cli.github.com/manual/gh_attestation_verify to verify that the downloaded artifact matches the attestation generated during the build process in runtime_commit_artifacts. Example: On a workflow run of runtime_build_and_test.yml with no attestations: ``` $ scripts/release/download-experimental-build.js --commit=ea5f065745b777cb41cc9e54a3b29ed8c727a574 Command failed: gh attestation verify artifacts_combined.zip --repo=facebook/react Error: failed to fetch attestations from facebook/react: HTTP 404: Not Found (https://api.github.com/repos/facebook/react/attestations/sha256:7adba0992ba477a927aad5a07f95ee2deb7d18427c84279d33fc40a3bc28ebaa?per_page=30) `gh attestation verify artifacts_combined.zip --repo=facebook/react` (exited with error code 1) ``` On one which does: ``` $ scripts/release/download-experimental-build.js --commit=12e85d74c1c233cdc2f3228a97473a4435d50c3b ✓ Downloading artifacts from GitHub for commit 12e85d74c1c233cdc2f3228a97473a4435d50c3b) 10.5 secs An experimental build has been downloaded! You can download this build again by running: scripts/download-experimental-build.js --commit=12e85d74c1c233cdc2f3228a97473a4435d50c3b ``` --- .../download-build-artifacts.js | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/scripts/release/shared-commands/download-build-artifacts.js b/scripts/release/shared-commands/download-build-artifacts.js index cf7301688027a..23e3541110fe1 100644 --- a/scripts/release/shared-commands/download-build-artifacts.js +++ b/scripts/release/shared-commands/download-build-artifacts.js @@ -3,8 +3,9 @@ const {join} = require('path'); const theme = require('../theme'); const {exec} = require('child-process-promise'); -const {existsSync, readFileSync} = require('fs'); +const {existsSync, mkdtempSync, readFileSync} = require('fs'); const {logPromise} = require('../utils'); +const os = require('os'); if (process.env.GH_TOKEN == null) { console.log( @@ -21,6 +22,15 @@ const GITHUB_HEADERS = ` -H "Authorization: Bearer ${process.env.GH_TOKEN}" \ -H "X-GitHub-Api-Version: 2022-11-28"`.trim(); +async function executableIsAvailable(name) { + try { + await exec(`which ${name}`); + return true; + } catch (_error) { + return false; + } +} + function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } @@ -78,10 +88,27 @@ async function getArtifact(workflowRunId, artifactName) { async function processArtifact(artifact, commit, releaseChannel) { // Download and extract artifact const cwd = join(__dirname, '..', '..', '..'); + const tmpDir = mkdtempSync(join(os.tmpdir(), 'react_')); await exec(`rm -rf ./build`, {cwd}); await exec( - `curl -L ${GITHUB_HEADERS} ${artifact.archive_download_url} \ - > a.zip && unzip a.zip -d . && rm a.zip build2.tgz && tar -xvzf build.tgz && rm build.tgz`, + `curl -L ${GITHUB_HEADERS} ${artifact.archive_download_url} > artifacts_combined.zip`, + { + cwd: tmpDir, + } + ); + + // Use https://cli.github.com/manual/gh_attestation_verify to verify artifact + if (executableIsAvailable('gh')) { + await exec( + `gh attestation verify artifacts_combined.zip --repo=${OWNER}/${REPO}`, + { + cwd: tmpDir, + } + ); + } + + await exec( + `unzip ${tmpDir}/artifacts_combined.zip -d . && rm build2.tgz && tar -xvzf build.tgz && rm build.tgz`, { cwd, } From 4c4edcdcb45facd1caf285d5803eb54ce138af36 Mon Sep 17 00:00:00 2001 From: Lauren Tan Date: Mon, 24 Mar 2025 18:16:13 -0400 Subject: [PATCH 2/2] [ci] Fix missing permissions for prereleases Missed these earlier. --- .github/workflows/runtime_prereleases.yml | 3 +++ .github/workflows/runtime_prereleases_manual.yml | 6 ++++++ .github/workflows/runtime_prereleases_nightly.yml | 6 ++++++ 3 files changed, 15 insertions(+) diff --git a/.github/workflows/runtime_prereleases.yml b/.github/workflows/runtime_prereleases.yml index 147ec0a496dc8..19cc47f1ce508 100644 --- a/.github/workflows/runtime_prereleases.yml +++ b/.github/workflows/runtime_prereleases.yml @@ -29,6 +29,9 @@ jobs: publish_prerelease: name: Publish prelease (${{ inputs.release_channel }}) ${{ inputs.commit_sha }} @${{ inputs.dist_tag }} runs-on: ubuntu-latest + permissions: + # We use github.token to download the build artifact from a previous runtime_build_and_test.yml run + actions: read steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 diff --git a/.github/workflows/runtime_prereleases_manual.yml b/.github/workflows/runtime_prereleases_manual.yml index 77d3fd5e4304b..3cf2786cce68f 100644 --- a/.github/workflows/runtime_prereleases_manual.yml +++ b/.github/workflows/runtime_prereleases_manual.yml @@ -16,6 +16,9 @@ jobs: publish_prerelease_canary: name: Publish to Canary channel uses: facebook/react/.github/workflows/runtime_prereleases.yml@main + permissions: + # We use github.token to download the build artifact from a previous runtime_build_and_test.yml run + actions: read with: commit_sha: ${{ inputs.prerelease_commit_sha }} release_channel: stable @@ -36,6 +39,9 @@ jobs: publish_prerelease_experimental: name: Publish to Experimental channel uses: facebook/react/.github/workflows/runtime_prereleases.yml@main + permissions: + # We use github.token to download the build artifact from a previous runtime_build_and_test.yml run + actions: read # NOTE: Intentionally running these jobs sequentially because npm # will sometimes fail if you try to concurrently publish two # different versions of the same package, even if they use different diff --git a/.github/workflows/runtime_prereleases_nightly.yml b/.github/workflows/runtime_prereleases_nightly.yml index 4622e15f55ad5..2405283ded116 100644 --- a/.github/workflows/runtime_prereleases_nightly.yml +++ b/.github/workflows/runtime_prereleases_nightly.yml @@ -14,6 +14,9 @@ jobs: publish_prerelease_canary: name: Publish to Canary channel uses: facebook/react/.github/workflows/runtime_prereleases.yml@main + permissions: + # We use github.token to download the build artifact from a previous runtime_build_and_test.yml run + actions: read with: commit_sha: ${{ github.sha }} release_channel: stable @@ -24,6 +27,9 @@ jobs: publish_prerelease_experimental: name: Publish to Experimental channel uses: facebook/react/.github/workflows/runtime_prereleases.yml@main + permissions: + # We use github.token to download the build artifact from a previous runtime_build_and_test.yml run + actions: read # NOTE: Intentionally running these jobs sequentially because npm # will sometimes fail if you try to concurrently publish two # different versions of the same package, even if they use different