새로운 DOM API, setHTML을 알아보자
웹 개발을 하다 보면 동적으로 HTML을 삽입해야 하는 경우가 많습니다. 이때 가장 흔하게 사용되는 방법은 innerHTML이지만, 이 방법은 크로스 사이트 스크립팅(XSS) 공격에 취약하다는 큰 단점이 있습니다. 그래서 많은 개발자들이 DOMPurify와 같은 외부 라이브러리를 사용해 HTML을 안전하게 삽입해왔습니다.
이제는 웹 표준에 내장된 안전한 방법으로 HTML을 삽입할 수 있게 될 전망입니다. 바로 새로운 DOM API인 setHTML()이 등장했기 때문입니다. 이 글에서는 setHTML()이 무엇인지, 왜 필요한지, 그리고 어떻게 사용하는지에 대해 자세히 알아보겠습니다.
setHTML()이란 무엇인가요?
setHTML()은 Element 인터페이스의 새로운 메서드로, 이름에서 알 수 있듯이 HTML 문자열을 DOM 요소에 안전하게 설정하는 역할을 합니다. 이 메서드는 단순히 HTML을 삽입하는 것을 넘어, 내장된 HTML Sanitizer API를 사용해 문자열을 파싱하고 소독(sanitize)하는 과정을 거칩니다.
setHTML()의 주요 특징은 다음과 같습니다.
- XSS 방지: 입력된 HTML 문자열에서 잠재적으로 위험한
<script>태그나onerror와 같은 인라인 이벤트 핸들러 등 XSS 공격을 유발할 수 있는 요소를 자동으로 제거합니다. - 유효성 검사: 현재 요소의 컨텍스트에 맞지 않는 잘못된 요소를 제거합니다.
- 기본 Sanitizer 제공: 별도의 설정 없이도 기본 Sanitizer 구성을 통해 안전하게 HTML을 삽입할 수 있습니다.
MDN 문서에 따르면, setHTML()은 허용되지 않은 HTML 엔티티를 제거할 뿐만 아니라, Sanitizer 설정에서 허용되었더라도 XSS에 안전하지 않은 요소나 속성은 무조건 제거합니다. 덕분에 개발자는 보안 걱정을 덜고 비즈니스 로직에 더 집중할 수 있게 됩니다.
왜 setHTML()을 사용해야 할까?
innerHTML이 있는데 굳이 setHTML()을 써야 하는 이유가 궁금하실 겁니다. 두 메서드를 비교하며 setHTML()의 장점을 살펴보겠습니다.
innerHTML과의 비교
innerHTML은 사용하기 간편하지만, 가장 큰 문제는 보안입니다. 외부에서 받은 신뢰할 수 없는 문자열을 innerHTML에 그대로 할당하면, 공격자가 삽입한 악성 스크립트가 그대로 실행될 수 있습니다.
const userInput = `<img src=x onerror="alert('XSS 공격!')">`;
const container = document.getElementById('container');
// userInput에 포함된 스크립트가 실행되어 경고창이 뜹니다.
container.innerHTML = userInput;
반면, setHTML()은 이런 위험을 원천적으로 차단합니다.
const userInput = `<img src=x onerror="alert('XSS 공격!')">`;
const container = document.getElementById('container');
// onerror 속성이 Sanitizer에 의해 제거되어 스크립트가 실행되지 않습니다.
container.setHTML(userInput);
setHTMLUnsafe()와의 비교
setHTML()과 함께 setHTMLUnsafe()라는 메서드도 있습니다. 이름에 "Unsafe"가 붙은 것에서 알 수 있듯이, 이 메서드는 기본적으로 HTML을 소독하지 않습니다. XSS에 취약한 요소를 포함시켜야 하는 아주 특수한 경우가 아니라면 setHTML() 사용이 강력히 권장됩니다. setHTMLUnsafe()는 신뢰할 수 있는 소스의 HTML을 삽입할 때만 제한적으로 사용해야 합니다.
결론적으로, setHTML()은 innerHTML의 편리함은 유지하면서 보안성을 크게 높인, innerHTML의 안전한 대안이라고 할 수 있습니다.
setHTML() 사용 방법
setHTML()의 기본적인 사용법은 매우 간단합니다 !
// 기본적인 사용법
const unsanitizedString = "이건 <b>안전한</b> HTML입니다. <script>alert('이건 실행되지 않아요!')</script>";
const targetElement = document.getElementById("target");
// 기본 Sanitizer를 사용해 HTML 삽입
targetElement.setHTML(unsanitizedString);
위 코드를 실행하면 <script> 태그는 제거되고 "이건 안전한 HTML입니다."라는 텍스트만 렌더링됩니다.
options.sanitizer로 사용자 정의하기
setHTML()은 두 번째 인자로 options 객체를 받아 Sanitizer의 동작을 제어할 수 있습니다. options.sanitizer 속성을 사용해 허용하거나 제거할 태그 및 속성을 직접 지정할 수 있습니다.
const unsanitizedString = "<div>div 태그</div> <p>p 태그</p> <button>button 태그</button>";
const targetElement = document.getElementById("target");
// 1. Sanitizer 객체를 직접 생성하여 전달
// div, p, button 태그만 허용합니다.
const customSanitizer = new Sanitizer({
elements: ["div", "p", "button"],
});
targetElement.setHTML(unsanitizedString, { sanitizer: customSanitizer });
// 2. SanitizerConfig 객체를 인라인으로 전달
// div와 p 태그를 제거합니다.
targetElement.setHTML(unsanitizedString, {
sanitizer: { removeElements: ["div", "p"] },
});
SanitizerConfig를 사용할 때 allowElements와 removeElements를 동시에 정의하는 등 정규화되지 않은 설정을 전달하면 TypeError가 발생하니 주의해야 합니다.
브라우저 호환성은 아직까진…?
아쉽게도 setHTML()은 아직 실험적인 기능이며, 글을 작성하는 시점 기준으로 대부분의 주요 브라우저에서 지원되지 않습니다. Firefox Nightly 버전에서 기본으로 활성화되었으며, 점차 다른 브라우저로 확산될 것으로 기대됩니다.
따라서 실제 프로덕션 환경에서 사용하기 전에는 반드시 MDN의 브라우저 호환성 표를 확인해야 합니다. 호환성이 확보되기 전까지는 Sanitizer API Polyfill을 사용해 비슷한 기능을 구현해볼 수 있습니다.
커뮤니티의 시선
setHTML()의 등장은 많은 개발자 커뮤니티에서 긍정적인 반응을 얻고 있습니다. 특히 Hacker News와 같은 포럼에서는 "25년 만에 드디어 나왔다"며 환영하는 분위기입니다.
- 라이브러리 의존성 감소: 경량화 라이브러리인 Lit의 개발자는
setHTML()이 표준으로 자리 잡으면unsafeHTML지시어 대신 이를 활용하여 DOMPurify와 같은 외부 의존성 없이도 안전한 HTML 렌더링을 구현할 수 있을 것이라며 기대감을 표했습니다. DOMPurify는 강력하지만,lit-html코어보다 몇 배 더 크기 때문에 내장 API의 등장은 큰 이점입니다. innerHTML의 안전한 대안: 많은 개발자들이setHTML()을innerHTML의 안전한 버전이자, npm에서 널리 쓰이는 DOMPurify의 내장 버전으로 이해하고 있습니다. 이는 웹 플랫폼 자체의 보안성이 한 단계 발전했음을 의미합니다.
개인적으로 정말 반가운 소식입니다 !
setHTML()은 innerHTML의 고질적인 보안 문제를 해결하고 웹 표준에 내장된 안전한 HTML 삽입 방법을 제공하는 중요한 API입니다. 비록 아직 실험 단계에 있지만, 이 기능이 모든 브라우저에 정식으로 도입되면 개발자들은 외부 라이브러리 의존성을 줄이고 더욱 안전하고 견고한 웹 애플리케이션을 구축할 수 있게 될 것입니다.
지금 당장 프로덕션에 적용하기는 어렵겠지만, 이 새로운 API의 존재를 인지하고 테스트해보면서 미래를 준비하는 것은 어떨까요? setHTML()이 가져올 더 안전한 웹 개발 환경을 기대해 봅니다.
개인적으로 지금 이 블로그도 HTML을 직접적으로 다루는 경우가 많은데 정말 희소식이네요! 🤣
얼른 안정화 되었으면 좋겠습니다 ! 🥹