본문 바로가기

Programing/OpenSource

Electron ≥ 12.x : 컨텍스트 분리(Context Isolation)

3년전에 둘째 출산 휴가 때 진행했던 serami 프로젝트를 얼마 전에 다시 보게 되었다.

이메일 인증을 개발하면서 템플릿의 결과를 확인하게 위해 사용을 했는데 개발한지 오래되었다보니 Github에서 버전 업데이트에 대한 경고를 그 동안 지속적으로 받고 있었다.

 

라이브러리 업데이트를 하고나서 보니 "Uncaught ReferenceError: require is not defined" 에러가 발생했다.

에러의 부분을 찾아보니 리액트를 bootstrap 하는 부분인 require 이라는 부분에 문제가 발생하였다.

일단 찾아보니 Electron 12 부터 Context Isolation 이라는 것이 활성화 되었고 Renderer Process 에서 수행할 수 있는 것들이 제한이 생겼다. stackoverflow 의 top 댓글은 회피하는 방법에 대해 알려주고 있다. 사실 이 방법이 가장 빠르고 편하게 할 수 있는 방법 때문이었을 것이다.

하지만 다른 댓글에서 해당 부분에 대한 보안 위험성(security risk)에 대해 언급하며 contextBridge를 이용하여 ipcRenderer 바인딩을 넘기는 방법을 제안하는 것이 두 번째 답변으로 젹혀있다. github에서도 issue 9920 에 댓글에 동일한 내용이 적혀있다.

 

일단 문제에 집중하기 위해 Context Isolation 를 끄고 돌려보니 애플리케이션이 동작하였다.

 

---

이후 주말에 어떻게 바뀌었는지 확인하기 이해 boiler-plate 코드를 이용해서 변화를 학습하였다.

code0xff 라는 분의 Electron + React 데스크탑 앱 개발 (1) 라는 글로 시작을 했다.

여기에 보니 버전 5부터 nodeIntegration의 기본값이 false로 되어 있는 부분을 true로 바꾸어서 node의 process 객체의 versions 정보를 획득할 수 있도록 되어 있었다.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    We are using node <script>document.write(process.versions.node)</script>,
    Chrome <script>document.write(process.versions.chrome)</script>,
    and Electron <script>document.write(process.versions.electron)</script>.
  </body>
</html>

하지만 Electron 12부터는 컨텍스트 격리가 기본으로 동작하게 되므로 해당 부분은 에러가 발생하게 된다.

화면에는 버전들이 표시가 안된다!
process 객체를 renderer process에서 참조할 수 없다.

물론 BrowserWindow 생성시 webPreferences 에 contextIsolation: false 로 설정하면 정상적으로 동작은 시킬 수 있다.

하지만 위에 언급되었듯이 이 방법은 웹 페이지에서 require 등을 통해 외부에서 내부 파일 시스템의 접근을 통해 변형 또는 읽을 수 있는 잠재적인 취약성을 가지고 있어서 권장되지 않는다.

contextBridge 를 이용하여 필요한 정보만 넘기기

preload.js 생성

새로운 파일을 만들어서 아래와 같이 window 객체로 넘긴다.

const { contextBridge } = require('electron');

contextBridge.exposeInMainWorld('versions', {
    node: process.versions.node,
    chrome: process.versions.chrome,
    electron: process.versions.electron,
})

main.js 수정

preload 파일의 지정 및 nodeIntegration 및 contextIsolation 는 기본값으로 설정한다.

// ..
function createWindow () {
  win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'), // 절대 경로임에 주의한다.
      nodeIntegration: false,
      contextIsolation: true, // preload에서 ContextBridge를 사용하려면 true 여야 한다.
    }
  });
// ..

preload 는 절대 경로로 지정을 해야된다. 안하면 실행시 아래와 같은 경고가 나타난다.

preload script must have absolute path

또한 contextIsolation이 false로 되어 있으면 preload 에서 contextBridge 를 사용하려고 Main Process 쪽에 에러가 나타난다.
물론 화면에서도 값을 제대로 받을 수 없다.

index.html 수정

process 객체 대신에 renderer process 에 기본적으로 있는 window 객체를 통해 versions 을 통해 읽어온다.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    We are using node <script>document.write(window.versions.node)</script>,
    Chrome <script>document.write(window.versions.chrome)</script>,
    and Electron <script>document.write(window.versions.electron)</script>.
  </body>
</html>

이렇게 수정하고 다시 돌려보면 정보를 제대로 표현하게 되었다.

References