@@ -5,6 +5,33 @@ const { execFile } = require('child_process');
55const { promisify } = require ( 'util' ) ;
66const execFileAsync = promisify ( execFile ) ;
77const EventEmitter = require ( 'events' ) ;
8+ const envPaths = require ( 'env-paths' ) ;
9+
10+ // Add path validation helper
11+ function isValidPath ( filePath ) {
12+ const resolvedPath = path . resolve ( filePath ) ;
13+ const cwd = process . cwd ( ) ;
14+ return resolvedPath . startsWith ( cwd ) ;
15+ }
16+
17+ // Add URL validation helper
18+ function isValidGitUrl ( url ) {
19+ // Allow git://, https://, or ssh:// URLs
20+ // Also allow scp-style URLs (user@host:path)
21+ const validUrlPattern =
22+ / ^ ( g i t : \/ \/ | h t t p s : \/ \/ | s s h : \/ \/ | [ a - z A - Z 0 - 9 . _ % + - ] + @ [ a - z A - Z 0 - 9 . - ] + \. [ a - z A - Z ] { 2 , } : ) / ;
23+ return typeof url === 'string' && validUrlPattern . test ( url ) ;
24+ }
25+
26+ // Add branch name validation helper
27+ function isValidBranchName ( branch ) {
28+ // Branch names can contain alphanumeric, -, _, /, and .
29+ // Cannot start with - or .
30+ // Cannot contain consecutive dots
31+ // Cannot contain control characters or spaces
32+ const validBranchPattern = / ^ [ a - z A - Z 0 - 9 ] [ a - z A - Z 0 - 9 \- _ / . ] * $ / ;
33+ return typeof branch === 'string' && validBranchPattern . test ( branch ) ;
34+ }
835
936class ConfigLoader extends EventEmitter {
1037 constructor ( initialConfig ) {
@@ -20,6 +47,12 @@ class ConfigLoader extends EventEmitter {
2047 return ;
2148 }
2249
50+ // Clear any existing interval before starting a new one
51+ if ( this . reloadTimer ) {
52+ clearInterval ( this . reloadTimer ) ;
53+ this . reloadTimer = null ;
54+ }
55+
2356 // Start periodic reload if interval is set
2457 if ( configurationSources . reloadIntervalSeconds > 0 ) {
2558 this . reloadTimer = setInterval (
@@ -92,6 +125,9 @@ class ConfigLoader extends EventEmitter {
92125
93126 async loadFromFile ( source ) {
94127 const configPath = path . resolve ( process . cwd ( ) , source . path ) ;
128+ if ( ! isValidPath ( configPath ) ) {
129+ throw new Error ( 'Invalid configuration file path' ) ;
130+ }
95131 const content = await fs . promises . readFile ( configPath , 'utf8' ) ;
96132 return JSON . parse ( content ) ;
97133 }
@@ -108,24 +144,40 @@ class ConfigLoader extends EventEmitter {
108144
109145 async loadFromGit ( source ) {
110146 // Validate inputs
111- if ( ! source . repository || typeof source . repository !== 'string' ) {
112- throw new Error ( 'Invalid repository URL' ) ;
147+ if ( ! source . repository || ! isValidGitUrl ( source . repository ) ) {
148+ throw new Error ( 'Invalid repository URL format ' ) ;
113149 }
114- if ( source . branch && typeof source . branch !== 'string' ) {
115- throw new Error ( 'Invalid branch name' ) ;
150+ if ( source . branch && ! isValidBranchName ( source . branch ) ) {
151+ throw new Error ( 'Invalid branch name format ' ) ;
116152 }
117153
118- const tempDir = path . join ( process . cwd ( ) , '.git-config-cache' ) ;
154+ // Use OS-specific cache directory
155+ const paths = envPaths ( 'git-proxy' , { suffix : '' } ) ;
156+ const tempDir = path . join ( paths . cache , 'git-config-cache' ) ;
157+ if ( ! isValidPath ( tempDir ) ) {
158+ throw new Error ( 'Invalid temporary directory path' ) ;
159+ }
119160 await fs . promises . mkdir ( tempDir , { recursive : true } ) ;
120161
121162 const repoDir = path . join ( tempDir , Buffer . from ( source . repository ) . toString ( 'base64' ) ) ;
163+ if ( ! isValidPath ( repoDir ) ) {
164+ throw new Error ( 'Invalid repository directory path' ) ;
165+ }
122166
123167 // Clone or pull repository
124168 if ( ! fs . existsSync ( repoDir ) ) {
125- if ( source . auth ?. type === 'ssh' ) {
126- process . env . GIT_SSH_COMMAND = `ssh -i ${ source . auth . privateKeyPath } ` ;
127- }
128- await execFileAsync ( 'git' , [ 'clone' , source . repository , repoDir ] ) ;
169+ const execOptions = {
170+ cwd : process . cwd ( ) ,
171+ env : {
172+ ...process . env ,
173+ ...( source . auth ?. type === 'ssh'
174+ ? {
175+ GIT_SSH_COMMAND : `ssh -i ${ source . auth . privateKeyPath } ` ,
176+ }
177+ : { } ) ,
178+ } ,
179+ } ;
180+ await execFileAsync ( 'git' , [ 'clone' , source . repository , repoDir ] , execOptions ) ;
129181 } else {
130182 await execFileAsync ( 'git' , [ 'pull' ] , { cwd : repoDir } ) ;
131183 }
@@ -137,6 +189,9 @@ class ConfigLoader extends EventEmitter {
137189
138190 // Read and parse config file
139191 const configPath = path . join ( repoDir , source . path ) ;
192+ if ( ! isValidPath ( configPath ) ) {
193+ throw new Error ( 'Invalid configuration file path in repository' ) ;
194+ }
140195 const content = await fs . promises . readFile ( configPath , 'utf8' ) ;
141196 return JSON . parse ( content ) ;
142197 }
0 commit comments