diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 323abb9..110dbdf 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -78,3 +78,7 @@ **Vulnerability:** The application lists all files in a directory, including hidden files (those starting with `.`), which could inadvertently expose sensitive information like `.env`, `.git`, or `.ssh` directories. **Learning:** Default directory listing implementations without hidden file filtering can lead to information disclosure vulnerabilities when serving directories containing configuration or sensitive files. **Prevention:** Automatically exclude hidden files (files starting with `.`) from the generated directory listing by default. +## 2023-10-27 - [Information Exposure via Case-Sensitive Matches] +**Vulnerability:** The `process_ignore_file` routine used case-sensitive matching for sensitive files (like `id_rsa`, `secrets.yml`), allowing case variations (e.g., `ID_RSA`) to bypass filtering and be included in the generated HTML. +**Learning:** Exact string matching is fragile for file systems that are case-insensitive or for users who create mixed-case variations of sensitive files. +**Prevention:** Always normalize file names (e.g., using `toLowerCase()`) when matching against exclusion lists, taking care to preserve expected outputs like exact sizes in tests or edge cases (like `.DS_Store`). diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt index c606e58..2da5302 100644 --- a/src/main/kotlin/html4tree/main.kt +++ b/src/main/kotlin/html4tree/main.kt @@ -222,8 +222,17 @@ fun process_ignore_file(curr_dir: File, dirFilesNames: Array? = null): S files_to_exclude.add("index.html") // 보안 향상: 민감한 시스템, 설정, 시크릿 파일을 디렉토리 목록에서 기본적으로 제외하여 정보 노출(Information Exposure) 방지 - val defaultSensitiveFiles = listOf(".git", ".env", ".ssh", ".htpasswd", ".htaccess", "id_rsa", "id_ed25519", "secrets.yml", ".html4ignore", ".DS_Store", ".aws", ".kube", ".npmrc", ".gnupg", "config.json", "credentials.json") + val defaultSensitiveFiles = listOf(".git", ".env", ".ssh", ".htpasswd", ".htaccess", "id_rsa", "id_ed25519", "secrets.yml", ".html4ignore", ".ds_store", ".aws", ".kube", ".npmrc", ".gnupg", "config.json", "credentials.json") files_to_exclude.addAll(defaultSensitiveFiles) + files_to_exclude.add(".DS_Store") // Ensure original case is also explicitly excluded + + // 🛡️ Sentinel: Make sensitive file checks case-insensitive + (dirFilesNames ?: curr_dir.list())?.forEach { + val lowerCaseName = it.toLowerCase() + if (lowerCaseName in defaultSensitiveFiles) { + files_to_exclude.add(it) + } + } // 보안 향상: .env, .git 등 민감한 정보가 포함될 수 있는 숨김 파일(.으로 시작하는 모든 항목)을 기본적으로 노출하지 않도록 제외 (정보 노출 방지) (dirFilesNames ?: curr_dir.list())?.forEach { diff --git a/src/test/kotlin/html4tree/MainTest.kt b/src/test/kotlin/html4tree/MainTest.kt index a4d2ba5..3ec6a16 100644 --- a/src/test/kotlin/html4tree/MainTest.kt +++ b/src/test/kotlin/html4tree/MainTest.kt @@ -267,7 +267,7 @@ class MainTest { fun testProcessIgnoreFileNoIgnore() { val excluded = process_ignore_file(tempDir, null) assertTrue(excluded.contains("index.html")) - assertEquals(17, excluded.size) // index.html + 16 default sensitive files + assertEquals(18, excluded.size) // updated // index.html + 16 default sensitive files } @Test @@ -277,7 +277,7 @@ class MainTest { val excluded = process_ignore_file(tempDir, arrayOf("test1.txt", "test3.txt")) assertTrue(excluded.contains("index.html")) - assertEquals(18, excluded.size) // index.html + 16 default sensitive + test1.txt + assertEquals(19, excluded.size) } @Test diff --git a/src/test/kotlin/html4tree/SecurityTest.kt b/src/test/kotlin/html4tree/SecurityTest.kt new file mode 100644 index 0000000..02bbb6a --- /dev/null +++ b/src/test/kotlin/html4tree/SecurityTest.kt @@ -0,0 +1,27 @@ +package html4tree + +import org.junit.Test +import org.junit.Assert.* +import java.io.File +import java.nio.file.Files + +class SecurityTest { + @Test + fun testSensitiveFilesCaseInsensitive() { + val tempDir = Files.createTempDirectory("sectest").toFile() + try { + val file1 = File(tempDir, "ID_RSA") + file1.createNewFile() + + val excluded = process_ignore_file(tempDir, null) + assertTrue("ID_RSA should be excluded", excluded.contains("ID_RSA")) + + process_dir(tempDir) + + val indexContent = File(tempDir, "index.html").readText() + assertFalse("ID_RSA should be excluded from index.html", indexContent.contains("ID_RSA")) + } finally { + tempDir.deleteRecursively() + } + } +}