@@ -11,11 +11,14 @@ import { generateMarkdownReport } from './reporting/markdown';
1111import path from 'path' ;
1212import fs from 'fs' ;
1313import chalk from 'chalk' ;
14+ import { scanConfigFile , ConfigFinding } from './scanners/configuration' ;
15+ import { scanForUnvalidatedUploads , UploadFinding } from './scanners/uploads' ;
16+ import { scanForExposedEndpoints , EndpointFinding } from './scanners/endpoints' ;
1417
1518// Define a combined finding type if needed later
1619
1720// Helper for coloring severities
18- function colorSeverity ( severity : FindingSeverity | SecretFinding [ 'severity' ] ) : string {
21+ function colorSeverity ( severity : FindingSeverity | SecretFinding [ 'severity' ] | UploadFinding [ 'severity' ] ) : string {
1922 switch ( severity ) {
2023 case 'Critical' : return chalk . red . bold ( severity ) ;
2124 case 'High' : return chalk . red ( severity ) ;
@@ -37,7 +40,7 @@ program.command('scan')
3740 . description ( 'Scan a directory for potential security issues.' )
3841 . argument ( '[directory]' , 'Directory to scan' , '.' )
3942 . option ( '-o, --output <file>' , 'Specify JSON output file path (e.g., report.json)' )
40- . option ( '-r, --report < file> ' , 'Specify Markdown report file path (e.g., report .md)' )
43+ . option ( '-r, --report [ file] ' , 'Specify Markdown report file path (defaults to VIBESAFE-REPORT .md)' )
4144 . option ( '--high-only' , 'Only report high severity issues' )
4245 . action ( async ( directory , options ) => {
4346 const rootDir = path . resolve ( directory ) ;
@@ -46,10 +49,21 @@ program.command('scan')
4649 console . log ( '(--high-only flag detected)' ) ;
4750 }
4851 if ( options . output ) {
49- console . log ( `Output will be written to: ${ options . output } ` ) ;
52+ console . log ( `JSON output will be written to: ${ options . output } ` ) ;
5053 }
51- if ( options . report ) {
52- console . log ( `Markdown report will be written to: ${ options . report } ` ) ;
54+
55+ // Determine report path based on options
56+ let reportPath : string | null = null ;
57+ if ( options . report ) { // Check if -r or --report was used
58+ if ( typeof options . report === 'string' ) {
59+ // User provided a specific filename
60+ reportPath = path . resolve ( options . report ) ;
61+ console . log ( `Markdown report will be written to: ${ reportPath } ` ) ;
62+ } else {
63+ // User used the flag without a filename, use default
64+ reportPath = path . join ( rootDir , 'VIBESAFE-REPORT.md' ) ;
65+ console . log ( `Markdown report will be written to default location: ${ reportPath } ` ) ;
66+ }
5367 }
5468
5569 // --- Moved: Check .gitignore Status ---
@@ -59,9 +73,13 @@ program.command('scan')
5973 // --- Findings Aggregation ---
6074 let allSecretFindings : SecretFinding [ ] = [ ] ;
6175 let allDependencyFindings : DependencyFinding [ ] = [ ] ;
76+ let allConfigFindings : ConfigFinding [ ] = [ ] ;
77+ let allUploadFindings : UploadFinding [ ] = [ ] ;
78+ let allEndpointFindings : EndpointFinding [ ] = [ ] ;
6279
6380 // --- File Traversal (Phase 2.2) ---
6481 const filesToScan = getFilesToScan ( directory ) ;
82+ const configFilesToScan = filesToScan . filter ( f => / \. ( j s o n | y a ? m l ) $ / i. test ( f ) ) ;
6583
6684 // --- Detect Package Manager (Phase 3.1) ---
6785 const detectedManagers = detectPackageManagers ( filesToScan , rootDir ) ;
@@ -93,6 +111,48 @@ program.command('scan')
93111 console . log ( 'Skipping CVE lookup as no dependencies were parsed.' ) ;
94112 }
95113
114+ // --- Configuration Scan (Phase 6.1) ---
115+ console . log ( `Scanning ${ configFilesToScan . length } potential config files...` ) ;
116+ configFilesToScan . forEach ( filePath => {
117+ const findings = scanConfigFile ( filePath ) ;
118+ const relativeFindings = findings . map ( f => ( { ...f , file : path . relative ( rootDir , f . file ) } ) ) ;
119+ allConfigFindings = allConfigFindings . concat ( relativeFindings ) ;
120+ } ) ;
121+
122+ // --- Upload Scan (Phase 6.2) ---
123+ // Define file extensions relevant for upload checks
124+ const UPLOAD_SCAN_EXTENSIONS = new Set ( [ '.js' , '.ts' , '.jsx' , '.tsx' , '.vue' , '.html' ] ) ;
125+ const filesForUploadScan = filesToScan . filter ( f => UPLOAD_SCAN_EXTENSIONS . has ( path . extname ( f ) . toLowerCase ( ) ) ) ;
126+ console . log ( `Scanning ${ filesForUploadScan . length } files for potential upload issues...` ) ;
127+ filesForUploadScan . forEach ( filePath => {
128+ try {
129+ const content = fs . readFileSync ( filePath , 'utf-8' ) ;
130+ const findings = scanForUnvalidatedUploads ( filePath , content ) ;
131+ const relativeFindings = findings . map ( f => ( { ...f , file : path . relative ( rootDir , f . file ) } ) ) ;
132+ allUploadFindings = allUploadFindings . concat ( relativeFindings ) ;
133+ } catch ( error : any ) {
134+ // Avoid crashing if a single file fails (e.g., read permission)
135+ console . warn ( chalk . yellow ( `Could not scan ${ path . relative ( rootDir , filePath ) } for uploads: ${ error . message } ` ) ) ;
136+ }
137+ } ) ;
138+
139+ // --- Endpoint Scan (Phase 6.3) ---
140+ // Define file extensions relevant for endpoint checks (JS/TS files)
141+ const ENDPOINT_SCAN_EXTENSIONS = new Set ( [ '.js' , '.ts' , '.jsx' , '.tsx' ] ) ;
142+ const filesForEndpointScan = filesToScan . filter ( f => ENDPOINT_SCAN_EXTENSIONS . has ( path . extname ( f ) . toLowerCase ( ) ) ) ;
143+ console . log ( `Scanning ${ filesForEndpointScan . length } files for potentially exposed endpoints...` ) ;
144+ filesForEndpointScan . forEach ( filePath => {
145+ try {
146+ const content = fs . readFileSync ( filePath , 'utf-8' ) ;
147+ const findings = scanForExposedEndpoints ( filePath , content ) ;
148+ const relativeFindings = findings . map ( f => ( { ...f , file : path . relative ( rootDir , f . file ) } ) ) ;
149+ allEndpointFindings = allEndpointFindings . concat ( relativeFindings ) ;
150+ } catch ( error : any ) {
151+ // Avoid crashing if a single file fails (e.g., read permission)
152+ console . warn ( chalk . yellow ( `Could not scan ${ path . relative ( rootDir , filePath ) } for endpoints: ${ error . message } ` ) ) ;
153+ }
154+ } ) ;
155+
96156 // Separate Info findings
97157 const infoSecretFindings = allSecretFindings . filter ( f => f . severity === 'Info' ) ;
98158 const standardSecretFindings = allSecretFindings . filter ( f => f . severity !== 'Info' ) ;
@@ -106,13 +166,31 @@ program.command('scan')
106166 ? allDependencyFindings . filter ( dep => ( dep . maxSeverity === 'High' || dep . maxSeverity === 'Critical' ) ) // Exclude errors when highOnly
107167 : allDependencyFindings . filter ( dep => dep . vulnerabilities . length > 0 || dep . error ) ;
108168
169+ // Filter config findings based on high-only flag if needed (e.g., only High CORS)
170+ const reportConfigFindings = options . highOnly
171+ ? allConfigFindings . filter ( f => f . severity === 'High' || f . severity === 'Critical' )
172+ : allConfigFindings ;
173+
174+ // Filter upload findings (adjust severity filtering as needed)
175+ const reportUploadFindings = options . highOnly
176+ ? allUploadFindings . filter ( f => f . severity === 'High' || f . severity === 'Critical' || f . severity === 'Medium' ) // Example: Include Medium for uploads even with --high-only?
177+ : allUploadFindings ;
178+
179+ // Filter endpoint findings (e.g., keep Medium+ for high-only)
180+ const reportEndpointFindings = options . highOnly
181+ ? allEndpointFindings . filter ( f => f . severity === 'High' || f . severity === 'Critical' || f . severity === 'Medium' )
182+ : allEndpointFindings ;
183+
109184 // --- NOW Check Gitignore Status ---
110185 gitignoreWarnings = checkGitignoreStatus ( rootDir ) ;
111186
112187 // --- Output Generation ---
113188 const reportData = {
114- secretFindings : reportSecretFindings , // Report only standard secrets
115- dependencyFindings : reportDependencyFindings
189+ secretFindings : reportSecretFindings ,
190+ dependencyFindings : reportDependencyFindings ,
191+ configFindings : reportConfigFindings ,
192+ uploadFindings : reportUploadFindings ,
193+ endpointFindings : reportEndpointFindings
116194 } ;
117195
118196 // Generate JSON if requested
@@ -121,6 +199,9 @@ program.command('scan')
121199 const outputJsonData = {
122200 secrets : reportSecretFindings ,
123201 dependencies : reportDependencyFindings ,
202+ configuration : reportConfigFindings ,
203+ uploads : reportUploadFindings ,
204+ endpoints : reportEndpointFindings
124205 }
125206 fs . writeFileSync ( options . output , JSON . stringify ( outputJsonData , null , 2 ) ) ;
126207 console . log ( `JSON results successfully written to ${ options . output } ` ) ;
@@ -131,19 +212,19 @@ program.command('scan')
131212 }
132213
133214 // Generate Markdown Report if requested
134- if ( options . report ) {
215+ if ( reportPath ) {
135216 try {
136217 // Await the async report generation
137218 const markdownContent = await generateMarkdownReport ( reportData ) ;
138- fs . writeFileSync ( options . report , markdownContent ) ;
139- console . log ( `Markdown report successfully written to ${ options . report } ` ) ;
219+ fs . writeFileSync ( reportPath , markdownContent ) ;
220+ console . log ( `Markdown report successfully written to ${ reportPath } ` ) ;
140221 } catch ( error ) {
141- console . error ( `Error writing Markdown report file ${ options . report } :` , error ) ;
222+ console . error ( `Error writing Markdown report file ${ reportPath } :` , error ) ;
142223 }
143224 }
144225
145226 // Print to console ONLY if neither JSON nor Markdown output was specified
146- if ( ! options . output && ! options . report ) {
227+ if ( ! options . output && ! reportPath ) {
147228
148229 // Print Configuration Warnings FIRST (after scans, before results)
149230 if ( gitignoreWarnings . length > 0 ) {
@@ -164,11 +245,14 @@ program.command('scan')
164245 } ) ;
165246 }
166247
167- // Check if any standard findings exist
248+ // Check if any standard findings exist (including config)
168249 const hasStandardSecrets = reportSecretFindings . length > 0 ;
169250 const hasDependencyIssues = reportDependencyFindings . length > 0 ;
251+ const hasConfigIssues = reportConfigFindings . length > 0 ;
252+ const hasUploadIssues = reportUploadFindings . length > 0 ;
253+ const hasEndpointIssues = reportEndpointFindings . length > 0 ;
170254
171- if ( hasStandardSecrets || hasDependencyIssues ) {
255+ if ( hasStandardSecrets || hasDependencyIssues || hasConfigIssues || hasUploadIssues || hasEndpointIssues ) {
172256 // Print standard secrets to console if found
173257 if ( hasStandardSecrets ) {
174258 console . log ( chalk . bold ( '\nPotential Secrets Found:' ) ) ;
@@ -192,6 +276,43 @@ program.command('scan')
192276 }
193277 } ) ;
194278 }
279+
280+ // Print config findings to console if found
281+ if ( hasConfigIssues ) {
282+ console . log ( chalk . bold ( '\nConfiguration Issues Found:' ) ) ;
283+ reportConfigFindings . sort ( ( a , b ) => severityToSortOrder ( b . severity ) - severityToSortOrder ( a . severity ) ) ;
284+ reportConfigFindings . forEach ( finding => {
285+ console . log ( ` - [${ colorSeverity ( finding . severity ) } ] ${ finding . type } : ${ chalk . cyan ( finding . file ) } - Key: ${ chalk . magenta ( finding . key ) } , Value: ${ chalk . yellow ( JSON . stringify ( finding . value ) ) } ` ) ;
286+ console . log ( chalk . dim ( ` > ${ finding . message } ` ) ) ;
287+ } ) ;
288+ }
289+
290+ // Print upload findings to console if found
291+ if ( hasUploadIssues ) {
292+ console . log ( chalk . bold ( '\nPotential Upload Issues Found:' ) ) ;
293+ reportUploadFindings . sort ( ( a , b ) => severityToSortOrder ( b . severity ) - severityToSortOrder ( a . severity ) ) ;
294+ reportUploadFindings . forEach ( finding => {
295+ // Customize console output for upload findings
296+ console . log ( ` - [${ colorSeverity ( finding . severity ) } ] ${ finding . type } in ${ chalk . cyan ( finding . file ) } :${ chalk . yellow ( String ( finding . line ) ) } ` ) ;
297+ console . log ( chalk . dim ( ` > ${ finding . message } ` ) ) ;
298+ if ( finding . details ) {
299+ console . log ( chalk . dim ( ` ${ finding . details } ` ) ) ;
300+ }
301+ } ) ;
302+ }
303+
304+ // Print endpoint findings to console if found
305+ if ( hasEndpointIssues ) {
306+ console . log ( chalk . bold ( '\nPotentially Exposed Endpoints Found:' ) ) ;
307+ reportEndpointFindings . sort ( ( a , b ) => severityToSortOrder ( b . severity ) - severityToSortOrder ( a . severity ) ) ;
308+ reportEndpointFindings . forEach ( finding => {
309+ console . log ( ` - [${ colorSeverity ( finding . severity ) } ] ${ finding . type } in ${ chalk . cyan ( finding . file ) } :${ chalk . yellow ( String ( finding . line ) ) } ` ) ;
310+ console . log ( chalk . dim ( ` > Path: ${ chalk . magenta ( finding . path ) } - ${ finding . message } ` ) ) ;
311+ if ( finding . details ) {
312+ console . log ( chalk . dim ( ` Context: ${ finding . details } ` ) ) ;
313+ }
314+ } ) ;
315+ }
195316 } else {
196317 // All Clear! Print positive message.
197318 // Check if we actually scanned for dependencies before saying no vulns found
@@ -210,8 +331,11 @@ program.command('scan')
210331 // Info findings should NOT affect exit code
211332 const highSeveritySecrets = reportSecretFindings . some ( f => f . severity === 'High' ) ;
212333 const highSeverityDeps = reportDependencyFindings . some ( d => d . maxSeverity === 'High' || d . maxSeverity === 'Critical' ) ;
334+ const highSeverityConfig = reportConfigFindings . some ( f => f . severity === 'High' || f . severity === 'Critical' ) ;
335+ const highSeverityUploads = reportUploadFindings . some ( f => f . severity === 'High' || f . severity === 'Critical' || f . severity === 'Medium' ) ;
336+ const highSeverityEndpoints = reportEndpointFindings . some ( f => f . severity === 'High' || f . severity === 'Critical' || f . severity === 'Medium' ) ;
213337
214- if ( options . highOnly && ( highSeveritySecrets || highSeverityDeps ) ) {
338+ if ( options . highOnly && ( highSeveritySecrets || highSeverityDeps || highSeverityConfig || highSeverityUploads || highSeverityEndpoints ) ) {
215339 console . log ( 'Exiting with code 1 due to High/Critical severity findings (--high-only specified).' ) ;
216340 process . exit ( 1 ) ;
217341 }
0 commit comments