diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt
index ff480cc..8942c04 100644
--- a/src/main/kotlin/html4tree/main.kt
+++ b/src/main/kotlin/html4tree/main.kt
@@ -182,7 +182,7 @@ internal fun crawl_directories(
dirFiles?.forEach {
// ⚡ Bolt Performance Optimization: Short-circuit OS stat calls
// by checking cheap in-memory string exclusion rules first
- if(!it.name.startsWith(".") && it.name !in exclude) {
+ if(!it.name.isHiddenFile() && it.name !in exclude) {
val childAttrs = readAttributes(it)
if(childAttrs != null && childAttrs.isDirectory && !childAttrs.isSymbolicLink) {
val childEntry = LinkedListEntry(it, currentLevel+1, readIdentity(it).key)
@@ -195,6 +195,13 @@ internal fun crawl_directories(
}
}
+fun String.isHiddenFile(): Boolean {
+ return when (firstOrNull()) {
+ '.', '\u3002', '\uFF0E', '\uFF61' -> true
+ else -> false
+ }
+}
+
// ⚡ Bolt Performance Optimization: Single-pass loop with lazy StringBuilder
// Chained `.replace()` calls allocate multiple intermediate strings.
// A single pass over the string lazily allocating a StringBuilder is much faster.
@@ -310,9 +317,9 @@ fun process_ignore_file(curr_dir: File, dirFilesNames: Array? = null): S
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)
- // 보안 향상: .env, .git 등 민감한 정보가 포함될 수 있는 숨김 파일(.으로 시작하는 모든 항목)을 기본적으로 노출하지 않도록 제외 (정보 노출 방지)
+ // 보안 향상: dot-like prefixes are treated as hidden to prevent visually-confusable sensitive entries from reaching generated indexes.
(dirFilesNames ?: curr_dir.list())?.forEach {
- if (it.startsWith(".")) {
+ if (it.isHiddenFile()) {
files_to_exclude.add(it)
}
}
@@ -366,7 +373,7 @@ fun process_dir(curr_dir: File, excludeSet: Set? = null, dirFiles: Array
val fileName = it.getName()
// ⚡ Bolt Performance Optimization: Short-circuit string match before expensive OS filesystem calls
// 🛡️ Sentinel: Ignore hidden files/directories to prevent sensitive data exposure
- if (!fileName.startsWith(".") && fileName !in exclude) {
+ if (!fileName.isHiddenFile() && fileName !in exclude) {
var isLinkedDirectory = false
var isSymbolicLink = false
try {
diff --git a/src/test/kotlin/html4tree/HiddenFileSecurityTest.kt b/src/test/kotlin/html4tree/HiddenFileSecurityTest.kt
new file mode 100644
index 0000000..3bee2a1
--- /dev/null
+++ b/src/test/kotlin/html4tree/HiddenFileSecurityTest.kt
@@ -0,0 +1,36 @@
+package html4tree
+
+import org.junit.Test
+import java.nio.file.Files
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+class HiddenFileSecurityTest {
+ @Test
+ fun hiddenFileClassifierRecognizesAsciiAndUnicodeDotPrefixes() {
+ val hiddenNames = listOf(".env", "\u3002env", "\uFF0Egit", "\uFF61ssh")
+
+ hiddenNames.forEach { name ->
+ assertTrue(name.isHiddenFile(), "Dot-like prefix must be treated as hidden: $name")
+ }
+ assertFalse("visible.txt".isHiddenFile())
+ assertFalse("".isHiddenFile())
+ }
+
+ @Test
+ fun unicodeDotHomoglyphsAreExcludedFromDirectoryIndexes() {
+ val directory = Files.createTempDirectory("html4tree-homoglyph-").toFile()
+ try {
+ val hiddenNames = listOf("\u3002env", "\uFF0Egit", "\uFF61ssh")
+ hiddenNames.forEach { name -> directory.resolve(name).writeText("secret") }
+
+ val excluded = process_ignore_file(directory)
+
+ hiddenNames.forEach { name ->
+ assertTrue(name in excluded, "Unicode dot homoglyph must be treated as hidden: $name")
+ }
+ } finally {
+ directory.deleteRecursively()
+ }
+ }
+}