diff --git a/packages/foundations/assets/fonts/generate-eu-fonts.ts b/packages/foundations/assets/fonts/generate-eu-fonts.ts index 865bec79c0cc..6c2455a67711 100644 --- a/packages/foundations/assets/fonts/generate-eu-fonts.ts +++ b/packages/foundations/assets/fonts/generate-eu-fonts.ts @@ -1,5 +1,5 @@ import { glob } from 'glob'; -import { exec } from 'node:child_process'; +import { execFile } from 'node:child_process'; import { promisify } from 'node:util'; import { dirname } from 'path'; @@ -8,12 +8,16 @@ import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename).replaceAll('\\', '/'); -const execAsync = promisify(exec); +// Security: Using execFile instead of exec to eliminate shell injection risks +// execFile directly executes the binary without involving a shell +const execFileAsync = promisify(execFile); const generateFonts = async () => { console.log('Generating EU fonts...'); try { - await execAsync('pyftsubset --help'); + // Security: Using array arguments instead of concatenated string + // This prevents shell interpretation of special characters + await execFileAsync('pyftsubset', ['--help']); } catch (e) { console.warn( 'You need to install pyftsubset. Check packages/foundations/assets/fonts/README.md for more information.' @@ -22,19 +26,28 @@ const generateFonts = async () => { try { const files = await glob(`${__dirname}/*.ttf`); - const commands = files.map((file) => - [ - 'pyftsubset', + + for (const file of files) { + // Security: Validate that the file is within the expected directory + // and has the expected extension to prevent path traversal attacks + if (!file.startsWith(__dirname) || !file.endsWith('.ttf')) { + console.warn(`Skipping potentially unsafe file path: ${file}`); + continue; + } + + // Security: Arguments are passed as separate array elements + // No shell concatenation means no risk of command injection + const args = [ file, '--layout-features=*', '--flavor=woff2', `--unicodes-file=${__dirname}/unicode-eu.txt`, `--output-file=${file.replace('.ttf', '-EU.woff2')}` - ].join(' ') - ); + ]; - for (const command of commands) { - const { stdout, stderr } = await execAsync(command); + // Security: execFile provides better performance and type safety + // as it doesn't spawn a shell process + const { stdout, stderr } = await execFileAsync('pyftsubset', args); if (stdout) console.log(`stdout: ${stdout}`); if (stderr) console.error(`stderr: ${stderr}`); }