1
1
import { localPathFileserver } from "../local-path" ;
2
- import { mkdtemp , rm } from "node:fs/promises" ;
2
+ import { mkdtemp , readFile , rm , symlink } from "node:fs/promises" ;
3
3
import { tmpdir } from "node:os" ;
4
4
import { join } from "path" ;
5
5
import { fsClient } from "@cocalc/conat/files/fs" ;
6
6
import { randomId } from "@cocalc/conat/names" ;
7
7
import { before , after , client } from "@cocalc/backend/conat/test/setup" ;
8
+ import { uuid } from "@cocalc/util/misc" ;
8
9
9
10
let tempDir ;
11
+ let tempDir2 ;
10
12
beforeAll ( async ( ) => {
11
13
await before ( ) ;
12
14
tempDir = await mkdtemp ( join ( tmpdir ( ) , "cocalc-local-path" ) ) ;
15
+ tempDir2 = await mkdtemp ( join ( tmpdir ( ) , "cocalc-local-path-2" ) ) ;
13
16
} ) ;
14
17
15
- describe ( "use the simple fileserver " , ( ) => {
18
+ describe ( "use all the standard api functions of fs " , ( ) => {
16
19
const service = `fs-${ randomId ( ) } ` ;
17
20
let server ;
18
21
it ( "creates the simple fileserver service" , async ( ) => {
19
22
server = await localPathFileserver ( { client, service, path : tempDir } ) ;
20
23
} ) ;
21
24
22
- const project_id = "6b851643-360e-435e-b87e-f9a6ab64a8b1" ;
25
+ const project_id = uuid ( ) ;
23
26
let fs ;
24
27
it ( "create a client" , ( ) => {
25
28
fs = fsClient ( { subject : `${ service } .project-${ project_id } ` } ) ;
@@ -82,6 +85,27 @@ describe("use the simple fileserver", () => {
82
85
expect ( s . isFile ( ) ) . toBe ( false ) ;
83
86
} ) ;
84
87
88
+ it ( "readFile works" , async ( ) => {
89
+ await fs . writeFile ( "a" , Buffer . from ( [ 1 , 2 , 3 ] ) ) ;
90
+ const s = await fs . readFile ( "a" ) ;
91
+ expect ( s ) . toEqual ( Buffer . from ( [ 1 , 2 , 3 ] ) ) ;
92
+
93
+ await fs . writeFile ( "b.txt" , "conat" ) ;
94
+ const t = await fs . readFile ( "b.txt" , "utf8" ) ;
95
+ expect ( t ) . toEqual ( "conat" ) ;
96
+ } ) ;
97
+
98
+ it ( "readdir works" , async ( ) => {
99
+ await fs . mkdir ( "dirtest" ) ;
100
+ for ( let i = 0 ; i < 5 ; i ++ ) {
101
+ await fs . writeFile ( `dirtest/${ i } ` , `${ i } ` ) ;
102
+ }
103
+ const fire = "🔥.txt" ;
104
+ await fs . writeFile ( join ( "dirtest" , fire ) , "this is ️🔥!" ) ;
105
+ const v = await fs . readdir ( "dirtest" ) ;
106
+ expect ( v ) . toEqual ( [ "0" , "1" , "2" , "3" , "4" , fire ] ) ;
107
+ } ) ;
108
+
85
109
it ( "creating a symlink works (and using lstat)" , async ( ) => {
86
110
await fs . writeFile ( "source1" , "the source" ) ;
87
111
await fs . symlink ( "source1" , "target1" ) ;
@@ -99,15 +123,71 @@ describe("use the simple fileserver", () => {
99
123
const stats0 = await fs . stat ( "source1" ) ;
100
124
expect ( stats0 . isSymbolicLink ( ) ) . toBe ( false ) ;
101
125
} ) ;
102
-
103
-
104
126
105
127
it ( "closes the service" , ( ) => {
106
128
server . close ( ) ;
107
129
} ) ;
108
130
} ) ;
109
131
132
+ describe ( "security: dangerous symlinks can't be followed" , ( ) => {
133
+ const service = `fs-${ randomId ( ) } ` ;
134
+ let server ;
135
+ it ( "creates the simple fileserver service" , async ( ) => {
136
+ server = await localPathFileserver ( { client, service, path : tempDir2 } ) ;
137
+ } ) ;
138
+
139
+ const project_id = uuid ( ) ;
140
+ const project_id2 = uuid ( ) ;
141
+ let fs , fs2 ;
142
+ it ( "create two clients" , ( ) => {
143
+ fs = fsClient ( { subject : `${ service } .project-${ project_id } ` } ) ;
144
+ fs2 = fsClient ( { subject : `${ service } .project-${ project_id2 } ` } ) ;
145
+ } ) ;
146
+
147
+ it ( "create a secret in one" , async ( ) => {
148
+ await fs . writeFile ( "password" , "s3cr3t" ) ;
149
+ await fs2 . writeFile ( "a" , "init" ) ;
150
+ } ) ;
151
+
152
+ // This is setup bypassing security and is part of our threat model, due to users
153
+ // having full access internally to their sandbox fs.
154
+ it ( "directly create a file that is a symlink outside of the sandbox -- this should work" , async ( ) => {
155
+ await symlink (
156
+ join ( tempDir2 , project_id , "password" ) ,
157
+ join ( tempDir2 , project_id2 , "link" ) ,
158
+ ) ;
159
+ const s = await readFile ( join ( tempDir2 , project_id2 , "link" ) , "utf8" ) ;
160
+ expect ( s ) . toBe ( "s3cr3t" ) ;
161
+ } ) ;
162
+
163
+ it ( "fails to read the symlink content via the api" , async ( ) => {
164
+ await expect ( async ( ) => {
165
+ await fs2 . readFile ( "link" , "utf8" ) ;
166
+ } ) . rejects . toThrow ( "outside of sandbox" ) ;
167
+ } ) ;
168
+
169
+ it ( "directly create a relative symlink " , async ( ) => {
170
+ await symlink (
171
+ join ( ".." , project_id , "password" ) ,
172
+ join ( tempDir2 , project_id2 , "link2" ) ,
173
+ ) ;
174
+ const s = await readFile ( join ( tempDir2 , project_id2 , "link2" ) , "utf8" ) ;
175
+ expect ( s ) . toBe ( "s3cr3t" ) ;
176
+ } ) ;
177
+
178
+ it ( "fails to read the relative symlink content via the api" , async ( ) => {
179
+ await expect ( async ( ) => {
180
+ await fs2 . readFile ( "link2" , "utf8" ) ;
181
+ } ) . rejects . toThrow ( "outside of sandbox" ) ;
182
+ } ) ;
183
+
184
+ it ( "closes the server" , ( ) => {
185
+ server . close ( ) ;
186
+ } ) ;
187
+ } ) ;
188
+
110
189
afterAll ( async ( ) => {
111
190
await after ( ) ;
112
191
await rm ( tempDir , { force : true , recursive : true } ) ;
192
+ // await rm(tempDir2, { force: true, recursive: true });
113
193
} ) ;
0 commit comments