StoryHelper v1.7.2 패치노트 Thumbnail

미리보기 및 기능 상태 인디케이터 적용이 안되는 문제 해결

8 min read
패치노트문제해결확장프로그램

[버그 수정] StoryHelper v1.7.2 패치노트

미리보기 및 기능 상태 인디케이터가 동작하지 않던 문제를 해결했다.


어떤 문제였나

StoryHelper v1.7에서 사이드뷰 실시간 미리보기 기능을 새롭게 선보였다. 그런데 배포 후 실제 사용자 환경에서 미리보기가 전혀 동작하지 않고, 기능 상태 인디케이터도 표시되지 않는 문제가 있었다.

개발 환경에서는 멀쩡히 동작해서 그냥 배포했는데, 알고 보니 배포 버전에서만 터지는 버그였다.

문제가 있다는 걸 직접 인지하지 못하고 있었는데, 최근에 바빠서 티스토리 글을 자주 쓰지 않다 보니 먼저 발견하지 못했다. 이 문제를 인식하게 된 계기는 StoryHelper 삭제 피드백 수집 기능 덕분이었다. 여러 사용자분들이 성능 및 버그 문제로 삭제했다는 피드백을 남겨주셔서 직접 사용해보고서야 문제를 확인할 수 있었다. 피드백 남겨주신 분들 덕분에 빠르게 수정할 수 있었다. 감사합니다 🙏


원인 분석

버그는 두 가지 독립적인 원인으로 발생하고 있었다.

1. 번들링 시 스코프 오염 문제

StoryHelper는 크롬 확장프로그램 특성상 두 종류의 content script를 페이지에 주입한다.

  • contentInjected: 티스토리 에디터 DOM에 직접 기능을 추가하는 스크립트
  • contentUI: React로 렌더링하는 UI 컴포넌트 (사이드뷰 패널, 상태 인디케이터 등)

원래는 모든 주입 UI를 순수 DOM 메서드로 만들었는데, v1.7에서 사이드뷰 미리보기와 상태 인디케이터를 React 기반으로 전환했다. 문제는 이 두 코드가 별개의 파일로 개발되지만 Rollup 번들링 시 하나의 청크로 합쳐질 때 발생했다.

minify 과정에서 변수명이 f, t, n 같은 짧은 이름으로 uglify되는데, 두 코드 모두에 동일한 이름이 선언되어 스코프가 오염되면서 실행 자체가 되지 않는 문제였다. 개발 환경에서는 minify를 하지 않아 변수명이 그대로 유지되기 때문에 문제가 없었고, 배포 빌드에서만 터진 이유가 바로 이것이었다.

해결 방법: 각 content script 청크를 IIFE(즉시 실행 함수 표현식)로 감싸는 Vite 플러그인을 추가했다. 이렇게 하면 각 청크가 독립적인 스코프를 가지게 된다.

{
  name: 'iife-content-scripts',
  generateBundle(_options, bundle) {
    for (const chunk of Object.values(bundle)) {
      if (chunk.type === 'chunk' && /content/i.test(chunk.fileName)) {
        chunk.code = `(()=>{\n${chunk.code}\n})();`;
      }
    }
  },
}

2. iframe 보안 정책으로 인한 미리보기 차단

사이드뷰 미리보기는 티스토리 원본 미리보기 HTML을 캡처해서 srcdoc 속성으로 iframe에 주입하는 방식으로 동작한다. 이 iframe에 아래와 같이 sandbox 속성을 설정하고 있었다.

<iframe srcdoc="..." sandbox="allow-scripts allow-same-origin" />

allow-scriptsallow-same-origin동시에 사용하면 iframe 내부 스크립트가 자신의 sandbox 제약을 스스로 제거할 수 있는 보안 취약점이 생긴다. 브라우저는 이를 sandbox escape로 간주한다.

개발 환경(언팩 확장)에서는 이걸 콘솔 경고로만 처리하고 기능은 동작했지만, 배포 버전에서는 더 엄격하게 적용된다. 특히 manifest에 all_frames: true가 설정되어 있어, allow-same-origin으로 인해 iframe이 tistory.com origin을 갖게 되면 Chrome이 해당 iframe에 content script를 주입하려 시도한다. 이 과정에서 sandbox escape 가능성을 감지해 iframe 실행 자체를 차단했다.

all_frames: true
  → 새로운 tistory.com origin iframe 감지 (sh-preview-iframe)
  → content script 주입 시도
  → "allow-scripts + allow-same-origin = sandbox 탈출 가능" 감지
  → 차단

해결 방법: allow-same-origin을 제거하고, srcdoc HTML 내 상대 경로 리소스가 정상 로드되도록 <base href>를 주입했다.

// 변경 전
sandbox="allow-scripts allow-same-origin"

// 변경 후
sandbox="allow-scripts"
// srcdoc HTML 파싱 후 base 태그 주입
const doc = parser.parseFromString(srcdocTemplate, 'text/html');
const base = doc.createElement('base');
base.href = window.location.origin + '/';
doc.head.insertBefore(base, doc.head.firstChild);
💡

쉽게 설명하면, 사이드뷰 미리보기는 iframe을 사용하는데 iframe으로 연결되는 src가 tistory.com 도메인을 가지기 때문에 Storyhelper 확장프로그램은 그 iframe 안에도 inject를 시도한다. 이 경우에는 iframe에서 스크립트가 실행될 수 있기 때문에 보안 취약점으로 간주된다.

이 문제를 예방하기 위해 기본적으로 제약이 걸려있는데, 이 제약을 sandbox 속성으로 조절할 수 있다. allow-scripts는 스크립트 실행을 허용하고, allow-same-origin은 iframe이 부모 페이지와 같은 도메인(tistory.com)으로 인식되도록 허용한다.

문제는 이 두 가지를 동시에 허용하면, iframe 안의 스크립트가 "나는 tistory.com이야"라고 인식된 상태에서 실행되기 때문에, 스크립트가 자기 자신의 sandbox 제약을 직접 제거해버릴 수 있다는 점이다. 쉽게 말하면 열쇠를 가진 사람을 금고 안에 가둔 느낌이다. 브라우저는 이 조합 자체를 보안 위협으로 보고 배포 환경에서 아예 실행을 막아버린다.


정리

문제원인해결
상태 인디케이터 미표시 / 기능 전반 비동작번들링 시 minify 변수명 충돌로 스코프 오염content script 청크를 IIFE로 래핑
사이드뷰 미리보기 비동작allow-scripts + allow-same-origin sandbox escape로 Chrome이 iframe 차단allow-same-origin 제거 + <base href> 주입

두 문제 모두 개발 환경에서는 재현이 안 되는 특성이 있어서 발견이 늦어졌다. 다음부터는 배포 후에도 검증하는 습관을 두어야겠다는 생각을 했다.

v1.7.2 업데이트로 정상 동작을 확인했다. 불편함을 겪으신 분들께 죄송하고, 피드백 남겨주신 분들께 다시 한번 감사드립니다.

0
추천 글