diff --git a/.jules/sentinel.md b/.jules/sentinel.md index a885865d..16b6b85a 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`). +## 2026-09-04 - Prevent TOCTOU in file reading +**Vulnerability:** `File.useLines` implicitly resolves symbolic links internally, which can lead to Time-of-Check-to-Time-of-Use (TOCTOU) if the file is replaced by a symlink after validation. +**Learning:** Even if symlinks are verified as absent prior to reading, Kotlin's standard library file readers might still traverse them during the reading phase, opening a TOCTOU window. +**Prevention:** Replace `File.useLines` with `Files.newInputStream(..., StandardOpenOption.READ, LinkOption.NOFOLLOW_LINKS).bufferedReader().useLines` to strictly disallow symlink traversal when opening the file. diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt index 0972fa2c..e914b54a 100644 --- a/src/main/kotlin/html4tree/main.kt +++ b/src/main/kotlin/html4tree/main.kt @@ -309,7 +309,7 @@ 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 -> + java.nio.file.Files.newInputStream(ignore_file.toPath(), java.nio.file.StandardOpenOption.READ, java.nio.file.LinkOption.NOFOLLOW_LINKS).bufferedReader().useLines { lines -> for ((lineIndex, it) in lines.withIndex()) { // 줄 수 제한이 패턴 수도 함께 상한(줄당 최대 1개 패턴)하므로 별도 패턴 카운터는 불필요 if (lineIndex >= 1000) break