@@ -83,128 +83,128 @@ export default class Scan extends SfCommand<Output> {
8383 } ) ,
8484 } ;
8585
86- public async run ( ) : Promise < Output > {
87- const { flags } = await this . parse ( Scan ) ;
88- this . failOn = flags . failon || "error" ;
89- this . spinner . start ( "Loading Lightning Flow Scanner" ) ;
90- this . userConfig = await loadScannerOptions ( flags . config ) ;
91- if ( flags . targetusername ) {
92- await this . retrieveFlowsFromOrg ( flags . targetusername ) ;
93- }
86+ public async run ( ) : Promise < Output > {
87+ // 🚨 Step 0: Block risky APIs before anything else
88+ this . enforceSecurityGuards ( ) ;
9489
95- const targets : string [ ] = flags . files ;
90+ // Step 1: Parse CLI flags
91+ const { flags } = await this . parse ( Scan ) ;
92+ this . failOn = flags . failon || "error" ;
93+
94+ this . spinner . start ( "Loading Lightning Flow Scanner" ) ;
95+ this . userConfig = await loadScannerOptions ( flags . config ) ;
9696
97- const flowFiles = this . findFlows ( flags . directory , targets ) ;
98- this . spinner . start ( `Identified ${ flowFiles . length } flows to scan` ) ;
99- // to
100- // core.Flow
101- const parsedFlows : ParsedFlow [ ] = await parseFlows ( flowFiles ) ;
102- this . debug ( `parsed flows ${ parsedFlows . length } ` , ...parsedFlows ) ;
97+ if ( flags . targetusername ) {
98+ await this . retrieveFlowsFromOrg ( flags . targetusername ) ;
99+ }
103100
104- const tryScan = ( ) : [ ScanResult [ ] , error : Error ] => {
105- try {
106- const scanResult =
107- this . userConfig && Object . keys ( this . userConfig ) . length > 0
108- ? scanFlows ( parsedFlows , this . userConfig )
109- : scanFlows ( parsedFlows ) ;
110- return [ scanResult , null ] ;
111- } catch ( error ) {
112- return [ null , error ] ;
113- }
114- } ;
101+ const targets : string [ ] = flags . files ;
115102
116- const [ scanResults , error ] = tryScan ( ) ;
117- this . debug ( `use new scan? ${ process . env . IS_NEW_SCAN_ENABLED } ` ) ;
118- this . debug ( `error:` , inspect ( error ) ) ;
119- this . debug ( `scan results: ${ scanResults . length } ` , ...scanResults ) ;
120- this . spinner . stop ( `Scan complete` ) ;
103+ // Step 2: Find flows to scan
104+ const flowFiles = this . findFlows ( flags . directory , targets ) ;
105+ this . spinner . start ( `Identified ${ flowFiles . length } flows to scan` ) ;
121106
122- // Build results
123- const results = this . buildResults ( scanResults ) ;
107+ // Step 3: Parse flows
108+ const parsedFlows : ParsedFlow [ ] = await parseFlows ( flowFiles ) ;
109+ this . debug ( `parsed flows ${ parsedFlows . length } ` , ...parsedFlows ) ;
124110
125- if ( results . length > 0 ) {
126- const resultsByFlow = { } ;
127- for ( const result of results ) {
128- resultsByFlow [ result . flowName ] = resultsByFlow [ result . flowName ] || [ ] ;
129- resultsByFlow [ result . flowName ] . push ( result ) ;
130- }
131- for ( const resultKey in resultsByFlow ) {
132- const matchingScanResult = scanResults . find ( ( res ) => {
133- return res . flow . label === resultKey ;
134- } ) ;
135- this . styledHeader (
136- "Flow: " +
137- chalk . yellow ( resultKey ) +
138- " " +
139- chalk . bgYellow ( `(${ matchingScanResult . flow . name } .flow-meta.xml)` ) +
140- " " +
141- chalk . red ( "(" + resultsByFlow [ resultKey ] . length + " results)" ) ,
142- ) ;
143- this . log ( chalk . italic ( "Type: " + matchingScanResult . flow . type ) ) ;
144- this . log ( "" ) ;
145- // todo flow uri
146- //this.table(resultsByFlow[resultKey], ['rule', 'type', 'name', 'severity']);
147- this . table ( {
148- data : resultsByFlow [ resultKey ] ,
149- columns : [ "rule" , "type" , "name" , "severity" ] ,
150- } ) ;
151- this . debug ( `Results By Flow:
152- ${ inspect ( resultsByFlow [ resultKey ] ) } ` ) ;
153- this . log ( "" ) ;
154- }
111+ // Step 4: Run scan safely
112+ const tryScan = ( ) : [ ScanResult [ ] , error : Error ] => {
113+ try {
114+ const scanResult =
115+ this . userConfig && Object . keys ( this . userConfig ) . length > 0
116+ ? scanFlows ( parsedFlows , this . userConfig )
117+ : scanFlows ( parsedFlows ) ;
118+ return [ scanResult , null ] ;
119+ } catch ( error ) {
120+ return [ null , error ] ;
155121 }
156- this . styledHeader (
157- "Total: " +
158- chalk . red ( results . length + " Results" ) +
159- " in " +
160- chalk . yellow ( scanResults . length + " Flows" ) +
161- "." ,
162- ) ;
122+ } ;
163123
164- // Display number of errors by severity
165- for ( const severity of [ "error" , "warning" , "note" ] ) {
166- const severityCounter = this . errorCounters [ severity ] || 0 ;
167- this . log ( `- ${ severity } : ${ severityCounter } ` ) ;
124+ const [ scanResults , error ] = tryScan ( ) ;
125+ this . debug ( `use new scan? ${ process . env . IS_NEW_SCAN_ENABLED } ` ) ;
126+ this . debug ( `error:` , inspect ( error ) ) ;
127+ this . debug ( `scan results: ${ scanResults . length } ` , ...scanResults ) ;
128+ this . spinner . stop ( `Scan complete` ) ;
129+
130+ // Step 5: Build and display results
131+ const results = this . buildResults ( scanResults ) ;
132+ if ( results . length > 0 ) {
133+ const resultsByFlow = { } ;
134+ for ( const result of results ) {
135+ resultsByFlow [ result . flowName ] = resultsByFlow [ result . flowName ] || [ ] ;
136+ resultsByFlow [ result . flowName ] . push ( result ) ;
137+ }
138+ for ( const resultKey in resultsByFlow ) {
139+ const matchingScanResult = scanResults . find (
140+ ( res ) => res . flow . label === resultKey
141+ ) ;
142+ this . styledHeader (
143+ `Flow: ${ chalk . yellow ( resultKey ) } ${ chalk . bgYellow (
144+ `(${ matchingScanResult . flow . name } .flow-meta.xml)`
145+ ) } ${ chalk . red (
146+ `(${ resultsByFlow [ resultKey ] . length } results)`
147+ ) } `
148+ ) ;
149+ this . log ( chalk . italic ( "Type: " + matchingScanResult . flow . type ) ) ;
150+ this . log ( "" ) ;
151+ this . table ( {
152+ data : resultsByFlow [ resultKey ] ,
153+ columns : [ "rule" , "type" , "name" , "severity" ] ,
154+ } ) ;
155+ this . debug ( `Results By Flow: ${ inspect ( resultsByFlow [ resultKey ] ) } ` ) ;
156+ this . log ( "" ) ;
168157 }
158+ }
159+
160+ this . styledHeader (
161+ `Total: ${ chalk . red ( results . length + " Results" ) } in ${ chalk . yellow (
162+ scanResults . length + " Flows"
163+ ) } .`
164+ ) ;
165+
166+ // Step 6: Display summary
167+ for ( const severity of [ "error" , "warning" , "note" ] ) {
168+ const severityCounter = this . errorCounters [ severity ] || 0 ;
169+ this . log ( `- ${ severity } : ${ severityCounter } ` ) ;
170+ }
169171
170- // TODO CALL TO ACTION
171- this . log ( "" ) ;
172- this . log (
173- chalk . bold (
174- chalk . italic (
175- chalk . yellowBright (
176- "Be a part of our mission to champion Flow Best Practices by starring ⭐ us on GitHub:" ,
177- ) ,
178- ) ,
179- ) ,
180- ) ;
181- this . log (
172+ this . log ( "" ) ;
173+ this . log (
174+ chalk . bold (
182175 chalk . italic (
183- chalk . blueBright (
184- chalk . underline ( "https://github.com/Lightning-Flow-Scanner" ) ,
185- ) ,
186- ) ,
187- ) ;
176+ chalk . yellowBright (
177+ "Be a part of our mission to champion Flow Best Practices by starring ⭐ us on GitHub:"
178+ )
179+ )
180+ )
181+ ) ;
182+ this . log (
183+ chalk . italic (
184+ chalk . blueBright (
185+ chalk . underline (
186+ "https://github.com/Lightning-Flow-Scanner"
187+ )
188+ )
189+ )
190+ ) ;
188191
189- const status = this . getStatus ( ) ;
190- // Set status code = 1 if there are errors, that will make cli exit with code 1 when not in --json mode
191- if ( status > 0 ) {
192- process . exitCode = status ;
193- }
194- const summary = {
195- flowsNumber : scanResults . length ,
196- results : results . length ,
197- message :
198- "A total of " +
199- results . length +
200- " results have been found in " +
201- scanResults . length +
202- " flows." ,
203- errorLevelsDetails : { } ,
204- } ;
205- return { summary, status : status , results } ;
192+ // Step 7: Return status and summary
193+ const status = this . getStatus ( ) ;
194+ if ( status > 0 ) {
195+ process . exitCode = status ;
206196 }
207197
198+ const summary = {
199+ flowsNumber : scanResults . length ,
200+ results : results . length ,
201+ message : `A total of ${ results . length } results have been found in ${ scanResults . length } flows.` ,
202+ errorLevelsDetails : { } ,
203+ } ;
204+
205+ return { summary, status : status , results } ;
206+ }
207+
208208 private findFlows ( directory : string , sourcepath : string [ ] ) {
209209 // List flows that will be scanned
210210 let flowFiles ;
@@ -278,6 +278,29 @@ export default class Scan extends SfCommand<Output> {
278278 return errors ;
279279 }
280280
281+ private enforceSecurityGuards ( ) : void {
282+ // 🔒 Monkey-patch eval
283+ ( global as any ) . eval = function ( ) : never {
284+ throw new Error ( "Blocked use of eval() in lightning-flow-scanner-core" ) ;
285+ } ;
286+
287+ // 🔒 Monkey-patch Function constructor
288+ ( global as any ) . Function = function ( ) : never {
289+ throw new Error ( "Blocked use of Function constructor in lightning-flow-scanner-core" ) ;
290+ } ;
291+
292+ // 🔒 Intercept dynamic import() calls
293+ const dynamicImport = ( globalThis as any ) . import ;
294+ ( globalThis as any ) . import = async ( ...args : any [ ] ) : Promise < any > => {
295+ const specifier = args [ 0 ] ;
296+ if ( typeof specifier === "string" && specifier . startsWith ( "http" ) ) {
297+ throw new Error ( `Blocked remote import: ${ specifier } ` ) ;
298+ }
299+ return dynamicImport ( ...args ) ;
300+ } ;
301+ }
302+
303+
281304 private async retrieveFlowsFromOrg ( targetusername : string ) {
282305 let errored = false ;
283306 this . spinner . start ( chalk . yellowBright ( "Retrieving Metadata..." ) ) ;
0 commit comments