@@ -9,8 +9,33 @@ import {isStream} from 'is-stream';
99
1010const pipeline = promisify ( stream . pipeline ) ; // TODO: Use `node:stream/promises` when targeting Node.js 16.
1111
12- // Use `path.basename()` to prevent path traversal.
13- const getPath = ( prefix = '' ) => path . join ( tempDir , path . basename ( prefix ) + uniqueString ( ) ) ;
12+ // TODO: Use `is-safe-filename` when targeting Node.js 20.
13+ function assertSafePathComponent ( pathComponent ) {
14+ if ( typeof pathComponent !== 'string' ) {
15+ throw new TypeError ( `Expected a string, got ${ typeof pathComponent } ` ) ;
16+ }
17+
18+ const trimmed = pathComponent . trim ( ) ;
19+
20+ const isSafe = trimmed !== ''
21+ && trimmed !== '.'
22+ && trimmed !== '..'
23+ && ! pathComponent . includes ( '/' )
24+ && ! pathComponent . includes ( '\\' )
25+ && ! pathComponent . includes ( '\0' ) ;
26+
27+ if ( ! isSafe ) {
28+ throw new Error ( `Unsafe path component: ${ JSON . stringify ( pathComponent ) } ` ) ;
29+ }
30+ }
31+
32+ const getPath = ( prefix = '' ) => {
33+ if ( prefix ) {
34+ assertSafePathComponent ( prefix ) ;
35+ }
36+
37+ return path . join ( tempDir , prefix + uniqueString ( ) ) ;
38+ } ;
1439
1540const writeStream = async ( filePath , data ) => pipeline ( data , fs . createWriteStream ( filePath ) ) ;
1641
@@ -23,17 +48,21 @@ async function runTask(temporaryPath, callback) {
2348}
2449
2550export function temporaryFile ( { name, extension} = { } ) {
26- if ( name ) {
51+ if ( name !== undefined && name !== null ) {
2752 if ( extension !== undefined && extension !== null ) {
2853 throw new Error ( 'The `name` and `extension` options are mutually exclusive' ) ;
2954 }
3055
31- // Use `path.basename()` to prevent path traversal.
32- return path . join ( temporaryDirectory ( ) , path . basename ( name ) ) ;
56+ assertSafePathComponent ( name ) ;
57+
58+ return path . join ( temporaryDirectory ( ) , name ) ;
59+ }
60+
61+ if ( extension !== undefined && extension !== null ) {
62+ assertSafePathComponent ( extension ) ;
3363 }
3464
35- // Use `path.basename()` to prevent path traversal.
36- return getPath ( ) + ( extension === undefined || extension === null ? '' : '.' + path . basename ( extension ) . replace ( / ^ \. / , '' ) ) ;
65+ return getPath ( ) + ( extension === undefined || extension === null ? '' : '.' + extension . replace ( / ^ \. / , '' ) ) ;
3766}
3867
3968export const temporaryFileTask = async ( callback , options ) => runTask ( temporaryFile ( options ) , callback ) ;
0 commit comments