11export const CODACY_FOLDER_NAME = '.codacy' ;
22import { exec } from 'child_process' ;
33import { Log } from 'sarif' ;
4+ import * as path from 'path' ;
45
56// Set a larger buffer size (10MB)
67const MAX_BUFFER_SIZE = 1024 * 1024 * 10 ;
@@ -38,8 +39,39 @@ export abstract class CodacyCli {
3839 this . _cliCommand = command ;
3940 }
4041
42+ protected isPathSafe ( filePath : string ) : boolean {
43+ // Reject null bytes (always a security risk)
44+ if ( filePath . includes ( '\0' ) ) {
45+ return false ;
46+ }
47+
48+ // Reject all control characters (including newline, tab, carriage return)
49+ // as they are very unusual for file names
50+ // eslint-disable-next-line no-control-regex -- Intentionally checking for control chars to reject them for security
51+ const hasUnsafeControlChars = / [ \x00 - \x1F \x7F ] / . test ( filePath ) ;
52+ if ( hasUnsafeControlChars ) {
53+ return false ;
54+ }
55+
56+ // Resolve the path to check for path traversal attempts
57+ const resolvedPath = path . resolve ( this . rootPath , filePath ) ;
58+ const normalizedRoot = path . normalize ( this . rootPath ) ;
59+ // Check if the resolved path is within the workspace
60+ if ( ! resolvedPath . startsWith ( normalizedRoot ) ) {
61+ return false ;
62+ }
63+
64+ return true ;
65+ }
66+
4167 protected preparePathForExec ( path : string ) : string {
42- return path ;
68+ // Validate path security before escaping
69+ if ( ! this . isPathSafe ( path ) ) {
70+ throw new Error ( `Unsafe file path rejected: ${ path } ` ) ;
71+ }
72+
73+ // Escape special characters for shell execution
74+ return path . replace ( / ( [ \s ' " \\ ; & | ` $ ( ) [ \] { } * ? ~ < > ] ) / g, '\\$1' ) ;
4375 }
4476
4577 protected execAsync (
@@ -48,11 +80,20 @@ export abstract class CodacyCli {
4880 ) : Promise < { stdout : string ; stderr : string } > {
4981 // stringyfy the args
5082 const argsString = Object . entries ( args || { } )
51- . map ( ( [ key , value ] ) => `--${ key } ${ value } ` )
83+ . map ( ( [ key , value ] ) => {
84+ // Validate argument key (should be alphanumeric and hyphens only)
85+ if ( ! / ^ [ a - z A - Z 0 - 9 - ] + $ / . test ( key ) ) {
86+ throw new Error ( `Invalid argument key: ${ key } ` ) ;
87+ }
88+
89+ // Escape the value to prevent injection
90+ const escapedValue = value . replace ( / ( [ \s ' " \\ ; & | ` $ ( ) [ \] { } * ? ~ < > ] ) / g, '\\$1' ) ;
91+ return `--${ key } ${ escapedValue } ` ;
92+ } )
5293 . join ( ' ' ) ;
5394
54- // Add the args to the command and remove any shell metacharacters
55- const cmd = `${ command } ${ argsString } ` . trim ( ) . replace ( / [ ; & | ` $ ] / g , '' ) ;
95+ // Build the command - no need to strip characters since we've already escaped them properly
96+ const cmd = `${ command } ${ argsString } ` . trim ( ) ;
5697
5798 return new Promise ( ( resolve , reject ) => {
5899 exec (
0 commit comments