@@ -16,7 +16,7 @@ const RELEASE_WORKFLOW_STEP = "Run Release Please";
1616const PUBLISH_OPERATION = "release" ;
1717const NPM_TAG_POLICY_ID = 55718965 ;
1818const PUBLISH_WORKFLOW_CONTRACT_SHA256 =
19- "43f70219c4b8deed5a68a7a369821cc18f891119b373134fda8ca46fc7080e24 " ;
19+ "088366a4c0fdb060b330249f4f8db72c97b10925f7f737e47886dffce4dee9be " ;
2020const PUBLISH_HANDOFF_IF =
2121 "vars.RELEASE_PLEASE_ENABLED == 'true' && github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' && github.event.workflow_run.head_branch == 'main'" ;
2222const PUBLISH_RESULT_IF = "steps.result.outputs.has-result == 'true'" ;
@@ -504,6 +504,11 @@ export function validatePublishWorkflowContract(workflow) {
504504 "Reconfirm protected state immediately before publication" ,
505505 "Publish protected-state reconfirmation" ,
506506 ) ;
507+ requireEqual (
508+ reconfirmStep ?. id ,
509+ "pre-publish" ,
510+ "Publish protected-state output identity" ,
511+ ) ;
507512 requireEqual (
508513 reconfirmStep ?. env ?. RELEASE_PLEASE_SNAPSHOT ,
509514 "${{ needs.verify.outputs.release-please-snapshot }}" ,
@@ -546,6 +551,23 @@ export function validatePublishWorkflowContract(workflow) {
546551 "${{ github.token }}" ,
547552 "Publish registry verification token" ,
548553 ) ;
554+ for ( const fragment of [
555+ "bash scripts/fetch-attestations.sh" ,
556+ 'post_exact_version="$(npm view' ,
557+ 'post_tagged_version="$(npm view' ,
558+ 'post_next_version="$(npm view cometapi@next version)"' ,
559+ 'post_registry_dist="$(npm view' ,
560+ "validatePublishedRegistryState" ,
561+ ] ) {
562+ if (
563+ typeof registryStep ?. run !== "string" ||
564+ ! registryStep . run . includes ( fragment )
565+ ) {
566+ fail (
567+ `Release workflow public-registry verification must contain ${ fragment } .` ,
568+ ) ;
569+ }
570+ }
549571 const publishSteps = jobs . publish ?. steps ?? [ ] ;
550572 if (
551573 publishSteps . indexOf ( reconfirmStep ) >= publishSteps . indexOf ( publishStep ) ||
@@ -560,6 +582,11 @@ export function validatePublishWorkflowContract(workflow) {
560582 "bash scripts/publish-artifact.sh" ,
561583 "npm publication command" ,
562584 ) ;
585+ requireEqual (
586+ publishStep ?. env ?. EXPECT_EXISTING ,
587+ "${{ steps.pre-publish.outputs.expect-existing }}" ,
588+ "npm publication replay expectation" ,
589+ ) ;
563590 requireEqual (
564591 createHash ( "sha256" ) . update ( JSON . stringify ( workflow ) ) . digest ( "hex" ) ,
565592 PUBLISH_WORKFLOW_CONTRACT_SHA256 ,
@@ -865,6 +892,62 @@ export function validateRegistryStateBeforePublish({
865892 return { exactVersion, latestVersion, nextVersion, previousVersion, version } ;
866893}
867894
895+ export function validatePublishedRegistryState ( {
896+ attestationUrl,
897+ dist,
898+ exactVersion,
899+ expectedIntegrity,
900+ nextVersion,
901+ taggedVersion,
902+ version,
903+ } ) {
904+ validateRegistryStateBeforePublish ( {
905+ exactVersion,
906+ latestVersion : taggedVersion ,
907+ nextVersion,
908+ version,
909+ } ) ;
910+ requireEqual ( exactVersion , version , "published registry exact version" ) ;
911+ requireEqual ( taggedVersion , version , "published registry dist-tag" ) ;
912+ requireEqual (
913+ attestationUrl ,
914+ `https://registry.npmjs.org/-/npm/v1/attestations/cometapi@${ version } ` ,
915+ "published registry attestation URL identity" ,
916+ ) ;
917+ if ( dist === null || typeof dist !== "object" || Array . isArray ( dist ) ) {
918+ fail ( "Release workflow published registry dist must be an object." ) ;
919+ }
920+ if (
921+ typeof expectedIntegrity !== "string" ||
922+ ! expectedIntegrity . startsWith ( "sha512-" )
923+ ) {
924+ fail ( "Release workflow expected registry integrity must use sha512." ) ;
925+ }
926+ requireEqual (
927+ dist . integrity ,
928+ expectedIntegrity ,
929+ "published registry integrity" ,
930+ ) ;
931+ requireEqual (
932+ dist . attestations ?. url ,
933+ attestationUrl ,
934+ "published registry attestation URL" ,
935+ ) ;
936+ requireEqual (
937+ dist . attestations ?. provenance ?. predicateType ,
938+ "https://slsa.dev/provenance/v1" ,
939+ "published registry provenance predicate" ,
940+ ) ;
941+ return {
942+ attestationUrl,
943+ exactVersion,
944+ integrity : dist . integrity ,
945+ nextVersion,
946+ taggedVersion,
947+ version,
948+ } ;
949+ }
950+
868951export function validateRegistryProvenance ( {
869952 attestations,
870953 commit,
0 commit comments