11import { localPathFileserver } from "../local-path" ;
2- import { mkdtemp , rm } from "node:fs/promises" ;
2+ import { mkdtemp , readFile , rm , symlink } from "node:fs/promises" ;
33import { tmpdir } from "node:os" ;
44import { join } from "path" ;
55import { fsClient } from "@cocalc/conat/files/fs" ;
66import { randomId } from "@cocalc/conat/names" ;
77import { before , after , client } from "@cocalc/backend/conat/test/setup" ;
8+ import { uuid } from "@cocalc/util/misc" ;
89
910let tempDir ;
11+ let tempDir2 ;
1012beforeAll ( async ( ) => {
1113 await before ( ) ;
1214 tempDir = await mkdtemp ( join ( tmpdir ( ) , "cocalc-local-path" ) ) ;
15+ tempDir2 = await mkdtemp ( join ( tmpdir ( ) , "cocalc-local-path-2" ) ) ;
1316} ) ;
1417
15- describe ( "use the simple fileserver " , ( ) => {
18+ describe ( "use all the standard api functions of fs " , ( ) => {
1619 const service = `fs-${ randomId ( ) } ` ;
1720 let server ;
1821 it ( "creates the simple fileserver service" , async ( ) => {
1922 server = await localPathFileserver ( { client, service, path : tempDir } ) ;
2023 } ) ;
2124
22- const project_id = "6b851643-360e-435e-b87e-f9a6ab64a8b1" ;
25+ const project_id = uuid ( ) ;
2326 let fs ;
2427 it ( "create a client" , ( ) => {
2528 fs = fsClient ( { subject : `${ service } .project-${ project_id } ` } ) ;
@@ -82,6 +85,27 @@ describe("use the simple fileserver", () => {
8285 expect ( s . isFile ( ) ) . toBe ( false ) ;
8386 } ) ;
8487
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+
85109 it ( "creating a symlink works (and using lstat)" , async ( ) => {
86110 await fs . writeFile ( "source1" , "the source" ) ;
87111 await fs . symlink ( "source1" , "target1" ) ;
@@ -99,15 +123,71 @@ describe("use the simple fileserver", () => {
99123 const stats0 = await fs . stat ( "source1" ) ;
100124 expect ( stats0 . isSymbolicLink ( ) ) . toBe ( false ) ;
101125 } ) ;
102-
103-
104126
105127 it ( "closes the service" , ( ) => {
106128 server . close ( ) ;
107129 } ) ;
108130} ) ;
109131
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+
110189afterAll ( async ( ) => {
111190 await after ( ) ;
112191 await rm ( tempDir , { force : true , recursive : true } ) ;
192+ // await rm(tempDir2, { force: true, recursive: true });
113193} ) ;
0 commit comments