1- import type { PackageVersionInfo } from '#shared/types'
1+ import type { PackageVersionInfo , PublishTrustLevel } from '#shared/types'
22import { compare } from 'semver'
33
44export interface PublishSecurityDowngrade {
@@ -11,6 +11,18 @@ export interface PublishSecurityDowngrade {
1111type VersionWithIndex = PackageVersionInfo & {
1212 index : number
1313 timestamp : number
14+ trustRank : number
15+ }
16+
17+ const TRUST_RANK : Record < PublishTrustLevel , number > = {
18+ none : 0 ,
19+ trustedPublisher : 1 ,
20+ provenance : 2 ,
21+ }
22+
23+ function getTrustRank ( version : PackageVersionInfo ) : number {
24+ if ( version . trustLevel ) return TRUST_RANK [ version . trustLevel ]
25+ return version . hasProvenance ? TRUST_RANK . provenance : TRUST_RANK . none
1426}
1527
1628function toTimestamp ( time ?: string ) : number {
@@ -50,20 +62,27 @@ export function detectPublishSecurityDowngrade(
5062 ...version ,
5163 index,
5264 timestamp : toTimestamp ( version . time ) ,
65+ trustRank : getTrustRank ( version ) ,
5366 } ) )
5467 . sort ( sortByRecency )
5568
5669 const latest = sorted . at ( 0 )
57- if ( ! latest || latest . hasProvenance ) return null
70+ if ( ! latest ) return null
5871
59- const latestTrusted = sorted . find ( version => version . hasProvenance )
60- if ( ! latestTrusted ) return null
72+ let strongestOlder : VersionWithIndex | null = null
73+ for ( const version of sorted . slice ( 1 ) ) {
74+ if ( ! strongestOlder || version . trustRank > strongestOlder . trustRank ) {
75+ strongestOlder = version
76+ }
77+ }
78+
79+ if ( ! strongestOlder || strongestOlder . trustRank <= latest . trustRank ) return null
6180
6281 return {
6382 downgradedVersion : latest . version ,
6483 downgradedPublishedAt : latest . time ,
65- trustedVersion : latestTrusted . version ,
66- trustedPublishedAt : latestTrusted . time ,
84+ trustedVersion : strongestOlder . version ,
85+ trustedPublishedAt : strongestOlder . time ,
6786 }
6887}
6988
@@ -83,22 +102,29 @@ export function detectPublishSecurityDowngradeForVersion(
83102 ...version ,
84103 index,
85104 timestamp : toTimestamp ( version . time ) ,
105+ trustRank : getTrustRank ( version ) ,
86106 } ) )
87107 . sort ( sortByRecency )
88108
89109 const currentIndex = sorted . findIndex ( version => version . version === viewedVersion )
90110 if ( currentIndex === - 1 ) return null
91111
92112 const current = sorted . at ( currentIndex )
93- if ( ! current || current . hasProvenance ) return null
113+ if ( ! current ) return null
114+
115+ let strongestOlder : VersionWithIndex | null = null
116+ for ( const version of sorted . slice ( currentIndex + 1 ) ) {
117+ if ( ! strongestOlder || version . trustRank > strongestOlder . trustRank ) {
118+ strongestOlder = version
119+ }
120+ }
94121
95- const trustedOlder = sorted . slice ( currentIndex + 1 ) . find ( version => version . hasProvenance )
96- if ( ! trustedOlder ) return null
122+ if ( ! strongestOlder || strongestOlder . trustRank <= current . trustRank ) return null
97123
98124 return {
99125 downgradedVersion : current . version ,
100126 downgradedPublishedAt : current . time ,
101- trustedVersion : trustedOlder . version ,
102- trustedPublishedAt : trustedOlder . time ,
127+ trustedVersion : strongestOlder . version ,
128+ trustedPublishedAt : strongestOlder . time ,
103129 }
104130}
0 commit comments