
로컬 패키지 설치 도우미가 호출자가 제공한 패키지 이름을 지나치게 신뢰했습니다. yeoman-environment에서 누락된 generators는 사용자 확인 없이 설치될 수 있어, 공격자가 제어하는 프로젝트 메타데이터가 패키지 설치 및 코드 실행 경로로 변질될 수 있었습니다.
로컬 패키지 설치 헬퍼가 호출자가 제공한 패키지 이름을 과도하게 신뢰했습니다. yeoman-environment에서 누락된 제너레이터가 사용자 확인 없이 설치될 수 있었고, 이로 인해 공격자가 제어하는 프로젝트 메타데이터가 패키지 설치 및 코드 실행 경로로 바뀔 수 있었습니다.
간단한 보안 질문을 염두에 두고 generator-jhipster를 리뷰하던 중 이 문제를 발견했습니다:
공격자가 제어하는 프로젝트 메타데이터가 개발자 도구로 하여금 사용자가 명시적으로 요청하기 전에 타사 코드를 가져와 실행하게 만들 수 있는가?
이 경우, 답은 '그렇다'였습니다.
처음에는 JHipster 문제처럼 보였지만, 알고 보니 더 깊은 상위 근본 원인이 yeoman-environment에 있었습니다.
취약한 동작은 Yeoman의 로컬 제너레이터 설치 흐름에 있었는데, 호출자가 제공한 누락된 패키지가 사용자 확인 없이 자동으로 설치되었습니다. 해당 경로에 공격자가 제어하는 패키지 이름을 전달하는 다운스트림 소비자가 있다면, 실제 패키지 설치 및 코드 실행 체인을 만들기에 충분했습니다.
이 문제는 CVE-2026-42089가 되었습니다.
yeoman-environment: GitHub의 yeoman-environment
패키지: yeoman-environment (npm)
CVE: CVE-2026-42089
이 취약점은 Yeoman의 제너레이터 로딩 및 부트스트래핑 흐름 뒤에서 작동하는 런타임 계층인 yeoman-environment에 영향을 미쳤습니다. 공식 프로젝트는 이 구성 요소가 제너레이터 수명 주기와 검색을 처리한다고 설명하며, 2026년 6월 26일 기준 npm 패키지 페이지에는 주간 다운로드 1,466,426회가 기록되어 있어 JavaScript 도구 생태계에서 널리 배포된 패키지임을 보여줍니다.
attacker-controlled project config -> caller-supplied generator package names -> yeoman-environment silently installs missing packages -> downstream tool loads installed generator code -> package installation and code execution during CLI bootstrap
yeoman-environment는 Yeoman 기반 도구 뒤에서 작동하는 런타임 및 제너레이터 로딩 계층입니다.
그 외에도 다음과 같은 작업을 처리합니다:
즉, 신뢰 경계에 바로 위치합니다.
핵심 질문은 Yeoman이 '단순한 로컬 도구'인지 여부가 아닙니다.
핵심 질문은 신뢰할 수 없는 입력이 패키지 설치 및 코드 로딩 동작에 영향을 미칠 수 있는지입니다.
이 경우, 그럴 수 있었습니다.
저는 여기서 메모리 손상이나 크래시 전용 버그를 찾고 있던 것이 아니었습니다.
더 강력한 목표는 확장 및 패키지 해석 공격 표면이었습니다.
다음을 수행하는 모든 시스템은 면밀한 조사 대상입니다:
특히 다운스트림 소비자가 프로젝트 로컬 데이터에서 해당 패키지 이름을 파생할 수 있는 경우에는 더욱 그렇습니다.
일반적인 구성이 조용히 보안 경계가 될 수 있는 바로 그런 지점입니다.
그곳이 바로 주목해야 할 올바른 위치였습니다.
먼저 generator-jhipster를 통해 해당 동작을 재현했습니다.
중요한 경로는 다음과 같았습니다:
.yo-rc.json이 블루프린트 패키지를 선언이는 다음과 같은 무해한 명령조차도 의미했습니다:
jhipster --help
요청한 명령이 완료되기 전에 패키지 설치에 도달할 수 있었습니다.
이것은 실제 신뢰 경계 실패입니다.
다운스트림 트리거가 이를 드러내는 데 도움이 되었지만, 안전하지 않은 기본 동작은 Yeoman에 있었습니다.
버그는 단순했습니다.
yeoman-environment에서 취약한 메서드는 다음과 같습니다:
async installLocalGenerators(packages) {
const entries = Object.entries(packages);
const specs = entries.map(([packageName, version]) => `${packageName}${version ? `@${version}` : ''}`);
const installResult = await this.repository.install(specs);
const failToInstall = installResult.find(result => !result.path);
if (failToInstall) {
throw new Error(`Fail to install ${failToInstall.pkgid}`);
}
await this.lookup({ packagePaths: installResult.map(result => result.path) });
return true;
}
이 메서드는 호출자가 제공한 패키지 이름을 다음을 통해 직접 설치했습니다:
this.repository.install(specs)
사용자에게 먼저 확인을 요청하지 않고 말입니다.
이것이 핵심 취약점입니다.
패키지 이름이 신뢰할 수 있는 출처에서 올 필요가 없기 때문입니다.
다운스트림 소비자가 이를 공격자가 제어하는 프로젝트 메타데이터에서 파생한다면 악용 체인은 간단합니다:
이것은 단지 "패키지 설치가 발생했다"는 것이 아닙니다.
신뢰할 수 없는 입력이 명시적 동의 경계 없이 패키지 설치 싱크로 넘어가는 것입니다.
핵심적인 차이는 신뢰할 수 없는 입력으로 인한 자동 설치입니다.
다음 사이에는 실제 차이가 있습니다:
그 패키지가 직후에 바로 로딩 가능해질 때 그 차이는 더욱 중요해집니다.
문제는 타사 제너레이터가 존재한다는 것이 아니었습니다.
문제는 Yeoman이 호출자가 제공한 패키지 이름을 사용자 확인 없이 기본적으로 설치 가능한 것으로 취급했다는 점이었습니다.
그로 인해 안전하지 않은 다운스트림 신뢰 가정이 실질적으로 더욱 악화됩니다.
이것이 바로 수정 사항에 확인 게이트가 추가된 이유입니다.
근본 원인과 실제 다운스트림 영향을 모두 입증할 수 있었기 때문에 두 가지 계층의 증명을 사용했습니다.
첫 번째 증명은 수정되지 않은 generator-jhipster를 사용했습니다.
아직 설치되지 않은 블루프린트 패키지를 참조하는 루트 .yo-rc.json이 있는 프로젝트를 만든 후 다음을 실행했습니다:
jhipster --help
그 결과 JHipster는 도움말이 완료되기도 전에 누락된 블루프린트를 Yeoman의 로컬 제너레이터 설치 흐름으로 전달했습니다.
중요한 결과는 다음과 같습니다:
이를 통해 실제 트리거 조건이 명확히 입증되었습니다.
두 번째 증명은 제어된 로컬 레지스트리와 import 시 부작용을 안전하게 입증하도록 설계된 패키지를 사용했습니다.
더 강력한 시나리오를 보여주고 싶었기 때문에 이것이 중요했습니다:
이것은 문제를 다음 범위를 넘어서게 했기 때문에 가장 강력한 증거 체인이었습니다:
"예상치 못한 설치 시도"
그리고 다음 단계로:
"설치 및 다운스트림 코드 로딩 경로가 실제로 도달 가능함"
신뢰 경계 실패를 무시하기가 훨씬 어려워지는 지점입니다.
첫 번째 PoC는 자동 설치 동작을 증명합니다.
두 번째 PoC는 그 동작이 왜 중요한지 증명합니다.
이러한 구분은 중요했습니다.
다음에서 멈추는 보고서는:
"패키지가 설치될 수 있다"
다음을 보여주는 보고서보다 약합니다:
그것이 완전한 이야기입니다.
권고 처리 및 로컬 검토 중에 이 동작은 다음에서 installLocalGenerators()가 도입된 시점까지 추적되었습니다:
yeoman-environment 2.9.0
따라서 영향 범위는 다음과 같습니다:
>= 2.9.0 and < 6.0.1
수정 버전은 다음과 같습니다:
6.0.1
수정은 정확하고 최소한이었습니다.
6.0.1에서는 강제 설치가 명시적으로 요청되지 않는 한 설치 전에 확인 단계를 추가하도록 installLocalGenerators()가 변경되었습니다.
수정된 형태는 다음과 같습니다:
async installLocalGenerators(packages, forceInstall = false) {
그리고 다음과 같습니다:
const { aproveInstall } = await this.adapter.prompt({
message: `The following packages need to be installed in the local repository: ${specs.join(', ')}. Do you want to proceed?`,
type: 'confirm',
name: 'aproveInstall',
default: false,
});
사용자가 거부하면 설치가 중단됩니다.
누락된 신뢰 경계를 복원하기 때문에 올바른 수정입니다:
이 수정은 다음에 반영되었습니다:
78d2af7
다음을 통해:
PR #753
이것은 바로 이런 보안 문제에서 원하는 유형의 수정입니다:
이 문제는 영향이 단순히 외관상의 문제나 예상 밖의 동작 수준을 넘어섰기 때문에 합당하게 진지하게 받아들여졌습니다.
취약한 동작은 다음으로 이어질 수 있습니다:
이 문제와 연관된 CVSS 벡터는 다음과 같습니다:
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
다운스트림 악용 시나리오에 비추어 볼 때 타당합니다:
이 문제는 generator-jhipster에 대한 비공개 보고로 시작되었습니다. 처음에 검증한 실제 트리거 경로였기 때문입니다.
트라이지 중 JHipster 관리자들은 자동 설치 동작 자체가 yeoman-environment에 있다고 지적하며 상위 수정 사항을 언급했습니다.
그로 인해 올바른 방향 전환이 이루어졌습니다:
Yeoman 관리자들은 문제를 검토하고 영향 범위를 확인한 후 비공개 권고로 추적했습니다.
보고서는 이후 다음과 같이 지정되었습니다:
CVE-2026-42089
해당 권고는 generator-jhipster를 통한 실제 다운스트림 트리거 경로도 문서화했습니다.
조정된 공개가 때때로 한 단계를 더 필요로 하는 이유를 보여주는 좋은 사례였습니다:
여기서 다운스트림 재현은 유용했지만, CVE의 올바른 위치는 상위 패키지였습니다.
핵심 교훈은 간단합니다:
패키지 설치는 로컬 개발자 도구에서도 보안 경계입니다
많은 사람들이 CLI 도구에서 발생한다는 이유로 이런 문제를 본능적으로 격하합니다.
그것은 실수입니다.
실제 질문은 도구가 로컬인지 여부가 아닙니다.
실제 질문은 다음과 같습니다:
신뢰할 수 없는 입력이 명시적인 사용자 결정 없이 도구가 코드를 가져와 신뢰하게 만들 수 있는가?
이 경우, 그렇습니다.
그것이 진짜 핵심 교훈입니다.
이 문제는 또한 좋은 취약점 연구에 관한 중요한 점을 강화합니다:
이것이 바로 이 CVE의 형태였습니다.
이 취약점은 화려한 페이로드에 관한 것이 아니었습니다.
올바른 신뢰 경계 질문을 던지는 것이었습니다.
다운스트림 도구가 프로젝트 로컬 데이터가 패키지 선택에 영향을 미치도록 허용했습니다. Yeoman은 누락된 패키지를 확인 없이 설치했습니다. 나머지 코드 로딩 체인이 나머지를 처리했습니다.
그래서 이것이 CVE-2026-42089가 된 이유입니다.
yeoman-environment 6.0.1에서 수정되었습니다.