JavaScript의 섀도우 리얼름 탐구: 보안 샌드박싱을 위한 활용
Emily Parker
Product Engineer · Leapcell

소개
웹 개발의 복잡한 세계에서 JavaScript는 동적이고 상호작용적인 사용자 경험을 제공하며 최고입니다. 그러나 강력한 힘에는 큰 책임이 따르며, 개발자가 직면하는 지속적인 과제 중 하나는 특히 타사 코드 통합 또는 사용자 정의 스크립트 처리 시 안전하고 안정적인 실행 환경을 유지하는 것입니다. 전통적인 접근 방식은 종종 <iframe>
요소 또는 Web Worker를 사용하지만, 각각 과부하, 통신 복잡성 및 실제 격리에 대한 고유한 제한 사항이 있습니다. 신뢰할 수 없는 JavaScript 코드를 기본 애플리케이션 스레드 내에서 직접 실행하면서도 전역 상태를 방해하거나 DOM을 수정하거나 민감한 데이터에 액세스하지 못하도록 완벽하게 확신할 수 있는 시나리오를 상상해 보세요. 이 야심 찬 목표는 ECMAScript 표준의 획기적인 제안인 Shadow Realms API 덕분에 이제 실현 가능해 보입니다. 이 글에서는 Shadow Realms의 흥미로운 잠재력을 살펴보고, 아키텍처, 실제 유용성 및 안전한 JavaScript 샌드박싱을 혁신하는 방법을 자세히 알아보겠습니다.
Shadow Realms 이해하기
Shadow Realms의 실제 적용 방법을 자세히 알아보기 전에 몇 가지 핵심 개념에 대한 공통된 이해를 확립해 보겠습니다.
- Realm (리얼름): JavaScript에서 리얼름은 완전하고 독립적인 실행 환경을 나타냅니다. 각 리얼름은 자체 전역 객체, 내장 객체(
Object
,Array
,Function
등) 및 고유한 전역 변수 집합을 가집니다. 기본적으로 웹 페이지는 단일 리얼름 내에서 실행됩니다.<iframe>
요소와 Web Worker는 우연히 새 리얼름을 생성하지만, 통신 과부하가 크고 실행 컨텍스트가 다릅니다(Web Worker의 경우 다른 이벤트 루프,<iframe>
의 경우 별도의 브라우징 컨텍스트). - Sandbox (샌드박스): 샌드박스는 실행 중인 프로그램을 분리하는 보안 메커니즘으로, 일반적으로 기본 시스템을 방해하지 못하도록 격리합니다. JavaScript의 맥락에서 샌드박스는 신뢰할 수 없는 코드의 호스트 환경 액세스를 제한하여 악의적인 행동이나 의도하지 않은 부작용을 방지하는 것을 목표로 합니다.
- Shadow Realm (섀도우 리얼름): 섀도우 리얼름은 격리를 위해 설계된 특정 유형의 리얼름입니다. 다른 리얼름 생성 메커니즘과 달리 섀도우 리얼름은 부모 리얼름과 동일한 실행 컨텍스트(동일한 이벤트 루프) 내에서 밀접하게 통합되도록 설계되었지만, 전역 객체 및 내장 객체의 엄격한 분리가 이루어집니다. 이를 통해
<iframe>
또는 Web Worker보다 통신 지연 시간이 짧고 리소스 공유가 더 효율적이며 강력한 보안 경계를 제공합니다.
Shadow Realms의 핵심 원리는 부모 리얼름의 전역 상태와 완전히 분리된 새롭고 깨끗한 JavaScript 실행 환경을 만드는 것입니다. 섀도우 리얼름을 생성할 때 새로 생성된 전역 객체(globalThis
), 새로 생성된 내장 객체 집합 및 완전히 비어 있는 스코프를 얻게 됩니다. 즉, 섀도우 리얼름 내에서 실행되는 코드는 부모 리얼름에 정의된 변수나 함수에 직접 액세스하거나 수정할 수 없으며, 명시적으로 노출되지 않는 한 부모의 DOM이나 다른 브라우저 API를 조작할 수도 없습니다.
섀도우 리얼름의 생성 및 상호 작용을 설명하는 기본 예제를 살펴보겠습니다.
// 부모 리얼름에서 (기본 스크립트) // 새 Shadow Realm 인스턴스 생성 const sr = new ShadowRealm(); // 부모 리얼름에서 함수 정의 const parentFunction = () => "Hello from the parent!"; // Shadow Realm 내에서 코드 실행 시도 try { // 'evaluate'를 사용하여 Shadow Realm에서 문자열 코드 실행 const result = sr.evaluate(` // 'window'와 'document'는 importValue/exportValue를 통해 명시적으로 노출되지 않는 한 // 여기서 직접 액세스할 수 없습니다. const shadowFunction = () => "Hello from the Shadow Realm!"; // parentFunction에 직접 액세스할 수 없습니다. // console.log(parentFunction()); // 가져오지 않았다면 오류가 발생합니다. shadowFunction(); `); console.log("Shadow Realm 평가 결과:", result); // 출력: Shadow Realm 평가 결과: Hello from the Shadow Realm! // 이제 importValue/exportValue를 사용하여 상호 작용을 시연해 보겠습니다. const exportedValue = sr.evaluate(` (() => { // Shadow Realm 내부에서 함수 생성 const greet = (name) => itulo{Hi, } itulo{name} from Shadow! `; return greet; })() `); // 부모 리얼름으로 함수 가져오기 const shadowGreet = sr.importValue(exportedValue); console.log("가져온 함수 호출:", shadowGreet("Alice")); // 출력: 가져온 함수 호출: Hi, Alice from Shadow! // 부모 리얼름의 함수를 Shadow Realm으로 내보내기 const parentLogger = (message) => console.log(`Parent log: itulo{message}`); sr.exportValue(parentLogger, 'logToParent'); sr.evaluate(` // 내보낸 'logToParent' 함수 가져오기 const logger = import 'logToParent' from 'shadowrealm'; logger('이 메시지는 Shadow Realm에서 왔습니다!'); `); // 부모 콘솔 출력: Parent log: 이 메시지는 Shadow Realm에서 왔습니다! } catch (error) { console.error("Shadow Realm 오류:", error); } // 부모에서 직접 Shadow Realm 함수에 액세스 시도(실패함) // console.log(sr.shadowFunction()); // ReferenceError: shadowFunction is not defined
이 예제에서 new ShadowRealm()
은 격리된 환경을 생성합니다. evaluate()
메서드는 해당 환경 내에서 JavaScript 코드 문자열을 실행하며, 반환 값은 부모 리얼름으로 안전하게 마샬링됩니다. 중요한 것은 importValue()
및 exportValue()
와 같은 메서드는 리얼름 간에 값을 교환하는 제어된 메커니즘을 제공하여 필요한 것만 신중하게 노출할 수 있도록 하여 보안 위험을 완화합니다. 값이 교환될 때 기본 값인 경우