fix(release): bind privileged checkout to verified event commit

Preserve contributor history and current-main behavior while resolving the exact reviewed follow-up.

Source-PR: https://github.com/affaan-m/ECC/pull/3195
Source-Parent: ffb69744b0
Review-Manifest-SHA256: f732b9a4ceb5adced2990690c3857227088bbcccbb5114fcaaa581ecf16eb13f
This commit is contained in:
affaan-m
2026-09-28 00:54:51 -04:00
parent ffb69744b0
commit f36b5d1fda
2 changed files with 37 additions and 2 deletions
+7 -1
View File
@@ -205,10 +205,16 @@ jobs:
ECC_RELEASE_SHA256: ${{ needs.verify.outputs.package_sha256 }}
run: node -e "const crypto = require('crypto'); const fs = require('fs'); const file = process.env.ECC_RELEASE_PACKAGE; const expected = process.env.ECC_RELEASE_SHA256; if (!/^ecc-universal-[0-9A-Za-z.+-]+\.tgz$/.test(file || '')) throw new Error('Unexpected packed filename'); if (!/^[a-f0-9]{64}$/.test(expected || '')) throw new Error('Invalid packed SHA-256'); const archives = fs.readdirSync('.').filter(name => name.endsWith('.tgz')); if (archives.length !== 1 || archives[0] !== file) throw new Error('Expected exactly one downloaded archive'); const actual = crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex'); if (actual !== expected) throw new Error('Downloaded publish artifact SHA-256 mismatch')"
- name: Bind gate source to triggering commit
env:
EVENT_SHA: ${{ github.sha }}
VERIFIED_RELEASE_SHA: ${{ needs.verify.outputs.release_sha }}
run: node -e "const actual = process.env.EVENT_SHA; const expected = process.env.VERIFIED_RELEASE_SHA; if (typeof actual !== 'string' || actual.length !== 40 || !/^[a-f0-9]{40}$/.test(actual) || expected !== actual) throw new Error('Verified release differs from triggering commit')"
- name: Checkout verified gate source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.verify.outputs.release_sha }}
ref: ${{ github.sha }}
path: release-gate-source
persist-credentials: false
sparse-checkout: scripts/ci/verify-release-gates.js
@@ -4,6 +4,7 @@ const assert = require('assert');
const fs = require('fs');
const path = require('path');
const yaml = require('js-yaml');
const vm = require('vm');
const repoRoot = path.resolve(__dirname, '..', '..');
const workflowPaths = [
@@ -583,7 +584,8 @@ for (const workflowPath of workflowPaths) {
const publish = workflow.jobs.publish;
assert.deepStrictEqual(publish.permissions, { contents: 'write', 'id-token': 'write' });
const checkout = publish.steps.find(step => step.uses?.startsWith('actions/checkout@'));
assert.strictEqual(checkout.with.ref, '${{ needs.verify.outputs.release_sha }}');
assert.strictEqual(checkout.with.ref, workflowPath === '.github/workflows/release.yml'
? '${{ github.sha }}' : '${{ needs.verify.outputs.release_sha }}');
assert.strictEqual(checkout.with['persist-credentials'], false);
assert.strictEqual(checkout.with.path, 'release-gate-source');
const index = publish.steps.findIndex(step => step.name === 'Recheck verified tag before publish');
@@ -597,6 +599,33 @@ for (const workflowPath of workflowPaths) {
});
}
test('tag-push publish binds its event checkout to the verified release before loading code', () => {
const workflow = yaml.load(load('.github/workflows/release.yml'));
assert.deepStrictEqual(workflow.on, { push: { tags: ['v*'] } });
const steps = workflow.jobs.publish.steps;
const binding = steps.findIndex(step => step.name === 'Bind gate source to triggering commit');
const checkout = steps.findIndex(step => step.name === 'Checkout verified gate source');
assert.ok(binding >= 0 && binding < checkout);
assert.strictEqual(steps[binding].if, undefined, 'binding must fail the job rather than silently skip');
assert.strictEqual(steps[checkout].if, undefined);
assert.deepStrictEqual(steps[binding].env, {
EVENT_SHA: '${{ github.sha }}',
VERIFIED_RELEASE_SHA: '${{ needs.verify.outputs.release_sha }}',
});
const program = /^node -e "([^\n"]+)"$/.exec(steps[binding].run);
assert.ok(program, 'binding must be a fixed environment-only Node check');
const execute = env => vm.runInNewContext(program[1], { process: { env: Object.freeze(env) } }, { timeout: 100 });
assert.doesNotThrow(() => execute({ EVENT_SHA: releaseSha, VERIFIED_RELEASE_SHA: releaseSha }));
for (const env of [
{},
{ EVENT_SHA: releaseSha },
{ VERIFIED_RELEASE_SHA: releaseSha },
{ EVENT_SHA: releaseSha, VERIFIED_RELEASE_SHA: 'b'.repeat(40) },
{ EVENT_SHA: 'invalid', VERIFIED_RELEASE_SHA: 'invalid' },
{ EVENT_SHA: releaseSha + '\n', VERIFIED_RELEASE_SHA: releaseSha + '\n' },
]) assert.throws(() => execute(env), /Verified release differs from triggering commit/);
});
test('reusable release requires its input to resolve through the tag namespace', () => {
const source = load('.github/workflows/reusable-release.yml');
const verify = jobBlock(source, 'verify', 'lifecycle');