From 9e4120cadf4b63610616fe170f032f552b657415 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Tue, 22 Sep 2026 21:14:00 +0000
Subject: [PATCH 1/9] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[MEDIUM?=
=?UTF-8?q?]=20BiDi=20=EC=8A=A4=ED=91=B8=ED=95=91=20=EC=B7=A8=EC=95=BD?=
=?UTF-8?q?=EC=A0=90=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
BiDi 제어 문자를 이스케이프하고 FSI/PDI 래퍼를 적용하여 파일 확장자 스푸핑 취약점(Trojan Source)을 수정했습니다.
- escapeHtml()에서 양방향 텍스트 문자 리터럴 이스케이프 처리
- 사용자 제어 텍스트에 First Strong Isolate, Pop Directional Isolate 래핑 적용
- a:hover CSS 셀렉터 오류(span:last-child -> .entry-name) 수정 및 접근성 테스트 업데이트
---
.jules/sentinel.md | 4 ++++
CHANGELOG.md | 1 +
src/main/kotlin/html4tree/main.kt | 19 ++++++++++++++-----
.../GeneratedIndexReadabilityTest.kt | 2 +-
4 files changed, 20 insertions(+), 6 deletions(-)
diff --git a/.jules/sentinel.md b/.jules/sentinel.md
index a885865d..d00e08e1 100644
--- a/.jules/sentinel.md
+++ b/.jules/sentinel.md
@@ -99,3 +99,7 @@
**Root cause:** The protected implementation added canonical names to the exclusion set but did not compare each observed directory entry through a locale-stable normalized key.
**Prevention:** Build one `Locale.ROOT` lowercase set from the canonical sensitive names, compare every observed name against it, and add the original spelling to the exclusion set so downstream exact membership remains correct.
**Evidence:** `testProcessIgnoreFileTreatsSensitiveNamesCaseInsensitively` failed on test-only commit `472b916cd40f70693c4e1eb48956042a25353feb` (CI run `31469596932`) and passed with the source fix at `bb113d858ccfc42ddaecf6729749b238e5ade2d0` (CI run `31469921661`).
+## 2024-05-24 - BiDi 스푸핑 취약점 (CVE-2021-42574)
+**Vulnerability:** 파일명에 유니코드 양방향(BiDi) 제어 문자(예: RLO, LRI)가 포함될 경우 UI에서 확장자가 스푸핑되어 악성 파일(예: `.exe`가 `.txt`로 보임)이 다운로드될 수 있는 Trojan Source 취약점이 발견되었습니다.
+**Learning:** `escapeHtml()` 함수에서 HTML 예약어 이외의 렌더링에 영향을 미치는 숨겨진 제어 문자는 기본적으로 필터링되지 않았으며, `dir="auto"`와 같은 단순한 설정은 `
` 등의 속성에서 완벽히 격리되지 않습니다.
+**Prevention:** 렌더링 시 악용될 수 있는 `\u202E`, `\u2066` 등의 BiDi 제어 문자를 `escapeHtml()`에서 명시적으로 리터럴(예: `\\u202E`)로 이스케이프하여 노출시킵니다. 또한, 디렉토리명 및 파일명 등 사용자가 통제 가능한 임의의 문자열을 HTML에 렌더링할 때는 W3C 권고안에 따라 First Strong Isolate (``) 및 Pop Directional Isolate (``)를 래핑하여 텍스트의 방향성을 구조적으로 격리해야 합니다.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c442b3b1..563acc08 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,4 @@
+- BiDi(양방향) 텍스트 제어 문자를 이스케이프하고 FSI/PDI 래퍼를 적용하여 파일명/확장자 스푸핑(Trojan Source) 취약점 수정
# Changelog
All notable changes to this project are documented in this file.
diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt
index 0972fa2c..48a39271 100644
--- a/src/main/kotlin/html4tree/main.kt
+++ b/src/main/kotlin/html4tree/main.kt
@@ -56,7 +56,7 @@ a:hover, a:focus-visible {
outline: 2px solid #0969da;
outline-offset: -2px;
}
-a:hover span:last-child, a:focus-visible span:last-child {
+a:hover .entry-name, a:focus-visible .entry-name {
text-decoration: underline;
}
@media (prefers-reduced-motion: reduce) {
@@ -243,6 +243,15 @@ fun String.escapeHtml(): String {
'"' -> """
'\'' -> "'"
'`' -> "`"
+ '\u202A' -> "\\u202A"
+ '\u202B' -> "\\u202B"
+ '\u202C' -> "\\u202C"
+ '\u202D' -> "\\u202D"
+ '\u202E' -> "\\u202E"
+ '\u2066' -> "\\u2066"
+ '\u2067' -> "\\u2067"
+ '\u2068' -> "\\u2068"
+ '\u2069' -> "\\u2069"
else -> null
}
if (replacement != null) {
@@ -421,12 +430,12 @@ fun process_dir(curr_dir: File, excludeSet: Set? = null, dirFiles: Array
- ${directoryName.escapeHtml()} - 디렉토리 목록
+ ${directoryName.escapeHtml()} - 디렉토리 목록
-