Skip to main content

Electron Fuses

패키징 시점 기능 토글

퓨즈(Fuses)란 무엇인가?

일부 Electron 기능의 경우, 전체 애플리케이션에서 특정 기능을 비활성화하는 것이 합리적이다. 예를 들어, 99%의 앱은 ELECTRON_RUN_AS_NODE 기능을 사용하지 않는다. 이러한 앱은 해당 기능을 사용할 수 없는 바이너리를 배포하고 싶어 한다. 또한 Electron 사용자가 소스 코드에서 Electron을 빌드하는 것을 원하지 않는다. 이는 기술적으로 매우 어려운 작업일 뿐만 아니라 시간과 비용이 많이 들기 때문이다.

퓨즈는 이 문제를 해결하는 방법이다. 간단히 말해, 퓨즈는 Electron 바이너리 내의 "마법의 비트"로, 앱을 패키징할 때 특정 기능이나 제한 사항을 활성화/비활성화하기 위해 조정할 수 있다. 퓨즈는 앱에 코드 서명을 하기 전에 패키징 단계에서 조정되므로, 운영체제는 OS 수준의 코드 서명 검증(예: Gatekeeper / App Locker)을 통해 이러한 비트가 다시 조정되지 않도록 보장한다.

현재 퓨즈 설정

runAsNode

기본값: 활성화됨

@electron/fuses: FuseV1Options.RunAsNode

runAsNode 퓨즈는 ELECTRON_RUN_AS_NODE 환경 변수를 적용할지 여부를 결정한다. 이 퓨즈를 비활성화하면 메인 프로세스에서 process.fork가 정상적으로 동작하지 않는다. 이 환경 변수에 의존하기 때문이다. 대신, 독립적인 Node.js 프로세스가 필요한 경우(예: Sqlite 서버 프로세스 등) Utility Processes를 사용하는 것을 권장한다.

cookieEncryption

기본값: 비활성화

@electron/fuses: FuseV1Options.EnableCookieEncryption

cookieEncryption 퓨즈는 쿠키 저장소를 OS 수준의 암호화 키를 사용해 암호화할지 여부를 결정한다. 기본적으로 Chromium이 쿠키를 저장하는 데 사용하는 sqlite 데이터베이스는 평문으로 값을 저장한다. 앱의 쿠키가 Chrome과 동일한 방식으로 암호화되도록 보장하려면 이 퓨즈를 활성화해야 한다. 이 퓨즈는 단방향 전환임에 유의한다. 퓨즈를 활성화하면 기존의 암호화되지 않은 쿠키는 쓰기 시 암호화되지만, 이후 퓨즈를 다시 비활성화하면 쿠키 저장소가 손상되어 사용할 수 없게 된다. 대부분의 앱은 이 퓨즈를 안전하게 활성화할 수 있다.

nodeOptions

기본값: 활성화됨

@electron/fuses: FuseV1Options.EnableNodeOptionsEnvironmentVariable

nodeOptions 퓨즈는 NODE_OPTIONSNODE_EXTRA_CA_CERTS 환경 변수가 적용되는지 여부를 조절한다. NODE_OPTIONS 환경 변수는 Node.js 런타임에 다양한 커스텀 옵션을 전달하는 데 사용되며, 일반적으로 프로덕션 환경의 앱에서는 사용되지 않는다. 대부분의 앱은 이 퓨즈를 안전하게 비활성화할 수 있다.

nodeCliInspect

기본값: 활성화됨

@electron/fuses: FuseV1Options.EnableNodeCliInspectArguments

nodeCliInspect 퓨즈는 --inspect, --inspect-brk 등의 플래그를 존중할지 여부를 결정한다. 이 퓨즈를 비활성화하면 SIGUSR1 신호가 메인 프로세스의 디버거를 초기화하지 않도록 보장한다. 대부분의 애플리케이션은 이 퓨즈를 안전하게 비활성화할 수 있다.

embeddedAsarIntegrityValidation

기본값: 비활성화

@electron/fuses: FuseV1Options.EnableEmbeddedAsarIntegrityValidation

embeddedAsarIntegrityValidation 퓨즈는 macOS에서 app.asar 파일이 로드될 때 해당 파일의 내용을 검증하는 실험적 기능을 활성화한다. 이 기능은 성능에 미치는 영향을 최소화하도록 설계되었지만, app.asar 아카이브 내부에서 파일을 읽는 속도가 약간 느려질 수 있다.

Asar 무결성 검증을 사용하는 방법에 대한 자세한 내용은 Asar 무결성 문서를 참고한다.

onlyLoadAppFromAsar

기본값: 비활성화

@electron/fuses: FuseV1Options.OnlyLoadAppFromAsar

onlyLoadAppFromAsar 퓨즈는 Electron이 앱 코드를 찾는 검색 방식을 변경한다. 기본적으로 Electron은 app.asar -> app -> default_app.asar 순서로 검색한다. 이 퓨즈를 활성화하면 검색 순서가 app.asar 단일 항목으로 변경된다. 이를 통해 embeddedAsarIntegrityValidation 퓨즈와 함께 사용할 경우, 검증되지 않은 코드를 불러오는 것이 불가능해진다.

loadBrowserProcessSpecificV8Snapshot

기본값: 비활성화

@electron/fuses: FuseV1Options.LoadBrowserProcessSpecificV8Snapshot

loadBrowserProcessSpecificV8Snapshot 퓨즈는 브라우저 프로세스에서 사용할 V8 스냅샷 파일을 변경한다. 기본적으로 Electron의 모든 프로세스는 동일한 V8 스냅샷 파일을 사용한다. 이 퓨즈를 활성화하면 브라우저 프로세스는 browser_v8_context_snapshot.bin이라는 파일을 V8 스냅샷으로 사용한다. 다른 프로세스들은 평소와 동일한 V8 스냅샷 파일을 사용한다.

V8 스냅샷은 앱 시작 성능을 개선하는 데 유용하다. V8은 초기화된 힙의 스냅샷을 생성하고 이를 다시 로드함으로써 힙 초기화 비용을 줄일 수 있다.

렌더러 프로세스와 메인 프로세스에 대해 별도의 스냅샷을 사용하면 보안을 강화할 수 있다. 특히, 렌더러가 nodeIntegration이 활성화된 스냅샷을 사용하지 않도록 보장하는 데 도움이 된다. 자세한 내용은 #35170을 참고한다.

grantFileProtocolExtraPrivileges

기본값: 활성화

@electron/fuses: FuseV1Options.GrantFileProtocolExtraPrivileges

grantFileProtocolExtraPrivileges 퓨즈는 file:// 프로토콜로 로드된 페이지가 전통적인 웹 브라우저에서 받을 수 있는 권한 이상의 특권을 부여할지 여부를 결정한다. 이 동작은 초기 버전의 Electron 앱에서 핵심 기능이었지만, 이제는 커스텀 프로토콜을 통해 로컬 파일을 제공하는 것이 권장되므로 더 이상 필요하지 않다. file:// 프로토콜을 사용하지 않는다면 이 퓨즈를 비활성화해야 한다.

이 퓨즈로 인해 file:// 프로토콜에 부여되는 추가 권한은 아래와 같다:

  • file:// 프로토콜 페이지는 fetch를 사용해 다른 에셋을 file:// 프로토콜로 로드할 수 있다.
  • file:// 프로토콜 페이지는 서비스 워커를 사용할 수 있다.
  • file:// 프로토콜 페이지는 샌드박스 설정과 관계없이 file:// 프로토콜에서 실행되는 자식 프레임에 대해 완전한 접근 권한을 가진다.

퓨즈를 어떻게 전환할까?

간단한 방법

퓨즈 전환을 쉽게 할 수 있도록 @electron/fuses라는 편리한 모듈을 만들었다. 사용법과 잠재적인 오류 상황에 대한 자세한 내용은 해당 모듈의 README를 참고한다.

const { flipFuses, FuseVersion, FuseV1Options } = require('@electron/fuses')

flipFuses(
// electron 경로
require('electron'),
// 전환할 퓨즈
{
version: FuseVersion.V1,
[FuseV1Options.RunAsNode]: false
}
)

퓨즈가 전환되었는지 확인하거나 임의의 Electron 앱의 퓨즈 상태를 확인하려면 fuses CLI를 사용한다.

npx @electron/fuses read --app /Applications/Foo.app

직접 해보는 방법

주요 용어 정리

  • 퓨즈 와이어(Fuse Wire): Electron 바이너리 내에 있는 바이트 시퀀스로, 퓨즈를 제어하는 데 사용된다.
  • 센티넬(Sentinel): 퓨즈 와이어의 위치를 찾기 위해 사용되는 정적이고 알려진 바이트 시퀀스다.
  • 퓨즈 스키마(Fuse Schema): 퓨즈 와이어의 형식과 허용되는 값들을 정의한다.

퓨즈를 직접 변경하려면 Electron 바이너리를 편집하고, 원하는 퓨즈 상태를 나타내는 바이트 시퀀스로 퓨즈 와이어를 수정해야 한다.

Electron 바이너리 내부 어딘가에 다음과 같은 바이트 시퀀스가 존재한다:

| ...binary | sentinel_bytes | fuse_version | fuse_wire_length | fuse_wire | ...binary |
  • sentinel_bytes는 항상 정확히 dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX라는 문자열이다.
  • fuse_version은 단일 바이트로, 부호 없는 정수 값이 퓨즈 스키마의 버전을 나타낸다.
  • fuse_wire_length는 단일 바이트로, 부호 없는 정수 값이 뒤따르는 퓨즈 와이어의 퓨즈 개수를 나타낸다.
  • fuse_wire는 N개의 바이트 시퀀스로, 각 바이트는 단일 퓨즈와 그 상태를 나타낸다.
    • "0" (0x30)은 퓨즈가 비활성화되었음을 나타낸다.
    • "1" (0x31)은 퓨즈가 활성화되었음을 나타낸다.
    • "r" (0x72)은 퓨즈가 제거되었음을 나타내며, 이 바이트를 1 또는 0으로 변경해도 아무런 효과가 없다.

퓨즈를 변경하려면 퓨즈 와이어에서 해당 위치를 찾고, 원하는 상태에 따라 "0" 또는 "1"로 변경하면 된다.

현재 스키마는 여기에서 확인할 수 있다.