@@ -19,6 +19,38 @@ const runPlugins = (input, opts) => {
19
19
return Promise . all ( opts . plugins . map ( x => x ( input , opts ) ) ) . then ( files => files . reduce ( ( a , b ) => a . concat ( b ) ) ) ;
20
20
} ;
21
21
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
+
22
54
const extractFile = ( input , output , opts ) => runPlugins ( input , opts ) . then ( files => {
23
55
if ( opts . strip > 0 ) {
24
56
files = files
@@ -47,12 +79,35 @@ const extractFile = (input, output, opts) => runPlugins(input, opts).then(files
47
79
const now = new Date ( ) ;
48
80
49
81
if ( x . type === 'directory' ) {
50
- return makeDir ( dest )
82
+ return makeDir ( output )
83
+ . then ( outputPath => fsP . realpath ( outputPath ) )
84
+ . then ( realOutputPath => safeMakeDir ( dest , realOutputPath ) )
51
85
. then ( ( ) => fsP . utimes ( dest , now , x . mtime ) )
52
86
. then ( ( ) => x ) ;
53
87
}
54
88
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
+ } )
56
111
. then ( ( ) => {
57
112
if ( x . type === 'link' ) {
58
113
return fsP . link ( x . linkname , dest ) ;
0 commit comments