Kestrel
대시보드로 돌아가기
CVE-2026-50029MEDIUM· 5.3GHSA대응게시일: 2026. 06. 26.수정일: 2026. 06. 26.

js-toml has silent type confusion via falsy-primitive duplicate-key bypass

위협 신호 · CVSS · EPSS · KEV

정기 패치· 높은 악용 신호 없음
CVSS
5.3medium

이론적 심각도 점수

EPSS

예측 데이터 없음

KEV
미등재

실측 악용 기록 없음

권장 대응 기한14일 이내CISA SSVC 기준

2주 이내 패치 — 우선 조치 대상

자동화 가능외부 노출· KEV 미등재 · 자동화 가능 · 부분 영향 · 외부 노출

CVSS 벡터 · 메트릭

악용 경로
공격 벡터네트워크
공격 복잡도낮음
필요 권한불필요
사용자 상호작용불필요
범위불변
영향
기밀성 영향없음
무결성 영향낮음
가용성 영향없음
버전별 점수
CVSS 3.15.3MODERATE
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N

상세 설명

Summary

js-toml's interpreter checks whether a key already exists in a parser-built container with if (object[key]) instead of if (key in object). When the prior value is a falsy primitive — false, 0, 0n, 0.0, -0, or "" — the duplicate-key branch is skipped and the value is silently overwritten by a later sub-table, dotted-key sub-table, or array-of-tables sharing the same name. Per the TOML 1.0.0 spec ("Defining a key multiple times is invalid"; "You cannot define any key or table more than once"), this should be a parse error.

The result is structural type confusion of attacker-named keys in the value returned by load(). A boolean-typed false (or numeric 0) becomes a truthy object. Host applications that gate behavior on if (config.flag), if (!user.banned), if (config.allowDelete), or if (config.publicMode) will silently take the truthy branch.

This is distinct from GHSA-65fc-cr5f-v7r2 (the 1.0.2 prototype-pollution fix). Object.prototype is not polluted. The Object.create(null) mitigation from 1.0.2 is intact; the bug here is in the duplicate-key state machine, not in container construction.

Details

Two truthy checks are wrong:

src/load/interpreter.ts:214Interpreter.tryCreatingObject

text
1if (object[key]) { // falsy primitives slip through
2 // duplicate-key logic
3} else {
4 object[key] = createSafeObject(); // silently overwrites the prior falsy value
5 ...
6}

src/load/interpreter.ts:278Interpreter.getOrCreateArray

text
1if (object[first] && !Array.isArray(object[first])) { // same flaw
2 throw new DuplicateKeyError();
3}
4object[first] = object[first] || []; // overwrites the prior falsy value

Both should use the in operator. Containers are created via Object.create(null), so in is unambiguous (no inherited keys to worry about).

The bug is reachable through every parent-walking interpreter path:

  • assignValue — dotted keys in key = value
  • createTable[stdTable] headers
  • getOrCreateArray[[arrayOfTables]] headers

PoC

text
1isAdmin = false
2[isAdmin]
3forced = "yes"
text
1import { load } from 'js-toml';
2
3const config = load(`
4isAdmin = false
5[isAdmin]
6forced = "yes"
7`);
8
9console.log(JSON.stringify(config));
10// {"isAdmin":{"forced":"yes"}}
11
12console.log(config.isAdmin ? 'BYPASS' : 'safe');
13// BYPASS
14
15if (config.isAdmin) {
16 // attacker reaches admin-only code
17}

Impact

Spec-violating input acceptance leading to structural type confusion. (CWE-697)

Suggested fix

in src/load/interpreter.ts

text
1export class Interpreter extends BaseCstVisitor {
2 ignoreImplicitDeclared,
3 ignoreExplicitDeclared
4 ) {
5- if (object[key]) {
6+ if (key in object) {
7 if (
8 !isPlainObject(object[key]) ||
9 (!ignoreExplicitDeclared &&
text
1export class Interpreter extends BaseCstVisitor {
2 return this.getOrCreateArray(keys, object[first], idx + 1);
3 }
4
5- if (object[first] && !Array.isArray(object[first])) {
6+ if (first in object && !Array.isArray(object[first])) {
7 throw new DuplicateKeyError();
8 }
9
10 object[first] = object[first] || [];

AI 심층 분석

공격 시나리오 · 재현 가능한 PoC 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.