Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,8 @@
**Vulnerability:** Defense in Depth (CSP Missing)
**Learning:** Even when inputs are properly escaped, statically generated HTML that displays file/directory structures should implement a Content Security Policy (CSP) to provide an extra layer of defense against potential XSS bypasses.
**Prevention:** Include a strict CSP meta tag (e.g., `default-src 'none'; style-src 'unsafe-inline';`) in auto-generated HTML headers when external scripts or resources are not required.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HIGH OpenCode could not establish approval sufficiency

  • Problem: the model pool exhausted without a valid current-head review control block, so this changed line cannot be approved from deterministic check state alone.
  • Impact: PR-intent mismatches, missing files, robustness bugs, UX/DX regressions, and CodeGraph-backed flow changes could be missed.
  • Fix: rerun OpenCode after model availability recovers, or add the missing source/test/docs/generated verification evidence needed for a source-backed approval.
  • Verification: rerun the OpenCode Review workflow and confirm it emits APPROVE or source-backed REQUEST_CHANGES for this head SHA.

## 2024-06-30 - canonicalFile을 통한 경로 탐색 및 심볼릭 링크 우회 취약점
**Vulnerability:** `topDir` 검사에서 심볼릭 링크 여부를 확인(`Files.isDirectory(..., LinkOption.NOFOLLOW_LINKS)`)하기 전에 `File(topDir).canonicalFile`을 사용했습니다.
**Learning:** `canonicalFile`은 심볼릭 링크를 실제 경로로 미리 확인합니다. 따라서 사용자가 심볼릭 링크를 입력하면 `canonicalFile`은 실제 타겟으로 경로를 조용히 평가하여 의도한 심볼릭 링크 제한 검사를 우회하게 됩니다. 이는 Java/Kotlin에서 흔히 발생하는 경로 탐색 및 심볼릭 링크 제한 우회 결함입니다.
**Prevention:** `canonicalFile` 대신 `File(topDir).toPath().toAbsolutePath().normalize().toFile()`을 사용합니다. 이는 파일 시스템과 상호 작용하여 심볼릭 링크를 해석하지 않고 경로 문자열을 정규화하므로 후속 심볼릭 링크 검사가 대상이 아닌 실제 심볼릭 링크 경로에서 올바르게 작동할 수 있습니다.
39 changes: 39 additions & 0 deletions patch_test.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<<<<<<< SEARCH
@Test
fun testGoWithMaxLevel() {
=======
@Test
fun testGoWithMaxLevel() {
val subdir = File(tempDir, "subdir")
subdir.mkdir()
val subsubdir = File(subdir, "subsubdir")
subsubdir.mkdir()

go(tempDir.absolutePath, 0)

assertTrue(File(tempDir, "index.html").exists())
assertFalse(File(subdir, "index.html").exists())
assertFalse(File(subsubdir, "index.html").exists())
}

@Test
fun testGoMaxLevelLimitsCrawl() {
val subdir = File(tempDir, "subdir")
subdir.mkdir()

go(tempDir.absolutePath, -2) // Should not process any dirs theoretically, testing the currentLevel <= maxLevel boundary
}

@Test
fun testProcessIgnoreFileWithListNull() {
val unreadableDir = File(tempDir, "unreadable2")
unreadableDir.mkdir()
File(unreadableDir, ".html4ignore").writeText(".*\\.txt")
unreadableDir.setReadable(false, false)
try {
process_ignore_file(unreadableDir)
} finally {
unreadableDir.setReadable(true, false)
}
}
>>>>>>> REPLACE
2 changes: 1 addition & 1 deletion src/main/kotlin/html4tree/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fun main(args: Array<String>) = Html4tree().main(args)

fun go(topDir: String, maxLevel: Int) {
require(topDir.isNotBlank())
val top_dir = File(topDir).canonicalFile
val top_dir = File(topDir).toPath().toAbsolutePath().normalize().toFile()
require(Files.isDirectory(top_dir.toPath(), LinkOption.NOFOLLOW_LINKS)) { "Top directory must be an existing non-symlink directory" }

val ll = LinkedList()
Expand Down
22 changes: 22 additions & 0 deletions src/test/kotlin/html4tree/MainTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,28 @@ class MainTest {
assertFalse(File(subsubdir, "index.html").exists())
}

@Test
fun testGoMaxLevelLimitsCrawl() {
val subdir = File(tempDir, "subdir")
subdir.mkdir()

go(tempDir.absolutePath, -2)
assertFalse(File(tempDir, "index.html").exists())
}

@Test
fun testProcessIgnoreFileWithListNull() {
val unreadableDir = File(tempDir, "unreadable2")
unreadableDir.mkdir()
File(unreadableDir, ".html4ignore").writeText(".*\\.txt")
Assume.assumeTrue(unreadableDir.setReadable(false, false))
try {
process_ignore_file(unreadableDir)
} finally {
unreadableDir.setReadable(true, false)
}
}

@Test
fun testGoWithUnreadableDir() {
val unreadableDir = File(tempDir, "unreadable")
Expand Down