@@ -19,6 +19,38 @@ const runPlugins = (input, opts) => {
1919 return Promise . all ( opts . plugins . map ( x => x ( input , opts ) ) ) . then ( files => files . reduce ( ( a , b ) => a . concat ( b ) ) ) ;
2020} ;
2121
22+ const safeMakeDir = ( dir , realOutputPath ) => {
23+ return fsP . realpath ( dir )
24+ . catch ( _ => {
25+ const parent = path . dirname ( dir ) ;
26+ return safeMakeDir ( parent , realOutputPath ) ;
27+ } )
28+ . then ( realParentPath => {
29+ if ( realParentPath . indexOf ( realOutputPath ) !== 0 ) {
30+ throw ( new Error ( 'Refusing to create a directory outside the output path.' ) ) ;
31+ }
32+
33+ return makeDir ( dir ) . then ( fsP . realpath ) ;
34+ } ) ;
35+ } ;
36+
37+ const preventWritingThroughSymlink = ( destination , realOutputPath ) => {
38+ return fsP . readlink ( destination )
39+ . catch ( _ => {
40+ // Either no file exists, or it's not a symlink. In either case, this is
41+ // not an escape we need to worry about in this phase.
42+ return null ;
43+ } )
44+ . then ( symlinkPointsTo => {
45+ if ( symlinkPointsTo ) {
46+ throw new Error ( 'Refusing to write into a symlink' ) ;
47+ }
48+
49+ // No symlink exists at `destination`, so we can continue
50+ return realOutputPath ;
51+ } ) ;
52+ } ;
53+
2254const extractFile = ( input , output , opts ) => runPlugins ( input , opts ) . then ( files => {
2355 if ( opts . strip > 0 ) {
2456 files = files
@@ -47,12 +79,35 @@ const extractFile = (input, output, opts) => runPlugins(input, opts).then(files
4779 const now = new Date ( ) ;
4880
4981 if ( x . type === 'directory' ) {
50- return makeDir ( dest )
82+ return makeDir ( output )
83+ . then ( outputPath => fsP . realpath ( outputPath ) )
84+ . then ( realOutputPath => safeMakeDir ( dest , realOutputPath ) )
5185 . then ( ( ) => fsP . utimes ( dest , now , x . mtime ) )
5286 . then ( ( ) => x ) ;
5387 }
5488
55- return makeDir ( path . dirname ( dest ) )
89+ return makeDir ( output )
90+ . then ( outputPath => fsP . realpath ( outputPath ) )
91+ . then ( realOutputPath => {
92+ // Attempt to ensure parent directory exists (failing if it's outside the output dir)
93+ return safeMakeDir ( path . dirname ( dest ) , realOutputPath )
94+ . then ( ( ) => realOutputPath ) ;
95+ } )
96+ . then ( realOutputPath => {
97+ if ( x . type === 'file' ) {
98+ return preventWritingThroughSymlink ( dest , realOutputPath ) ;
99+ }
100+
101+ return realOutputPath ;
102+ } )
103+ . then ( realOutputPath => {
104+ return fsP . realpath ( path . dirname ( dest ) )
105+ . then ( realDestinationDir => {
106+ if ( realDestinationDir . indexOf ( realOutputPath ) !== 0 ) {
107+ throw ( new Error ( 'Refusing to write outside output directory: ' + realDestinationDir ) ) ;
108+ }
109+ } ) ;
110+ } )
56111 . then ( ( ) => {
57112 if ( x . type === 'link' ) {
58113 return fsP . link ( x . linkname , dest ) ;
0 commit comments