11import { glob } from 'glob' ;
2- import { exec } from 'node:child_process' ;
2+ import { execFile } from 'node:child_process' ;
33import { promisify } from 'node:util' ;
44
55import { dirname } from 'path' ;
@@ -8,12 +8,16 @@ import { fileURLToPath } from 'url';
88const __filename = fileURLToPath ( import . meta. url ) ;
99const __dirname = dirname ( __filename ) . replaceAll ( '\\' , '/' ) ;
1010
11- const execAsync = promisify ( exec ) ;
11+ // Security: Using execFile instead of exec to eliminate shell injection risks
12+ // execFile directly executes the binary without involving a shell
13+ const execFileAsync = promisify ( execFile ) ;
1214
1315const generateFonts = async ( ) => {
1416 console . log ( 'Generating EU fonts...' ) ;
1517 try {
16- await execAsync ( 'pyftsubset --help' ) ;
18+ // Security: Using array arguments instead of concatenated string
19+ // This prevents shell interpretation of special characters
20+ await execFileAsync ( 'pyftsubset' , [ '--help' ] ) ;
1721 } catch ( e ) {
1822 console . warn (
1923 'You need to install pyftsubset. Check packages/foundations/assets/fonts/README.md for more information.'
@@ -22,19 +26,28 @@ const generateFonts = async () => {
2226
2327 try {
2428 const files = await glob ( `${ __dirname } /*.ttf` ) ;
25- const commands = files . map ( ( file ) =>
26- [
27- 'pyftsubset' ,
29+
30+ for ( const file of files ) {
31+ // Security: Validate that the file is within the expected directory
32+ // and has the expected extension to prevent path traversal attacks
33+ if ( ! file . startsWith ( __dirname ) || ! file . endsWith ( '.ttf' ) ) {
34+ console . warn ( `Skipping potentially unsafe file path: ${ file } ` ) ;
35+ continue ;
36+ }
37+
38+ // Security: Arguments are passed as separate array elements
39+ // No shell concatenation means no risk of command injection
40+ const args = [
2841 file ,
2942 '--layout-features=*' ,
3043 '--flavor=woff2' ,
3144 `--unicodes-file=${ __dirname } /unicode-eu.txt` ,
3245 `--output-file=${ file . replace ( '.ttf' , '-EU.woff2' ) } `
33- ] . join ( ' ' )
34- ) ;
46+ ] ;
3547
36- for ( const command of commands ) {
37- const { stdout, stderr } = await execAsync ( command ) ;
48+ // Security: execFile provides better performance and type safety
49+ // as it doesn't spawn a shell process
50+ const { stdout, stderr } = await execFileAsync ( 'pyftsubset' , args ) ;
3851 if ( stdout ) console . log ( `stdout: ${ stdout } ` ) ;
3952 if ( stderr ) console . error ( `stderr: ${ stderr } ` ) ;
4053 }
0 commit comments