From e9c847f0c180b559084ab44cc3a61373a903fce9 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Mon, 31 Aug 2026 21:14:00 +0000 Subject: [PATCH 1/7] =?UTF-8?q?.html4ignore=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=BD=EA=B8=B0=20TOCTOU=20DoS=20=EC=B7=A8=EC=95=BD=EC=A0=90?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/sentinel.md | 5 +++++ src/main/kotlin/html4tree/main.kt | 22 +++++++++++++--------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index a885865d..b2453c06 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -99,3 +99,8 @@ **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`). + +## 2026-08-31 - [MEDIUM] TOCTOU (Time-of-Check to Time-of-Use) DoS in File.useLines +**Vulnerability:** `canRead()`로 파일 읽기 권한을 확인한 직후라도, `useLines()`로 파일을 열 때 권한이 변경되거나 파일이 삭제되면 처리되지 않은 `IOException`이 발생하여 전체 크롤링 프로세스가 중단(DoS)될 수 있습니다. +**Learning:** 파일 상태를 검증(`canRead()`)하는 시점과 실제로 I/O 작업을 수행(`useLines()`)하는 시점 사이에는 간격이 존재하므로, 검증에만 의존하면 TOCTOU 취약점에 노출됩니다. +**Prevention:** 파일 I/O 작업 시 단일 시점의 상태 검증에만 의존하지 말고, 실제 I/O 호출부를 `try-catch (IOException)`으로 감싸서 예외 발생 시 애플리케이션 크래시 대신 우아하게 실패(Fail Securely)하도록 구현해야 합니다. diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt index 0972fa2c..2888cfcf 100644 --- a/src/main/kotlin/html4tree/main.kt +++ b/src/main/kotlin/html4tree/main.kt @@ -309,18 +309,22 @@ fun process_ignore_file(curr_dir: File, dirFilesNames: Array? = null): S if(ignore_file.isFile && !Files.isSymbolicLink(ignore_file.toPath()) && ignore_file.canRead() && ignore_file.length() <= 1048576){ val ignored_matchers = mutableListOf() - ignore_file.useLines { lines -> - for ((lineIndex, it) in lines.withIndex()) { - // 줄 수 제한이 패턴 수도 함께 상한(줄당 최대 1개 패턴)하므로 별도 패턴 카운터는 불필요 - if (lineIndex >= 1000) break - val pattern = it.trim() - if (pattern.isNotEmpty() && pattern.length <= 100) { - try { - ignored_matchers.add(java.nio.file.FileSystems.getDefault().getPathMatcher("glob:$pattern")) - } catch (_: IllegalArgumentException) { + try { + ignore_file.useLines { lines -> + for ((lineIndex, it) in lines.withIndex()) { + // 줄 수 제한이 패턴 수도 함께 상한(줄당 최대 1개 패턴)하므로 별도 패턴 카운터는 불필요 + if (lineIndex >= 1000) break + val pattern = it.trim() + if (pattern.isNotEmpty() && pattern.length <= 100) { + try { + ignored_matchers.add(java.nio.file.FileSystems.getDefault().getPathMatcher("glob:$pattern")) + } catch (_: IllegalArgumentException) { + } } } } + } catch (_: java.io.IOException) { + // 보안: TOCTOU 파일 접근 거부/삭제 예외 시 크래시(DoS) 방지 및 안전하게 무시 (Fail Securely) } // ⚡ Bolt Performance Optimization: 디렉토리 목록을 Set에 추가하기 위해 필터링만 할 때는 정렬이 불필요하므로 .sorted()를 제거하여 O(N log N) 오버헤드를 방지합니다. From 1b99f7c518cc0ffd88baf746ec27b205c5647b3e Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Mon, 31 Aug 2026 22:55:39 +0000 Subject: [PATCH 2/7] =?UTF-8?q?.html4ignore=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=BD=EA=B8=B0=20TOCTOU=20DoS=20=EC=B7=A8=EC=95=BD=EC=A0=90?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/sentinel.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index b2453c06..a885865d 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -99,8 +99,3 @@ **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`). - -## 2026-08-31 - [MEDIUM] TOCTOU (Time-of-Check to Time-of-Use) DoS in File.useLines -**Vulnerability:** `canRead()`로 파일 읽기 권한을 확인한 직후라도, `useLines()`로 파일을 열 때 권한이 변경되거나 파일이 삭제되면 처리되지 않은 `IOException`이 발생하여 전체 크롤링 프로세스가 중단(DoS)될 수 있습니다. -**Learning:** 파일 상태를 검증(`canRead()`)하는 시점과 실제로 I/O 작업을 수행(`useLines()`)하는 시점 사이에는 간격이 존재하므로, 검증에만 의존하면 TOCTOU 취약점에 노출됩니다. -**Prevention:** 파일 I/O 작업 시 단일 시점의 상태 검증에만 의존하지 말고, 실제 I/O 호출부를 `try-catch (IOException)`으로 감싸서 예외 발생 시 애플리케이션 크래시 대신 우아하게 실패(Fail Securely)하도록 구현해야 합니다. From f5684d924e6cfd937ae3da5c5dc9e87d635077c1 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 1 Sep 2026 08:48:23 +0000 Subject: [PATCH 3/7] =?UTF-8?q?.html4ignore=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=BD=EA=B8=B0=20TOCTOU=20DoS=20=EC=B7=A8=EC=95=BD=EC=A0=90?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/sentinel.md | 5 ++++ src/main/kotlin/html4tree/main.kt | 2 +- src/test/kotlin/html4tree/ToctouTest.kt | 38 +++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 src/test/kotlin/html4tree/ToctouTest.kt diff --git a/.jules/sentinel.md b/.jules/sentinel.md index a885865d..b8f034d4 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -99,3 +99,8 @@ **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`). + +## 2026-09-01 - [MEDIUM] TOCTOU (Time-of-Check to Time-of-Use) DoS in File.useLines +**Vulnerability:** `canRead()`로 파일 읽기 권한을 확인한 직후라도, `useLines()`로 파일을 열 때 권한이 변경되거나 파일이 삭제되면 처리되지 않은 예외가 발생하여 전체 크롤링 프로세스가 중단(DoS)될 수 있습니다. +**Learning:** 파일 상태를 검증(`canRead()`)하는 시점과 실제로 I/O 작업을 수행(`useLines()`)하는 시점 사이에는 간격이 존재하므로, 검증에만 의존하면 TOCTOU 취약점에 노출됩니다. +**Prevention:** 파일 I/O 작업 시 단일 시점의 상태 검증에만 의존하지 말고, 실제 I/O 호출부를 `try-catch (Exception)`으로 감싸서 예외 발생 시 애플리케이션 크래시 대신 우아하게 실패(Fail Securely)하도록 구현해야 합니다. diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt index 2888cfcf..94cbd3af 100644 --- a/src/main/kotlin/html4tree/main.kt +++ b/src/main/kotlin/html4tree/main.kt @@ -323,7 +323,7 @@ fun process_ignore_file(curr_dir: File, dirFilesNames: Array? = null): S } } } - } catch (_: java.io.IOException) { + } catch (_: Exception) { // 보안: TOCTOU 파일 접근 거부/삭제 예외 시 크래시(DoS) 방지 및 안전하게 무시 (Fail Securely) } diff --git a/src/test/kotlin/html4tree/ToctouTest.kt b/src/test/kotlin/html4tree/ToctouTest.kt new file mode 100644 index 00000000..39ead7c2 --- /dev/null +++ b/src/test/kotlin/html4tree/ToctouTest.kt @@ -0,0 +1,38 @@ +package html4tree + +import org.junit.Test +import java.io.File +import kotlin.test.assertTrue +import kotlin.concurrent.thread +import java.nio.file.Files + +class ToctouTest { + @Test + fun testProcessIgnoreFileToctouExceptionRace() { + val tempDir = Files.createTempDirectory("toctoutest").toFile() + val ignoreFile = File(tempDir, ".html4ignore") + + var excluded: Set? = null + for (i in 0..500) { + ignoreFile.writeText("test.txt") + val t = thread { ignoreFile.delete() } + excluded = process_ignore_file(tempDir, null) + t.join() + } + assertTrue(excluded?.contains("index.html") ?: false) + } + + @Test + fun testProcessIgnoreFileToctouException() { + val tempDir = Files.createTempDirectory("toctoutest2").toFile() + val ignoreFile = File(tempDir, ".html4ignore") + ignoreFile.writeText("test.txt") + + // Force an IOException during useLines + val method = java.io.File::class.java.getDeclaredMethod("setReadable", Boolean::class.java) + method.isAccessible = true + method.invoke(ignoreFile, false) + val excluded = process_ignore_file(tempDir, null) + assertTrue(excluded.contains("index.html")) + } +} From 0d9bac617c145c6f1828ce6c735d373fc9900c93 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 1 Sep 2026 17:02:53 +0000 Subject: [PATCH 4/7] =?UTF-8?q?.html4ignore=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=BD=EA=B8=B0=20TOCTOU=20DoS=20=EC=B7=A8=EC=95=BD=EC=A0=90?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/kotlin/html4tree/ToctouTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/html4tree/ToctouTest.kt b/src/test/kotlin/html4tree/ToctouTest.kt index 39ead7c2..2bee4e24 100644 --- a/src/test/kotlin/html4tree/ToctouTest.kt +++ b/src/test/kotlin/html4tree/ToctouTest.kt @@ -13,7 +13,7 @@ class ToctouTest { val ignoreFile = File(tempDir, ".html4ignore") var excluded: Set? = null - for (i in 0..500) { + for (i in 0..2000) { ignoreFile.writeText("test.txt") val t = thread { ignoreFile.delete() } excluded = process_ignore_file(tempDir, null) From be7655a168ca93593eb8b03f1e3ac75d48580a16 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 2 Sep 2026 04:22:15 +0000 Subject: [PATCH 5/7] =?UTF-8?q?.html4ignore=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=BD=EA=B8=B0=20TOCTOU=20DoS=20=EC=B7=A8=EC=95=BD=EC=A0=90?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/html4tree/main.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt index 94cbd3af..2888cfcf 100644 --- a/src/main/kotlin/html4tree/main.kt +++ b/src/main/kotlin/html4tree/main.kt @@ -323,7 +323,7 @@ fun process_ignore_file(curr_dir: File, dirFilesNames: Array? = null): S } } } - } catch (_: Exception) { + } catch (_: java.io.IOException) { // 보안: TOCTOU 파일 접근 거부/삭제 예외 시 크래시(DoS) 방지 및 안전하게 무시 (Fail Securely) } From 52e6b0e43c17e76433c9db79c1dd53ee57810a9c Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:14:42 +0000 Subject: [PATCH 6/7] =?UTF-8?q?.html4ignore=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=BD=EA=B8=B0=20TOCTOU=20DoS=20=EC=B7=A8=EC=95=BD=EC=A0=90?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/html4tree/main.kt | 2 +- src/test/kotlin/html4tree/ToctouTest.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt index 2888cfcf..94cbd3af 100644 --- a/src/main/kotlin/html4tree/main.kt +++ b/src/main/kotlin/html4tree/main.kt @@ -323,7 +323,7 @@ fun process_ignore_file(curr_dir: File, dirFilesNames: Array? = null): S } } } - } catch (_: java.io.IOException) { + } catch (_: Exception) { // 보안: TOCTOU 파일 접근 거부/삭제 예외 시 크래시(DoS) 방지 및 안전하게 무시 (Fail Securely) } diff --git a/src/test/kotlin/html4tree/ToctouTest.kt b/src/test/kotlin/html4tree/ToctouTest.kt index 2bee4e24..fe012b78 100644 --- a/src/test/kotlin/html4tree/ToctouTest.kt +++ b/src/test/kotlin/html4tree/ToctouTest.kt @@ -13,7 +13,7 @@ class ToctouTest { val ignoreFile = File(tempDir, ".html4ignore") var excluded: Set? = null - for (i in 0..2000) { + for (i in 0..50) { ignoreFile.writeText("test.txt") val t = thread { ignoreFile.delete() } excluded = process_ignore_file(tempDir, null) From 3a1ebe7e458ba7e996f5c97e983c0ece17fa1dc5 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 3 Sep 2026 02:29:55 +0000 Subject: [PATCH 7/7] =?UTF-8?q?.html4ignore=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=BD=EA=B8=B0=20TOCTOU=20DoS=20=EC=B7=A8=EC=95=BD=EC=A0=90?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/kotlin/html4tree/ToctouTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/html4tree/ToctouTest.kt b/src/test/kotlin/html4tree/ToctouTest.kt index fe012b78..2bee4e24 100644 --- a/src/test/kotlin/html4tree/ToctouTest.kt +++ b/src/test/kotlin/html4tree/ToctouTest.kt @@ -13,7 +13,7 @@ class ToctouTest { val ignoreFile = File(tempDir, ".html4ignore") var excluded: Set? = null - for (i in 0..50) { + for (i in 0..2000) { ignoreFile.writeText("test.txt") val t = thread { ignoreFile.delete() } excluded = process_ignore_file(tempDir, null)