Skip to content

Commit efb36e1

Browse files
committed
security: validate source run before publishing tarballs
Verify that the source-run-id passed to publish-npm.yml: 1. Came from an allowed workflow (publish-release, nightly, or publish-bumped-packages) 2. Completed successfully 3. Ran on a protected ref (main, *-stable, or a version tag) This prevents arbitrary tarballs from being published via a manual dispatch with a crafted run ID.
1 parent 81c8890 commit efb36e1

1 file changed

Lines changed: 52 additions & 0 deletions

File tree

.github/workflows/publish-npm.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,58 @@ jobs:
3636
steps:
3737
- name: Checkout
3838
uses: actions/checkout@v6
39+
- name: Validate source run
40+
uses: actions/github-script@v8
41+
with:
42+
script: |
43+
const runId = parseInt('${{ inputs.source-run-id }}', 10);
44+
const {data: run} = await github.rest.actions.getWorkflowRun({
45+
owner: context.repo.owner,
46+
repo: context.repo.repo,
47+
run_id: runId,
48+
});
49+
50+
// Only accept artifacts from known publish workflows
51+
const ALLOWED_WORKFLOWS = [
52+
'publish-release.yml',
53+
'nightly.yml',
54+
'publish-bumped-packages.yml',
55+
];
56+
const workflowName = run.path.split('/').pop();
57+
if (!ALLOWED_WORKFLOWS.includes(workflowName)) {
58+
core.setFailed(
59+
`Source run ${runId} is from workflow "${run.path}" — ` +
60+
`only runs from ${ALLOWED_WORKFLOWS.join(', ')} are accepted.`
61+
);
62+
return;
63+
}
64+
65+
// Must have completed successfully
66+
if (run.status !== 'completed' || run.conclusion !== 'success') {
67+
core.setFailed(
68+
`Source run ${runId} has status=${run.status}, conclusion=${run.conclusion}. ` +
69+
`Only successful runs are accepted.`
70+
);
71+
return;
72+
}
73+
74+
// Must be on a protected ref (main, *-stable, or a version tag)
75+
const ref = run.head_branch || '';
76+
const isProtectedRef =
77+
ref === 'main' ||
78+
/^\d+-stable$/.test(ref) ||
79+
/^v0\.\d+\.\d+/.test(run.display_title);
80+
if (!isProtectedRef) {
81+
core.setFailed(
82+
`Source run ${runId} ran on ref "${ref}" which is not a protected branch or release tag.`
83+
);
84+
return;
85+
}
86+
87+
console.log(`✅ Source run ${runId} validated:`);
88+
console.log(` Workflow: ${workflowName}`);
89+
console.log(` Ref: ${ref}`);
90+
console.log(` Conclusion: ${run.conclusion}`);
3991
- name: Setup node.js
4092
uses: actions/setup-node@v6
4193
with:

0 commit comments

Comments
 (0)