@@ -51,43 +51,83 @@ function activeYamlLines(source) {
5151 . filter ( ( line ) => line . trim ( ) && ! line . trimStart ( ) . startsWith ( '#' ) )
5252}
5353
54+ function yamlMapping ( line ) {
55+ const match = line . match (
56+ / ^ ( \s * ) (?: ( [ " ' ] ) ( [ ^ " ' ] + ) \2| ( [ A - Z a - z _ ] [ A - Z a - z 0 - 9 _ - ] * ) ) \s * : ( .* ) $ / ,
57+ )
58+ if ( ! match ) return null
59+ return {
60+ indent : match [ 1 ] . length ,
61+ key : match [ 3 ] ?? match [ 4 ] ,
62+ quoted : Boolean ( match [ 2 ] ) ,
63+ value : match [ 5 ] . replace ( / \s + # .* $ / , '' ) . trim ( ) ,
64+ }
65+ }
66+
5467function historicalWorkflowIsLowPrivilege ( source ) {
5568 const lines = activeYamlLines ( source )
56- const onIndex = lines . findIndex ( ( line ) => / ^ o n : \s * (?: # . * ) ? $ / . test ( line ) )
57- if ( onIndex === - 1 ) return false
58- const onBlock = lines . slice ( onIndex + 1 ) . findIndex ( ( line ) => / ^ \S / . test ( line ) )
59- const triggerLines =
60- onBlock === - 1 ? lines . slice ( onIndex + 1 ) : lines . slice ( onIndex + 1 , onIndex + 1 + onBlock )
69+ if ( lines . some ( ( line ) => line . includes ( '\t' ) || / ^ \s * < < \s * : / . test ( line ) ) ) return false
70+ const mappings = lines . map ( ( line , index ) => ( {
71+ index ,
72+ mapping : yamlMapping ( line ) ,
73+ } ) )
6174 if (
62- ! triggerLines . some ( ( line ) => / ^ p u l l _ r e q u e s t : \s * (?: # .* ) ? $ / . test ( line ) ) ||
63- lines . some ( ( line ) => / ^ \s * p u l l _ r e q u e s t _ t a r g e t \s * : / . test ( line ) )
75+ mappings . some ( ( { mapping } ) => mapping ?. quoted ) ||
76+ mappings . some (
77+ ( { mapping } ) =>
78+ mapping &&
79+ ( mapping . key === 'pull_request_target' ||
80+ ( mapping . key === 'permissions' && mapping . indent > 0 ) ) ,
81+ )
6482 ) {
6583 return false
6684 }
85+ const topLevelMappings = mappings . filter ( ( { mapping } ) => mapping ?. indent === 0 )
86+ const topLevelKeys = topLevelMappings . map ( ( { mapping } ) => mapping . key )
87+ if ( new Set ( topLevelKeys ) . size !== topLevelKeys . length ) return false
6788
68- const permissionIndexes = lines . flatMap ( ( line , index ) =>
69- / ^ p e r m i s s i o n s \s * : / . test ( line ) ? [ index ] : [ ] ,
89+ const blockLines = ( { index } ) => {
90+ const endOffset = lines . slice ( index + 1 ) . findIndex ( ( line ) => / ^ \S / . test ( line ) )
91+ return endOffset === - 1
92+ ? lines . slice ( index + 1 )
93+ : lines . slice ( index + 1 , index + 1 + endOffset )
94+ }
95+
96+ const onEntries = topLevelMappings . filter ( ( { mapping } ) => mapping . key === 'on' )
97+ if ( onEntries . length !== 1 || onEntries [ 0 ] . mapping . value ) return false
98+ const triggerBlock = blockLines ( onEntries [ 0 ] )
99+ const triggerBoundaryLines = triggerBlock . filter (
100+ ( line ) => ( line . match ( / ^ \s * / ) ?. [ 0 ] . length ?? 0 ) <= 2 ,
70101 )
102+ const triggerMappings = triggerBoundaryLines
103+ . map ( yamlMapping )
104+ . filter ( ( mapping ) => mapping ?. indent === 2 )
71105 if (
72- permissionIndexes . length !== 1 ||
73- ! / ^ p e r m i s s i o n s : \s * (?: # .* ) ? $ / . test ( lines [ permissionIndexes [ 0 ] ] ) ||
74- lines . some ( ( line ) => / ^ \s + p e r m i s s i o n s \s * : / . test ( line ) )
106+ triggerBoundaryLines . length !== 1 ||
107+ triggerMappings . length !== 1 ||
108+ triggerMappings [ 0 ] . key !== 'pull_request' ||
109+ triggerMappings [ 0 ] . value
75110 ) {
76111 return false
77112 }
78- const permissionIndex = permissionIndexes [ 0 ]
79- const permissionBlockEnd = lines
80- . slice ( permissionIndex + 1 )
81- . findIndex ( ( line ) => / ^ \S / . test ( line ) )
82- const permissionLines =
83- permissionBlockEnd === - 1
84- ? lines . slice ( permissionIndex + 1 )
85- : lines . slice ( permissionIndex + 1 , permissionIndex + 1 + permissionBlockEnd )
113+
114+ const permissionEntries = topLevelMappings . filter (
115+ ( { mapping } ) => mapping . key === 'permissions' ,
116+ )
117+ if ( permissionEntries . length !== 1 || permissionEntries [ 0 ] . mapping . value ) return false
118+ const permissionLines = blockLines ( permissionEntries [ 0 ] )
86119 const permissions = new Map ( )
87120 for ( const line of permissionLines ) {
88- const match = line . match ( / ^ ( [ a - z - ] + ) : \s * ( r e a d | n o n e ) \s * (?: # .* ) ? $ / )
89- if ( ! match || permissions . has ( match [ 1 ] ) ) return false
90- permissions . set ( match [ 1 ] , match [ 2 ] )
121+ const mapping = yamlMapping ( line )
122+ if (
123+ ! mapping ||
124+ mapping . indent !== 2 ||
125+ ! [ 'read' , 'none' ] . includes ( mapping . value ) ||
126+ permissions . has ( mapping . key )
127+ ) {
128+ return false
129+ }
130+ permissions . set ( mapping . key , mapping . value )
91131 }
92132 return permissions . get ( 'contents' ) === 'read'
93133}
0 commit comments