원문 제목: Six bugs that only appeared after real users installed my React security library 원문 링크: https://dev.to/nedunuri_anurag/six-bugs-that-only-appeared-after-real-users-installed-my-react-security-library-29mk 번역일: 2026-04-16 KST
작성자는 민감한 폼 입력값을 DOM 바깥으로 격리하기 위해 FieldShield라는 React 보안 입력 라이브러리를 만들었다. 핵심 아이디어는 단순하다. DOM에는 항상 xxxxx 같은 마스킹 문자만 남기고, 실제 값은 Web Worker 메모리 안에만 보관한다. 제출 시점에만 명시적으로 실제 값을 꺼낸다. 개발 환경에서는 완벽하게 보였지만, 실제 사용자가 npm으로 설치해 다양한 번들러와 CSS 환경에서 써보자 예상 못 한 버그가 터졌다.
첫 번째 치명적 문제는 워커 파일 경로였다. 작성자 로컬의 Vite 개발 환경에서는 절대 경로 기반 워커 로딩이 동작했지만, 소비자 프로젝트에서는 해당 파일이 node_modules 아래에 있어 dev server가 경로를 서빙하지 못했다. 결과적으로 입력이 비어 보였다. 해결책은 워커 소스를 번들에 인라인하고 blob URL로 생성하는 방식이었다. 글은 이 문제를 최근의 패키징 실수 사례와 연결하며, 배포 전 npm pack --dry-run을 반드시 확인하라고 강조한다.
두 번째 문제는 폰트였다. 라이브러리는 마스크 레이어와 실제 투명 입력 레이어를 겹쳐서 커서를 맞추는데, 개발 때는 monospace 폰트를 써서 문제가 드러나지 않았다. 하지만 소비자가 Inter 같은 proportional font를 쓰자 x의 폭과 실제 문자 폭이 달라져 커서가 점점 어긋났다. 이를 막기 위해 상속 가능한 텍스트 관련 CSS 속성들을 명시적으로 초기화해야 했다.
세 번째는 플레이스홀더 고스트 현상이었다. 작성자는 마스크 레이어와 실제 input 양쪽에 플레이스홀더를 렌더링하고 color: transparent로 실제 플레이스홀더를 숨긴다고 생각했다. 하지만 브라우저는 ::placeholder에 자체 opacity 규칙을 적용했고, 소비자 폰트가 달라지자 두 플레이스홀더 위치가 약간 어긋나며 흐릿한 잔상이 생겼다. 결국 ::placeholder에 대해 별도의 투명 처리 규칙을 추가해야 했다.
네 번째는 상위 루트에서 상속된 CSS였다. 소비자 앱의 #root에 text-align: center가 설정돼 있었고, 이 값이 라이브러리 내부 마스크 레이어까지 전파되면서 화면상 텍스트는 가운데 정렬되었지만 실제 입력 커서는 왼쪽에 남았다. 작성자는 오버레이 기반 보안 컴포넌트에서는 상속형 CSS 속성을 적극적으로 리셋해야 한다는 점을 교훈으로 제시한다.
다섯 번째는 스타일시트 import 경로였다. README에서 dist 경로의 CSS를 직접 import하라고 안내했지만, Vite 4 이상은 package.json의 exports 필드에 없는 경로 접근을 막는다. 파일이 실제로 존재해도 import가 실패했다. 해결은 "./style.css": "./dist/style.css" 같은 명시적 export 추가였다.
여섯 번째는 Ctrl+Z 동작이었다. 사용자가 undo를 누르면 이전 실제 값이 아니라 xxxx가 보였다. 작성자는 이것이 단순 버그가 아니라 설계상 보안 보장이라고 설명한다. 브라우저의 undo 히스토리는 DOM 변경만 추적하므로, DOM에 존재한 값이 xxxx뿐이라면 undo도 그것만 복원할 수 있다. 이후 버전에서는 Worker 메모리 기반의 커스텀 undo 스택을 추가할 계획이라고 한다.
이 글의 실무적 메시지는 명확하다. 보안 컴포넌트의 진짜 테스트는 단위 테스트만으로 끝나지 않는다. 다른 번들러, 다른 기본 폰트, 다른 CSS reset, 다른 앱 구조가 합쳐질 때 비로소 숨겨진 버그가 튀어나온다. 특히 npm 패키지, 워커, CSS 상속, 접근성/브라우저 기본 동작을 건드리는 FE 라이브러리라면 “내 환경에서 된다”는 검증은 거의 의미가 없다는 점을 잘 보여준다.