@@ -19,6 +19,34 @@ const DANGEROUS_CONTROL_PATTERNS = [
1919 / \u001b \[ .* [ h l ] / , // Mode setting (dangerous)
2020] ;
2121
22+ /**
23+ * Dangerous environment variables that can be exploited for attacks
24+ */
25+ const DANGEROUS_ENV_VARS = [
26+ "LD_PRELOAD" ,
27+ "LD_LIBRARY_PATH" ,
28+ "DYLD_INSERT_LIBRARIES" , // macOS
29+ "PYTHONPATH" ,
30+ "NODE_PATH" ,
31+ "GEM_PATH" ,
32+ "PERL5LIB" ,
33+ "RUBYLIB" ,
34+ "CLASSPATH" , // Java
35+ ] ;
36+
37+ /**
38+ * Sanitize environment variables by removing dangerous ones
39+ * @param env - Environment variables object
40+ * @returns Sanitized environment variables
41+ */
42+ const sanitizeEnv = ( env : NodeJS . ProcessEnv ) : NodeJS . ProcessEnv => {
43+ const cleaned = { ...env } ;
44+ for ( const dangerous of DANGEROUS_ENV_VARS ) {
45+ delete cleaned [ dangerous ] ;
46+ }
47+ return cleaned ;
48+ } ;
49+
2250/**
2351 * Validate input data for dangerous control sequences
2452 * @param data - Input data to validate
@@ -100,6 +128,7 @@ export class PtyProcess {
100128 } > = [ ] ;
101129 private initPromise : Promise < void > ;
102130 private isDisposed = false ;
131+ private execTimeoutId ?: ReturnType < typeof setTimeout > ;
103132
104133 constructor ( commandOrOptions : string | PtyOptions ) {
105134 this . id = nanoid ( ) ;
@@ -129,12 +158,13 @@ export class PtyProcess {
129158 // Security check for executable
130159 checkExecutablePermission ( command ) ;
131160
161+ const sanitizedEnv = sanitizeEnv ( { ...process . env , ...this . options . env } ) ;
132162 this . pty = spawn ( command , args , {
133163 name : "xterm-256color" ,
134164 cols : this . terminal . cols ,
135165 rows : this . terminal . rows ,
136166 cwd : this . options . cwd || process . cwd ( ) ,
137- env : { ... process . env , ... this . options . env } as Record < string , string > ,
167+ env : sanitizedEnv as Record < string , string > ,
138168 } ) ;
139169
140170 // PTY output -> xterm and subscribers
@@ -175,6 +205,16 @@ export class PtyProcess {
175205 } ) ;
176206
177207 this . status = "active" ;
208+
209+ // Set execution timeout if specified
210+ if ( this . options . execTimeout ) {
211+ this . execTimeoutId = setTimeout ( ( ) => {
212+ logger . warn (
213+ `PTY ${ this . id } execution timeout (${ this . options . execTimeout } ms), disposing` ,
214+ ) ;
215+ void this . dispose ( ) ;
216+ } , this . options . execTimeout ) ;
217+ }
178218 }
179219
180220 async ready ( ) : Promise < void > {
@@ -373,6 +413,17 @@ export class PtyProcess {
373413 if ( this . status === "idle" ) {
374414 this . status = "active" ;
375415 }
416+
417+ // Reset execution timeout on activity
418+ if ( this . execTimeoutId && this . options . execTimeout ) {
419+ clearTimeout ( this . execTimeoutId ) ;
420+ this . execTimeoutId = setTimeout ( ( ) => {
421+ logger . warn (
422+ `PTY ${ this . id } execution timeout (${ this . options . execTimeout } ms), disposing` ,
423+ ) ;
424+ void this . dispose ( ) ;
425+ } , this . options . execTimeout ) ;
426+ }
376427 }
377428
378429 /**
@@ -398,6 +449,12 @@ export class PtyProcess {
398449 this . status = "terminating" ;
399450 this . subscribers = [ ] ;
400451
452+ // Clear execution timeout
453+ if ( this . execTimeoutId ) {
454+ clearTimeout ( this . execTimeoutId ) ;
455+ this . execTimeoutId = undefined ;
456+ }
457+
401458 if ( this . pty ) {
402459 // Only kill if process is still running
403460 if ( this . exitCode === null ) {
0 commit comments