Skip to content

Push Cargo.lock to release-please PR #1021

Push Cargo.lock to release-please PR

Push Cargo.lock to release-please PR #1021

name: Push Cargo.lock to release-please PR
# Privileged half of the release-please-PR Cargo.lock pipeline.
#
# Fires after the unprivileged `Build Cargo.lock for release-please
# PR` workflow finishes. Downloads the Cargo.lock artifact it
# produced, validates it, then pushes the file to the PR branch via
# the GitHub Git Data API.
#
# **No `actions/checkout` of the PR branch.** **No `cargo` /
# `npm` / `bun` invocation.** The only code that runs in this
# privileged context is the artifact unzip + a github-script step
# that talks to the API. This is what closes CodeQL's
# `actions/untrusted-checkout/critical` rule: a privileged
# workflow_run job must not execute arbitrary code from a non-
# default ref.
"on":
workflow_run:
workflows: ["Build Cargo.lock for release-please PR"]
types: [completed]
permissions:
contents: write
actions: read
pull-requests: read
jobs:
push:
if: >-
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- name: Download artifact from upstream workflow run
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
const match = artifacts.data.artifacts.find(
(a) => a.name === 'release-please-lockfile',
);
if (!match) {
core.setFailed('No release-please-lockfile artifact found on upstream run');
return;
}
const download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: match.id,
archive_format: 'zip',
});
const fs = require('fs');
fs.writeFileSync('artifact.zip', Buffer.from(download.data));
- name: Unzip + validate artifact
# Bail fast (and loudly) on anything unexpected β€” a privileged
# workflow consuming an artifact from an unprivileged run must
# treat that artifact as untrusted input. We accept it only
# when every expected file is present, the Cargo.lock starts
# with cargo's canonical header, and the file size is within
# a sane envelope (1 KB-5 MB).
run: |
mkdir -p artifact
unzip -q artifact.zip -d artifact
for f in Cargo.lock pr_number pr_branch pr_head_sha; do
test -f "artifact/$f" || { echo "::error::missing $f"; exit 1; }
done
head -1 artifact/Cargo.lock | grep -qE '^# This file is automatically @generated by Cargo' || {
echo "::error::Cargo.lock does not start with the cargo header"
exit 1
}
size=$(stat -c%s artifact/Cargo.lock)
if [ "$size" -lt 1000 ] || [ "$size" -gt 5000000 ]; then
echo "::error::Cargo.lock size suspicious ($size bytes)"
exit 1
fi
- name: Validate PR identity + push Cargo.lock via API
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const fs = require('fs');
const prNumber = parseInt(
fs.readFileSync('artifact/pr_number', 'utf8').trim(),
10,
);
const branch = fs.readFileSync('artifact/pr_branch', 'utf8').trim();
const artifactHeadSha = fs
.readFileSync('artifact/pr_head_sha', 'utf8')
.trim();
// Defensive parse β€” the artifact came from an unprivileged
// build that ran on attacker-controllable code paths.
if (!Number.isInteger(prNumber) || prNumber <= 0) {
core.setFailed(`invalid PR number: ${prNumber}`);
return;
}
if (!/^release-please--[A-Za-z0-9._\/-]+$/.test(branch)) {
core.setFailed(`branch name does not match release-please pattern: ${branch}`);
return;
}
if (!/^[0-9a-f]{40}$/.test(artifactHeadSha)) {
core.setFailed(`invalid head SHA: ${artifactHeadSha}`);
return;
}
// Verify the PR is still open, still authored by the
// release-please bot, and its head ref still matches the
// artifact's claim.
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
});
if (pr.data.state !== 'open') {
core.info(`PR #${prNumber} is ${pr.data.state} β€” nothing to do`);
return;
}
if (pr.data.user.login !== 'github-actions[bot]') {
core.setFailed(
`PR author is ${pr.data.user.login}, expected github-actions[bot]`,
);
return;
}
if (pr.data.head.ref !== branch) {
core.setFailed(
`PR head ref ${pr.data.head.ref} does not match artifact branch ${branch}`,
);
return;
}
// If the PR branch has moved since the artifact was built,
// the Cargo.lock we have was generated against a now-stale
// Cargo.toml. Bail and let the next build-workflow run
// produce a fresh artifact.
const currentHead = pr.data.head.sha;
if (currentHead !== artifactHeadSha) {
core.info(
`PR head moved (${artifactHeadSha} -> ${currentHead}); skipping push, the next build will pick this up`,
);
return;
}
// Create a blob with the new Cargo.lock content.
const content = fs.readFileSync('artifact/Cargo.lock');
const blob = await github.rest.git.createBlob({
owner: context.repo.owner,
repo: context.repo.repo,
content: content.toString('base64'),
encoding: 'base64',
});
// Build a new tree that swaps src-tauri/Cargo.lock for our
// blob, leaving the rest of the parent tree untouched.
const parent = await github.rest.git.getCommit({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: currentHead,
});
const tree = await github.rest.git.createTree({
owner: context.repo.owner,
repo: context.repo.repo,
base_tree: parent.data.tree.sha,
tree: [
{
path: 'src-tauri/Cargo.lock',
mode: '100644',
type: 'blob',
sha: blob.data.sha,
},
],
});
// Idempotent: if Cargo.lock is already in sync, the new
// tree SHA equals the parent's and we bail.
if (tree.data.sha === parent.data.tree.sha) {
core.info('Cargo.lock already in sync with Cargo.toml β€” nothing to push');
return;
}
// Commit + fast-forward the branch ref.
const commit = await github.rest.git.createCommit({
owner: context.repo.owner,
repo: context.repo.repo,
message: 'chore: bump Cargo.lock',
tree: tree.data.sha,
parents: [currentHead],
});
await github.rest.git.updateRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `heads/${branch}`,
sha: commit.data.sha,
});
core.info(`Pushed Cargo.lock commit ${commit.data.sha} to ${branch}`);