figma

웹에서 플러그인 시스템 (새 탭에서 열림)

Figma는 서드파티 플러그인을 브라우저 기반 디자인 편집기 안에서 실행하면서도 보안·안정성·성능을 모두 확보해야 했다. 단순히 eval(PLUGIN_CODE)를 사용하는 것은 위험하고, 기존 플러그인처럼 플랫폼 성능을 저하시키거나 업데이트 때마다 깨지는 문제도 피해야 했다. 여러 접근을 검토한 결과, 당시에는 JavaScript Realm 기반 샌드박스를 선택했지만, 이후 보안 취약점 공개를 계기로 C로 작성된 JavaScript VM을 WebAssembly로 컴파일하는 방식으로 변경했다.

플러그인 시스템이 해결해야 할 제약

  • 플러그인은 접근성 검사, 번역, 색상 도구, 이미지 가져오기 등 사용자가 작성한 임의의 코드를 실행한다.
  • 플러그인이 Figma 편집기의 내부 데이터와 기능을 사용해야 하므로, 단순한 외부 웹페이지처럼 완전히 격리할 수는 없다.
  • 동시에 플러그인이 다음 영역에 영향을 주면 안 된다.
    • Figma 문서나 다른 사용자의 데이터에 대한 무단 접근
    • 편집기 UI와 실행 환경의 안정성
    • CPU·메모리 등 시스템 자원의 과도한 사용
    • Figma 업데이트에 따른 플러그인 호환성 저하
  • Figma는 WebGL, WebAssembly, TypeScript, React, 실시간 협업 기능을 함께 사용하는 구조라 일반적인 웹 애플리케이션보다 실행 환경이 복잡했다.

시도 1: <iframe> 샌드박스

  • 가장 표준적이고 검증된 웹 보안 기능인 <iframe>을 플러그인 실행 환경으로 검토했다.
  • iframe은 별도의 문서와 JavaScript 실행 컨텍스트를 제공해 플러그인 코드가 Figma의 전역 객체나 DOM에 직접 접근하지 못하게 할 수 있다.
  • sandbox 속성과 출처(origin) 분리를 사용하면 플러그인과 호스트 애플리케이션 사이의 경계를 강화할 수 있다.
  • 플러그인과 Figma 사이의 통신은 postMessage 같은 명시적인 메시지 전달 방식으로 제한할 수 있다.
  • 그러나 iframe 방식에는 중요한 한계가 있었다.
    • Figma 내부 데이터 구조에 대한 빠르고 자연스러운 접근이 어렵다.
    • 플러그인 API 호출을 위해 많은 객체와 요청을 직렬화·전달해야 한다.
    • 별도 브라우저 컨텍스트를 만들기 때문에 성능과 메모리 비용이 발생한다.
    • iframe 자체가 안전하더라도 플러그인이 CPU나 메모리를 과도하게 사용해 편집기를 느리게 만들 가능성은 남는다.
  • 따라서 일반적인 웹 위젯에는 적합하지만, Figma처럼 고성능 편집기와 긴밀하게 상호작용해야 하는 플러그인 환경에는 충분하지 않았다.

시도 2: JavaScript 인터프리터를 WebAssembly로 컴파일

  • 두 번째 접근은 플러그인 코드를 브라우저의 JavaScript 엔진에서 직접 실행하지 않고, 별도의 JavaScript 인터프리터 안에서 실행하는 방식이었다.
  • 인터프리터를 WebAssembly로 컴파일하면 플러그인 코드는 Figma의 실제 JavaScript 환경과 분리된 가상 실행 환경에서 동작한다.
  • 이 방식의 장점은 다음과 같다.
    • 플러그인이 브라우저의 전역 객체, DOM, Figma 내부 구현에 직접 접근할 수 없다.
    • 노출할 API를 명시적으로 선택할 수 있다.
    • 실행 환경을 통제하고 향후 브라우저 변경의 영향을 줄일 수 있다.
  • 반면 별도의 JavaScript 인터프리터를 실행해야 하므로 일반 JavaScript보다 느릴 수 있다.
  • 표준 JavaScript 기능과 내장 객체를 정확하게 구현해야 하며, 언어 호환성 문제도 발생한다.
  • 인터프리터 자체의 구현 오류나 보안 취약점이 샌드박스를 무너뜨릴 가능성도 고려해야 했다.
  • 당시에는 성능과 구현 복잡성이 주요 장애물이었다.

시도 3: JavaScript Realm

  • 세 번째 접근은 별도의 전역 환경과 객체 영역을 만드는 Realm 개념이었다.
  • Realm은 플러그인이 Figma의 전역 객체와 분리된 JavaScript 환경에서 실행되도록 하면서도, 필요한 API만 선택적으로 제공할 수 있게 한다.
  • iframe보다 가볍고, 별도 JavaScript 인터프리터를 내장하는 방식보다 브라우저의 기본 실행 성능을 더 많이 활용할 수 있다.
  • Figma는 다음과 같은 형태의 경계를 구성할 수 있었다.
    • 플러그인에 필요한 API만 노출
    • 호스트 객체와 플러그인 객체 사이의 직접 참조 제한
    • 허용된 요청만 Figma 내부 기능으로 전달
    • 플러그인 전역 환경과 Figma 전역 환경의 분리
  • 이 접근은 보안, 성능, API 사용성 사이의 균형이 가장 좋다고 판단되어 원래 구현에 채택됐다.
  • 다만 Realm은 당시 표준 기능으로 완전히 지원된 것이 아니라 shim에 의존해야 했다.
  • JavaScript 객체 모델과 프로토타입 체인을 완벽하게 격리하는 것은 매우 어려워, shim의 작은 결함도 보안 취약점으로 이어질 수 있었다.

운영 과정에서 드러난 보안 문제와 변경

  • 글 게시 후 Realm shim에서 보안 취약점이 비공개로 제보됐다.
  • 취약점은 공개되기 전에 shim 팀에 의해 수정됐고, Figma는 실제 악용 증거를 발견하지 못했다고 밝혔다.
  • 그러나 샌드박스의 핵심이 외부 라이브러리의 복잡한 JavaScript 격리에 의존한다는 점은 중요한 위험 요소였다.
  • Figma는 이후 C로 작성된 JavaScript VM을 WebAssembly로 컴파일하는 대안으로 구현을 변경했다.
  • 이 방식은 브라우저의 JavaScript 객체와 실행 컨텍스트를 더 강하게 분리해, Realm shim에 의존하는 공격 표면을 줄이는 방향이다.

설계에서 얻은 교훈

  • 서드파티 코드를 안전하게 실행하는 문제는 단순히 eval을 다른 API로 바꾸는 문제가 아니다.
  • 격리 수준, API 호출 비용, 실행 성능, 자원 제한, 유지보수성을 함께 평가해야 한다.
  • “브라우저 기능을 사용하므로 자동으로 안전하다”거나 “샌드박스이므로 모든 문제가 해결된다”고 볼 수 없다.
  • 특히 샌드박스 구현 자체가 복잡한 경우, 해당 구현의 취약점과 업데이트 정책까지 시스템의 보안 경계로 봐야 한다.
  • 가장 현실적인 설계는 플러그인에 필요한 최소 API만 노출하고, 실행 환경과 호스트 애플리케이션 사이의 통신을 명확한 경계로 제한하는 것이다.

플러그인 시스템을 설계할 때는 iframe, 별도 인터프리터, Realm 같은 선택지를 보안·성능·호환성 관점에서 비교해야 한다. 또한 외부 샌드박스 라이브러리에 의존한다면 정기적인 보안 검토와 교체 가능한 구조를 마련하고, 높은 보안 수준이 필요할 경우 WebAssembly 기반 독립 VM처럼 더 강한 실행 격리를 고려하는 것이 바람직하다.