콘텐츠로 이동

File System

Electron fs 사용 흐름에 대응하는 Suji core 파일 시스템 API. Frontend JS와 Zig/Rust/Go/Node 백엔드 SDK 모두 같은 __core__ cmd를 사용한다.

현재 범위는 UTF-8 텍스트 파일과 디렉토리 metadata/listing이다. 바이너리/스트리밍은 후속.

Sandbox (Electron webPreferences.sandbox 대응)

섹션 제목: “Sandbox (Electron webPreferences.sandbox 대응)”

Frontend(renderer)에서 호출되는 fs.*는 path 화이트리스트로 검증된다. Backend는 항상 무제한 (사용자 자체 코드라 신뢰).

suji.json:

{
"fs": {
"allowedRoots": ["~/Documents/myapp", "/tmp/myapp"]
}
}
설정Frontend 동작
allowedRoots 미설정 / []모든 frontend fs.* 차단 → error: "forbidden"
["~/Documents/myapp"]해당 prefix 안 path만 허용 (~$HOME으로 확장)
["*"]escape hatch — 모든 path 허용 (.. traversal은 여전히 차단)

path traversal 가드: .. 토큰 포함 path는 모든 mode에서 항상 차단 (["*"]도 우회 불가). Backend SDK 호출은 sandbox를 우회한다 (백엔드는 사용자 자체 코드이므로 항상 무제한).

렌더러가 file path를 받는 native cmd도 동일 allowedRoots 경계로 게이트된다 — 쓰기: print_to_pdf / capture_page / desktop_capturer_capture_thumbnail, 읽기: native_image_get_size / native_image_to_png|jpeg(임의 파일을 base64로 렌더러에 반환하면 파일 내용이 유출될 수 있음). 단 opt-inallowedRoots 미설정/[]이면 이 cmd들은 기존과 동일하게 무제한으로 동작한다. allowedRoots를 설정한 앱에서는 fs.*와 동일한 traversal/boundary 검사를 적용해 신뢰할 수 없는 렌더러의 임의 파일 읽기/쓰기를 차단한다. Backend 호출은 여기서도 sandbox를 우회한다.

APIcmd응답
fs.readFile(path)fs_read_file{success:true,text}
fs.writeFile(path, text)fs_write_file{success:true}
fs.stat(path)fs_stat{success:true,type,size,mtime} (mtime: ms since epoch)
fs.mkdir(path, {recursive})fs_mkdir{success:true}
fs.readdir(path)fs_readdir{success:true,entries:[{name,type}]}
fs.rm(path, {recursive,force})fs_rm{success:true} (Node fs.rm 호환)

실패 응답은 {success:false,error} 형태다. readFile/writeFile은 8 KiB 한도 안에서 동작하며 초과 시 error:"too_large". 큰 파일은 아직 대상이 아니다.

import { fs } from '@suji/api';
await fs.mkdir('/tmp/suji', { recursive: true });
await fs.writeFile('/tmp/suji/hello.txt', 'hello\nworld');
const text = await fs.readFile('/tmp/suji/hello.txt');
const stat = await fs.stat('/tmp/suji/hello.txt');
const entries = await fs.readdir('/tmp/suji');
await fs.rm('/tmp/suji', { recursive: true, force: true });
_ = suji.fs.mkdir("/tmp/suji", true);
_ = suji.fs.writeFile("/tmp/suji/hello.txt", "hello\nworld");
_ = suji.fs.readFile("/tmp/suji/hello.txt");
_ = suji.fs.stat("/tmp/suji/hello.txt");
_ = suji.fs.readdir("/tmp/suji");
use suji::fs;
let _ = fs::mkdir("/tmp/suji", true);
let _ = fs::write_file("/tmp/suji/hello.txt", "hello\nworld");
let _ = fs::read_file("/tmp/suji/hello.txt");
let _ = fs::stat("/tmp/suji/hello.txt");
let _ = fs::readdir("/tmp/suji");
import "github.com/ohah/suji-go/fs"
fs.Mkdir("/tmp/suji", true)
fs.WriteFile("/tmp/suji/hello.txt", "hello\nworld")
fs.ReadFile("/tmp/suji/hello.txt")
fs.Stat("/tmp/suji/hello.txt")
fs.ReadDir("/tmp/suji")
import { fs } from '@suji/node';
await fs.mkdir('/tmp/suji', { recursive: true });
await fs.writeFile('/tmp/suji/hello.txt', 'hello\nworld');
const entries = await fs.readdir('/tmp/suji');

stat.typereaddir[].type은 다음 문자열 중 하나다:

file, directory, symlink, blockDevice, characterDevice, fifo, socket, whiteout, door, eventPort, unknown.