콘텐츠로 이동

보안 모델

Suji가 frontend(renderer)에서 backend로 향하는 모든 호출에 적용하는 보안 정책. Electron 모델과 동등한 곳, 다른 곳을 정확히 구분한다.

window.__suji__ 오브젝트에 노출되는 것:

항목종류비고
invoke(backend, request)native (V8 binding)backend cmd 호출, Promise<JSON>
emit(event, data)native (V8 binding)EventBus 브로드캐스트
core(request)JS helperinvoke("__core__", ...) shorthand
on(event, cb) / off(event, cb)JS helperEventBus listener
platformstring 상수`“macos"

Electron 비교: Electron의 contextIsolation: true는 preload script만 main world에 노출. Suji는 모든 surface를 main world의 window.__suji__에 직접 바인딩 (Electron의 contextIsolation:false 동등). 노출 surface가 매우 작아 (invoke + emit 2개 native만) 실제 위험은 cmd-level 정책에 의존한다.

로드맵: Electron-style contextBridge isolated world 분리. 현재는 surface가 작고 fs/cache 격리로 대부분의 위험을 차단하고 있어 우선순위가 낮다.

frontend가 호출하는 fs.* cmd는 path 화이트리스트 + traversal 가드로 검증. fs.mdx 참고.

{ "fs": { "allowedRoots": ["~/Documents/myapp"] } }
보호동작
Default safeallowedRoots 미설정/빈 배열 → 모든 frontend fs.* 차단
Path traversal.. path component는 모든 mode에서 항상 차단 (["*"]도 우회 불가)
Prefix-extension 차단/foo/bar 허용 시 /foo/barX 통과 X (separator boundary 가드)
~ 사전 expandconfig load 시 1회 — hot path env lookup 0회
Backend 우회backend SDK 호출은 무제한 (사용자 자체 코드 신뢰)

fs.* 외에도 renderer가 file path를 넘기는 native cmd는 같은 경계로 검사한다.

cmdsink정책
print_to_pdf / capture_page / desktop_capturer_capture_thumbnail파일 쓰기fs.allowedRoots 설정 시 enforce
native_image_get_size / native_image_to_png / native_image_to_jpeg파일 읽기/인코딩fs.allowedRoots 설정 시 enforce
tray_create.iconPath이미지 파일 로드fs.allowedRoots 설정 시 enforce

이 경로들은 기존 릴리즈에서 무제한이었기 때문에 fs.allowedRoots가 미설정/빈 배열이면 레거시 동작을 유지한다. fs.allowedRoots를 설정한 앱에서는 fs.*와 동일한 traversal/boundary 검사를 적용한다.

shell.*와 dialog open/save의 defaultPath는 opt-in allowlist를 제공한다. 키가 없으면 기존 동작을 유지하고, 키가 있으면 빈 배열은 deny-all, ["*"]는 allow-all, 나머지는 path boundary 또는 URL glob 매치만 허용한다.

{
"shell": {
"allowedPaths": ["~/Documents/myapp"],
"allowedExternalUrls": ["https://*.example.com/*"]
},
"dialog": {
"allowedPaths": ["~/Documents/myapp"]
}
}

Backend SDK 호출은 fs sandbox와 동일하게 우회한다.

config.app.name 키로 OS 표준 user-data 디렉토리 아래 자동 격리. 한 시스템에 여러 Suji 앱이 있어도 cookie/localStorage/IndexedDB/Service Worker가 서로 영향 없음. cache.mdx 참고.

__core__ channel을 통해 들어오는 모든 IPC 메시지가 통과:

검증거부 시 응답
payload size ≤ 32KBerror: "payload_too_large"
cmd 필드 존재error: "missing_cmd"
cmd 영숫자/언더스코어만error: "invalid_cmd" (newline/quote 등 injection 차단)
알려진 cmdunknown 시 error: "unknown_cmd" (typo / version mismatch 진단)
cmd 정확 매치substring 매치 X

외부 untrusted 콘텐츠 (광고/위젯/임베드)를 iframe으로 띄울 때 origin 화이트리스트로 제한. production suji:// 응답에 붙는 CSP frame-src directive로 Chromium이 차단한다.

suji.json:

{
"security": {
"iframeAllowedOrigins": ["https://trusted.com", "https://api.example.com"]
}
}
설정동작
미설정 / []default block — 모든 iframe 차단 (frame-src 'none')
["https://trusted.com"]해당 origin만 허용 (frame-src 'self' https://trusted.com)
["*"]무제한 escape hatch (frame-src *) — 권장 X

사용자가 security.csp로 직접 CSP 명시 시 그것 우선 — iframe_allowed_origins은 무시. 그 경우 사용자가 직접 frame-src 명시 책임.

security.csp는 명시 문자열 override와 "disabled" escape hatch를 지원한다. 미설정이면 Suji가 default CSP를 만들고 iframeAllowedOriginsframe-src에 반영한다.

suji build --sandbox 또는 SUJI_SANDBOX로 App Sandbox entitlements를 자동 부착한다. main app과 CEF Helper(Browser/GPU/Renderer/Plugin)별 plist를 분리하고 helper에는 inherit entitlement를 적용한다. Security-scoped bookmarks API는 별도 sandbox.mdx에 정리되어 있다.

  • 진짜 isolated world: window.__suji__ surface는 freeze/hardening되어 있지만 Chromium isolated world/contextBridge와 동일한 별도 V8 context는 아직 아니다.
  • dev mode CSP: dev_url로 Vite 등 외부 서버를 직접 로드하는 동안에는 Suji가 응답 헤더를 주입하지 않는다. dev 서버나 HTML meta CSP로 별도 검증해야 한다.
  • network/webRequest: webRequest는 선언적 blocklist API로 제공한다. renderer 일반 fetch allowlist는 별도 HTTP plugin 정책으로 다룬다.
  • App Sandbox 실효 검증: 로컬 빌드와 entitlement 부착은 자동화되어 있지만, Mac App Store 심사 환경의 실제 sandbox 검증은 별도 배포 검증이 필요하다.