RequireJS 지원 종료: AMD 로더의 위험 요소 및 보안 지원

지원 종료(EOL)된 PHP용 php2shell 패치(CVE-2026-109618 및 CVE-2026-109619)가 출시되었습니다. 수요가 많아 평소보다 영업팀의 답변이 늦어지고 있으므로, 패치된 패키지를 가장 빠르게 확보하려면 온라인에서 Runtimes용 ELS를 구매하시기 바랍니다 (새 탭에서 열림). 패치 FAQ 읽어보기

이 블로그 글의 AI 요약으로 바로 가기

  • Perplexity를 사용하여 이 게시물을 요약해 주세요
  • Gemini로 이 게시물을 요약해 주세요

주요 내용

  • RequireJS는 OpenJS 아카이브에 포함된 프로젝트이므로, 향후 발생할 수 있는 취약점을 수정할 책임이 있는 업스트림 유지보수자가 없습니다.
  • ES 모듈로의 전환은 모듈 정의, 로딩 방식, 빌드 최적화, 테스트 및 배포에 영향을 미칩니다.
  • CVE-2024-38999는 출처에 따라 ‘중간’에서 ‘심각’까지 다양한 위험 등급을 받으므로, 접근 가능 여부가 더 중요합니다.
  • Magento와 Moodle의 경우 RequireJS가 플랫폼에 내장되어 있으므로, AMD를 제거하는 것은 플랫폼 차원의 작업입니다.

RequireJS는 네이티브 ES 모듈이 등장하기 전부터 대규모 브라우저 애플리케이션이 모듈화될 수 있도록 도왔습니다. 활발한 개발은 수년 전에 중단되었지만, AMD 로더가 여전히 많은 기업용 프론트엔드 코드의 기반에 자리 잡고 있으며, 특히 해당 로더를 임의로 교체할 수 없는 플랫폼에서 자주 사용되고 있다는 점을 고려하면 이는 결코 사소한 일이 아닙니다.

이 글에서는 RequireJS와 같은 로더의 ‘수명 종료(end of life)’가 무엇을 의미하는지, 심각도 점수가 극도로 일관성이 없는 주요 CVE를 어떻게 처리해야 하는지, 그리고 AMD에서 벗어나는 것이 왜 단순한 의존성 버전 업데이트가 아닌 아키텍처 프로젝트인지를 다룹니다.

RequireJS는 브라우저보다 먼저 자바스크립트의 모듈화 문제를 해결했습니다.

조직 내 어딘가에는 아마도 여전히 다음을 통해 부팅되는 프론트엔드가 있을 것입니다. data-main 속성인데, 현재 팀원 중 누구도 이 코드를 작성하지 않았습니다. 약 10년 동안 아무 문제 없이 잘 로드되어 왔는데, 바로 그게 문제입니다.

RequireJS는 브라우저에 자바스크립트를 모듈로 분할하고 필요에 따라 불러올 수 있는 기본 기능이 없던 시기에 등장했습니다. 이 라이브러리는 AMD(비동기 모듈 정의 형식)를 구현하여, 대규모 코드베이스에서도 모든 코드를 하나의 파일로 합쳐서 순서만 잘 지켜지기를 바라는 대신, 의존성을 명시적으로 선언하고 이를 병렬로 로드할 수 있게 했습니다. 2013년 당시 브라우저에 수십만 줄의 코드를 배포하던 팀들에게 있어, 이러한 구조는 유지보수가 용이한 앱과 전역 변수의 늪을 가르는 결정적인 차이였습니다.

여전히 특정 곳에서는 이를 찾아볼 수 있습니다. 마젠토 2(Magento 2) 스토어프론트, 무들(Moodle) 배포 환경, 2015년경의 백본(Backbone)이나 녹아웃(Knockout) 애플리케이션, 그리고 다음과 연동된 내부 보고 도구 등입니다. data-main 그것을 지은 사람이 떠난 이후로 아무도 열어본 적이 없다는 것이다.

고장 난 부분은 없고, 적극적으로 유지보수하는 사람도 없으며, 기본 아키텍처상 부분적인 수정이 불가능합니다. 아키텍처상의 결정은 패치만으로 해결할 수 없습니다.

RequireJS는 여전히 유지보수되고 있나요?

어떤 의미에서든 이를 기반으로 지원 계획을 세우기에는 부적합하며, 이 표기는 공식적인 것입니다. OpenJS 재단은 RequireJS를 jQuery Mobile, jQuery UI, Sizzle, Pointer Events Polyfill과 같은 지원이 종료된 다른 라이브러리들과 함께 ‘보관된 프로젝트’로 분류하고 있습니다.

이 저장소는 여전히 공개 상태이며 npm 패키지도 여전히 설치할 수 있지만, ‘보관됨(Archived)’ 상태라면 다음 보안 권고 사항이 발표될 때 업스트림 개발자가 응답할 것이라고 기대할 이유가 전혀 없습니다.

알고 계셨나요? RequireJS는 OpenJS가 처음 이 라이브러리를 지원 종료 목록에 올린 지 5년 이상이 지난 2025년 11월 30일에 2.3.8 버전을 출시했습니다. 따라서 ‘지원 종료’라는 표기는 코드가 더 이상 업데이트되지 않는다는 의미가 아닙니다. 이는 아무도 이 라이브러리를 유지보수하겠다고 약속하지 않았다는 뜻이며, 이는 완전히 다른 문제입니다.

AMD는 플랫폼 측에서 해결책을 내놓기도 전에 브라우저 모듈 로딩 문제를 해결해냈고, 네이티브 ES 모듈과 최신 번들러들이 결국 이 프로젝트가 담당하던 모든 역할을 흡수해 버렸다. 심지어 이 프로젝트는 공식 홈페이지 메인 페이지에 현재 진행 상황을 공개적으로 밝히라는 요청까지 받은 바 있다. 해당 페이지에는 여전히 2.3.7이 최신 릴리스로 명시되어 있으며, ‘Archived’ 라벨에 대해서는 아무런 언급도 없다.

세 가지 다른 심각도 등급이 지정된 CVE 확인하기

38999 이는 RequireJS 스캔 결과로 나타나는 문제입니다. 이는 s.contexts._.configure 이는 2.3.6까지의 모든 릴리스에 영향을 미쳤으며, 해당 수정 사항은 2024년 7월에 출시된 2.3.7에 포함되었습니다.

이제 데이터베이스들이 동일한 버그에 대해 어떻게 점수를 매기는지 살펴보세요.

출처 CVSS 위험도
NVD (NIST) 점수 미부여 농축되지 않음
CISA-ADP 10.0 (v3.1) 중요
Tenable 9.3 (v4.0) 중요
GitHub 공지사항 8.9 (v4.0) 높음
Snyk 6.9 (v4.0) Medium

점수 차이가 매우 커 보이지만, 그 대부분은 버그 자체에 대한 실질적인 의견 차이보다는 평가 방식에서 비롯된 것입니다. CISA-ADP의 10.0은 CVSS v3.1 기준입니다. Tenable의 9.3점과 GitHub의 8.9점은 동일한 CVSS v4.0 기본 지표를 공유하며, GitHub의 점수가 낮은 이유는 ‘개념 증명(Proof-of-Concept, E:P)’ 단계의 ‘익스플로잇 성숙도(Exploit Maturity)’ 위협 지표를 반영했기 때문입니다. 진정한 특이치는 Snyk의 ‘중간(Medium)’ 등급으로, 이는 훨씬 낮은 영향도 평가에 근거한 것입니다.

이 CVE의 EPSS 점수는 약 0.76% (2026년 9월 14일 기준, 0~1점 척도에서 0.0076)이므로, 이 모델은 향후 30일 내 악용될 확률을 1% 미만으로 예측합니다. 이는 여전히 점수가 매겨진 모든 CVE 중에서 중간 수준(53번째 백분위수)에 해당하지만, 해당 백분위수는 다른 CVE와의 상대적 순위일 뿐, 확률을 나타내는 것은 아닙니다.

이 혼란의 일부는 해당 레코드가 처리된 방식에 기인합니다. NVD는 여전히 CVE-2024-38999를 ‘보류(Deferred)’ 상태로 분류하고 있어, 각 하위 데이터베이스는 NVD가 분석한 적도 없는 레코드에 자체적인 점수 산정 기준을 적용했습니다. RequireJS는 같은 기간에 두 번째 프로토타입 오염(prototype pollution) 보고서를 접수하기도 했습니다. 해당 보고서(CVE-2024-38998)는 config 함수를 대상으로 했으며, CNA가 보안 문제가 아니라고 결론 내린 후 2025년 1월에 기각되었습니다.

트라이아지(triage)의 경우, 점수는 단지 하나의 입력 요소일 뿐이며, 접근 가능성(reachability)은 숫자보다 더 큰 비중을 차지해야 합니다. 공격자가 제어하는 데이터가 애플리케이션이 전달하는 객체에 도달할 수 있는지 requirejs.config()? 바로 그 전화가 원동력이 되어 s.contexts._.configure. 만약 대답이 ‘아니오’라면, 배지의 색상과 상관없이 실질적인 위험은 낮습니다.

주의: 점수에 이의를 제기한다고 해서 보고서에서 해당 평가 결과가 삭제되지는 않습니다. “이 항목은 과대평가되었다고 생각합니다”라는 말은 단순한 의견일 뿐, 시정 조치가 아닙니다.

실제로, 보고서에 CVE가 포함되어 있는데 수정된 버전으로 전환할 수 없는 경우, 패치가 적용된 빌드나 서면으로 작성된 악용 가능성 진술서 중 하나가 필요합니다. CVSS에 대한 주장은 이 두 가지 중 어느 하나에도 해당되지 않습니다.

RequireJS의 대안을 선택하는 것은 아키텍처상의 결정입니다.

RequireJS를 더 최신 버전으로 교체하는 것은 의존성 변경으로 보입니다. 이는 애플리케이션이 코드를 어떻게 로드할지 결정하는 문제와 더 가깝습니다.

실제적으로 고려할 수 있는 네 가지 방향이 있으며, 이 방향들은 서로 동일하지 않습니다. 네이티브 ES 모듈은 브라우저나 런타임이 로딩을 처리하도록 하며, RequireJS는 완전히 사라집니다. 번들러가 관리하는 모듈은 모듈 그래프를 webpack, Vite 또는 esbuild에 전달하며, 이들은 빌드 시점에 이를 해결하고 최적화된 출력물을 제공합니다. 증분 어댑터와 상호 운용성 심(shim)은 긴 마이그레이션 기간 동안 AMD와 ES 모듈이 공존할 수 있도록 해줍니다. 또한 일부 구형 환경이나 접근이 제한된 환경에서는 여전히 일반 스크립트 로딩이 필수적입니다.

이 네 가지 문제 모두 밑바탕에는 간과하기 쉬운 한 가지 의문이 깔려 있습니다. 해당 애플리케이션에 여전히 런타임 모듈 로딩이 필요한지, 아니면 그저 개발 당시의 시대적 제약이었는지 말입니다. 많은 AMD 아키텍처가 존재하는 이유는 오로지 2013년 당시의 브라우저들이 다른 대안을 제공하지 않았기 때문입니다.

게다가 코드 자체에 내재된 가정들이 있는데, 바로 이 부분에서 예상치가 빗나가는 경우가 많습니다. 동일한 트리 내에 명명된 모듈 정의와 익명 모듈 정의가 뒤섞여 있을 것으로 예상해야 하며, 다음과 같은 플러그인 접두사도 고려해야 합니다. text! 템플릿을 불러오기 위해, 에 정의된 별칭 paths, 그리고 shim AMD가 아닌 전역 변수에 대한 항목들입니다. 로더가 우연히 용인한 순환 의존성, 모듈처럼 보이게 하기 위해 모듈로 감싸진 전역 변수(실제로는 모듈이 아님), 그리고 배포 환경에 특화된 번들을 생성하는 최적화기 구성 등을 예상할 수 있습니다.

알아두면 좋은 팁: 코드베이스에서 다음을 grep으로 검색하세요 text!, shim, 그리고 paths 무엇을 검토하기 전에. 이 세 가지 패턴은 단순한 파일 수보다 마이그레이션 시 발생할 어려움을 더 잘 예측해 줍니다. 각 패턴은 번들러가 그대로 재현할 수 없는 런타임 동작을 반영하고 있기 때문입니다.

누구도 코드를 손대기 전에 먼저 목표를 정하세요. ES 모듈로 변환하는 것과 최적화기 아티팩트 레이아웃을 재현하는 것은 예산이 다른 별개의 프로젝트입니다.

자동 변환만으로는 왜 한계에 부딪히게 되는가

Codemods와 jscodeshift 변환은 실제로 존재하며, 구문을 재배치합니다. 일반 정적 모듈의 경우, a define([...], function(){}) 블록이 다음과 같이 변합니다 import 별다른 드라마 없이 진술을 했다. 문제는 구문이 결코 어려운 부분이 아니었다는 점이다.

런타임 가정은 변하지 않습니다. 모듈이 다르게 해결되는 순간 로드 순서와 부수 효과가 달라질 수 있으며, 특정 AMD 로드 순서에 의존하던 앱은 정적 분석으로는 감지할 수 없는 방식으로 오작동할 수 있습니다. 테스트는 종종 RequireJS 구성에 직접 연동되어 있어, 테스트 대상 코드와는 무관한 이유로 실패하기도 합니다. 프로덕션 배포 환경에서는 특정 최적화된 아티팩트 레이아웃을 기대할 수 있지만, 새로운 번들러는 이를 생성하지 못할 수도 있습니다. 또한 런타임에 변수를 조합하여 생성되는 동적 모듈 이름은 그 특성상 정적 분석이 불가능합니다.

그리고 ‘플랫폼 함정’이라는 문제가 있는데, 마젠토(Magento)나 무들(Moodle) 배포 환경이 바로 여기에 해당합니다. 이러한 플랫폼에서 RequireJS는 프론트엔드 아키텍처의 일부입니다. 일반적으로 해당 구성을 확장할 수 있습니다(마젠토 모듈과 테마는 자체적인 requirejs-config.js (예: 파일 등)이지만, 로더 자체를 교체하는 것은 플랫폼 버전이 지원하는 범위에 제한을 받습니다.

현장의 실태: 30여 개의 맞춤형 RequireJS 모듈이 적용되고, 플랫폼의 로더 설정을 부분적으로 재정의하는 테마가 적용된, 대대적으로 커스터마이징된 Magento 환경을 사용하는 소매업체를 상상해 보세요. 플랫폼을 업그레이드하면 최신 버전의 RequireJS가 번들로 제공될 수 있지만, 그 후에도 스토어프론트는 여전히 AMD를 통해 부팅됩니다. 이러한 환경에서 RequireJS를 벗어나는 것은 귀사의 로드맵만큼이나 플랫폼의 로드맵에 달려 있으며, 코드모드(Codemod)로는 이를 수정할 수 없습니다.

그러니 대략적인 추정을 하기 전에 먼저 개수 확인 작업을 시작하세요. 파일 수를 세고, 심(shim) 항목 수를 세세요. 로더 설정 중 어느 정도가 여러분의 몫이고 어느 정도가 플랫폼에 속하는지 파악하세요. 이 세 가지 답변은 이후의 거의 모든 과정을 좌우하며, 특히 마지막 답변은 마이그레이션이 과연 여러분의 통제 하에 있는지를 결정합니다.

AMD에서 최신 모듈로 이어지는 단계별 경로

마이그레이션이 필요한 경우, 단계적 접근 방식이 일괄 재작성보다 거의 항상 더 낫습니다. 실제 운영 환경에서 통하는 일반적인 순서는 모듈 그래프를 매핑하여 어떤 모듈이 어떤 모듈에 의존하는지 파악하는 것부터 시작합니다. 그 다음, 리프 모듈과 기타 위험이 낮은 영역을 먼저 변환하고, 문제가 발생했을 때 우연히가 아니라 의도적으로 호환성 계층을 도입해야 합니다. 테스트는 해당 프로덕션 모듈이 변경된 후에가 아니라, 테스트 대상인 프로덕션 모듈과 동시에 변환해야 합니다. 각 단계가 끝날 때마다 번들 크기와 로딩 동작을 측정하고, 쉴름을 사용하는 모든 모듈이 제거된 후에야 쉴름을 제거해야 합니다.

각 단계가 끝날 때마다 앱은 배포 가능한 상태가 되어야 합니다. ‘리프-퍼스트(leaf-first)’ 순서를 따르는 이유는, 로딩 방식이 변경될 경우 위험이 낮은 모듈은 깔끔하게 원상태로 되돌릴 수 있지만, 의존 관계가 깊게 얽힌 모듈은 그렇게 할 수 없기 때문입니다.

전문가 팁: 아키텍처상 가능하다면, 전환 기간 동안 피처 플래그를 통해 기존 RequireJS 빌드와 새로운 번들러 빌드를 병행하여 실행하고, 기존 로더를 제거하기 전에 두 빌드의 네트워크 워터폴을 비교해 보세요. 요청 순서의 변화는 종종 부수 효과가 이동했음을 나타내는 첫 번째 단서가 됩니다.

하지만 모든 애플리케이션이 이 방식을 따를 필요는 없습니다. 올바른 대응 방안은 빌드를 누가 관리하는지와 아티팩트가 얼마나 고정되어 있는지에 따라 달라집니다. 코드베이스가 작고 빌드를 완전히 통제할 수 있다면, ES 모듈로 변환하고 끝내면 됩니다. 플랫폼 측에서 로더를 관리하는 경우에는 현장에서 패치를 적용하고, 플랫폼 업그레이드는 구체적인 일정이 정해진 로드맵에 포함시키면 됩니다. 고정된 아티팩트나 에어갭(air-gap) 환경이 요구되는 경우에는 현장에서 패치를 적용하고, 감사관을 위해 취약점 악용 가능성을 문서화해야 합니다.

기존 로더에 여전히 보호 조치가 필요한 경우

때로는 마이그레이션이 이번 분기 계획에 포함되지 않더라도, 그동안 로더는 여전히 안전하게 작동해야 합니다. 이러한 상황은 수명이 긴 엔터프라이즈 포털, 내장형 관리 콘솔, 그리고 업그레이드 시기가 엄격하게 제한된 제품에서 흔히 발생합니다. 또한, 현대화 대신 교체되는 애플리케이션이나, 회귀 테스트 범위가 아직 로더 마이그레이션을 지원할 수 없는 시스템에서도 같은 상황이 발생합니다.

업데이트 상한선에 대해 냉철하게 판단해야 합니다. 2.3.7이나 현재 버전인 2.3.8로 업데이트하면 CVE-2024-38999가 해결되지만, 업스트림에서 제공하는 업데이트는 거기까지입니다. 2.4 버전이 출시될 것이라는 징후는 전혀 없습니다. 다음 보안 권고에는 유지보수 약속이 포함되어 있지 않으며, 권고가 발표되더라도 대응 시간이 보장되지 않습니다.

현실 점검: 최신 버전의 RequireJS로 업그레이드한다고 해서 지원이 보장되는 것은 아닙니다. 오늘 수정된 버전이 있다고 해서 내년에 보고될 취약점에 대해 아무것도 보장하지 않습니다.

TuxCare의 RequireJS용 ‘Endless Lifecycle Support(ELS)’는 버전 2.1.22 및 2.3.6을 지원하며, TuxCare가 호스팅하는 레지스트리에서 npm 패키지로 제공됩니다. 여기에는 SBOM 및 VEX 문서와 CVSS 심각도에 연동된 대응 목표가 포함됩니다. 이는 업스트림 개발이 아닌 관리 업무에 해당합니다. 팀은 새로운 CVE가 지원 대상 버전에 적용되는지 확인하고, 수정 사항을 백포트한 후, 배포 전에 모든 빌드의 무결성, 기능 및 회귀 현상을 테스트하여 기존 코드가 현재와 동일한 방식으로 계속 작동하도록 보장합니다. 로더의 경우 동작 방식이 달라지면 안 되기 때문에 이 마지막 단계가 특히 중요합니다. 이는 업스트림에서 이미 지원이 종료된 컴포넌트를 계속 사용해야 하는 팀을 위한 TuxCare의 광범위한 EOL 라이브러리 지원의 일환입니다.

프로젝트의 활동 단계가 끝난 후 OpenJS의 지속 가능성

RequireJS가 OpenJS의 ‘아카이브’ 프로젝트로 분류되었다고 해서 보안 문제가 끝난 것은 아닙니다. 재단의 ‘생태계 지속 가능성 프로그램(Ecosystem Sustainability Program)’은 바로 아카이브된 버전, 지원 종료(EOL) 버전, 구버전 등이 상용 보안 서비스를 받을 수 있도록 마련된 것으로, 수익의 일부는 재단과 그 프로젝트에 환원됩니다. TuxCare는 이 프로그램의 골드 회원이자 제공업체이며, 이 회사가 제공하는 패치는 다운스트림에 적용됩니다. 이러한 패치는 RequireJS의 업스트림 릴리스로 포함되지는 않습니다.

그러니 RequireJS를 프론트엔드를 하나로 묶어주는 아키텍처의 일부로 여기세요. 그게 바로 RequireJS의 역할이니까요. 금요일 오후에 이를 삭제해 버리면 일이 잘 풀리지 않을 것이며, 이를 대체하려면 서둘러 패키지만 교체하는 것 이상의 신중한 접근이 필요합니다.

요약
RequireJS 지원 종료: AMD 로더의 위험 요소 및 보안 지원
기사 이름
RequireJS 지원 종료: AMD 로더의 위험 요소 및 보안 지원
설명
RequireJS는 유지보수 약속이 없는 OpenJS 아카이브 프로젝트입니다. EOL이 AMD 프론트엔드에 어떤 의미를 지니는지, 그리고 어떤 지원 옵션이 있는지 확인해 보세요.
작성자
게시자 이름
TuxCare
게시자 로고

4,500명 이상의 Linux 및 오픈 소스 전문가와 함께하세요!

한 달에 두 번. 스팸이 없습니다.

목차
필요한 오픈 소스 보안에 대한 답변 얻기

4,500명 이상의 Linux 및 오픈 소스 전문가와 함께하세요!

한 달에 두 번. 스팸이 없습니다.