-
Notifications
You must be signed in to change notification settings - Fork 0
๐ก๏ธ Sentinel: [CRITICAL] ๋ณด์ ์ ์ฑ ํ์ผ(.html4ignore) Fail-closed ์ฒ๋ฆฌ ์ ์ฉ #780
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
ae0a7ee
5dd7daa
3742121
b40a17f
72f5eec
c9a8b24
e97a7c7
57bf2b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -200,20 +200,26 @@ internal fun crawl_directories( | |
| val dirFilesNames = dirFiles?.let { files -> | ||
| Array(files.size) { index -> files[index].name } | ||
| } | ||
| val exclude = processIgnoreFile(lle.file, dirFilesNames) | ||
|
|
||
| if(maxLevel == -1 || currentLevel <= maxLevel) | ||
| processDirectory(lle.file, exclude, dirFiles) | ||
|
|
||
| if(maxLevel == -1 || currentLevel < maxLevel) { | ||
| dirFiles?.forEach { | ||
| // โก Bolt Performance Optimization: Short-circuit OS stat calls | ||
| // by checking cheap in-memory string exclusion rules first | ||
| 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) | ||
| ll.push(childEntry) | ||
| val exclude = try { | ||
| processIgnoreFile(lle.file, dirFilesNames) | ||
| } catch (e: IgnoreFileReadException) { | ||
| null | ||
| } | ||
|
|
||
| if(exclude != null) { | ||
| if(maxLevel == -1 || currentLevel <= maxLevel) | ||
| processDirectory(lle.file, exclude, dirFiles) | ||
|
|
||
| if(maxLevel == -1 || currentLevel < maxLevel) { | ||
| dirFiles?.forEach { | ||
| // โก Bolt Performance Optimization: Short-circuit OS stat calls | ||
| // by checking cheap in-memory string exclusion rules first | ||
| 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) | ||
| ll.push(childEntry) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -293,6 +299,12 @@ fun String.urlEncodePath(): String { | |
| return encoded?.toString() ?: this | ||
| } | ||
|
|
||
| /** | ||
| * Exception thrown when a policy file like .html4ignore exists but cannot be safely read. | ||
| * Enforces a fail-closed security contract to prevent TOCTOU bypasses and information exposure. | ||
| */ | ||
| class IgnoreFileReadException(message: String, cause: Throwable? = null) : RuntimeException(message, cause) | ||
|
|
||
| fun process_ignore_file(curr_dir: File, dirFilesNames: Array<String>? = null): Set<String> { | ||
|
|
||
| val ignore_filename = ".html4ignore" | ||
|
|
@@ -303,10 +315,13 @@ fun process_ignore_file(curr_dir: File, dirFilesNames: Array<String>? = null): S | |
|
|
||
| val files_to_exclude = mutableSetOf<String>() | ||
|
|
||
| // ๋ณด์ ํฅ์: .html4ignore ํ์ผ์ด ์ผ๋ฐ ํ์ผ์ธ์ง ํ์ธํ๊ณ , ์ฌ๋ณผ๋ฆญ ๋งํฌ์ธ ๊ฒฝ์ฐ ๋ฌด์ํ์ฌ DoS ๋ฐ ๊ฒฝ๋ก ์กฐ์์ ๋ฐฉ์งํฉ๋๋ค. | ||
| // ๋ณด์ ํฅ์: ํ์ผ ํฌ๊ธฐ(1MB ์ ํ) ๋ฐ ์ค ์(1000์ค), ์ ๊ท์ ๊ธธ์ด(100์)๋ฅผ ์ ํํ์ฌ ReDoS ๋ฐ ๋ฉ๋ชจ๋ฆฌ ๊ณ ๊ฐ(OOM) ๋ฐฉ์ง | ||
| // ๋ณด์ ํฅ์: ๊ถํ์ด ์๋ ํ์ผ ์ ๊ทผ ์ ๋ฐ์ํ๋ ์์ธ(DoS)๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด canRead() ์ถ๊ฐ ํ์ธ | ||
| if(ignore_file.isFile && !Files.isSymbolicLink(ignore_file.toPath()) && ignore_file.canRead() && ignore_file.length() <= 1048576){ | ||
| // ๋ณด์ ํฅ์: TOCTOU(Time-of-check to time-of-use) ์ทจ์ฝ์ ๋ฐฉ์ง ๋ฐ Fail-closed ์ ์ฑ ์ ์ฉ | ||
| val hasIgnoreFile = (dirFilesNames != null && dirFilesNames.contains(ignore_filename)) || ignore_file.exists() || Files.isSymbolicLink(ignore_file.toPath()) | ||
|
|
||
| if(hasIgnoreFile){ | ||
| if(!ignore_file.isFile || Files.isSymbolicLink(ignore_file.toPath()) || !ignore_file.canRead() || ignore_file.length() > 1048576){ | ||
| throw IgnoreFileReadException("Policy file cannot be safely read: fail closed.") | ||
|
Comment on lines
+322
to
+323
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ Security & Privacy | ๐ก๏ธ Detected with Advanced Tier | ๐ Major | ๐๏ธ Heavy lift Exploitability: Difficult ๊ฒ์ฌํ ํ์ฌ ์ฝ๋๋ ๊ฒฝ๋ก๋ก ํ์ผ ์ ํ, ์ฌ๋ณผ๋ฆญ ๋งํฌ, ํฌ๊ธฐ๋ฅผ ๊ฒ์ฌํ ๋ค ๊ฐ์ ๊ฒฝ๋ก๋ฅผ ๋ค์ ์ฝ๋๋ค. ๋๋ ํฐ๋ฆฌ ํญ๋ชฉ์ ๋ณ๊ฒฝํ ์ ์๋ ์์ฑ์๊ฐ ์ด ์ฌ์ด์ ๊ฒฝ๋ก๋ฅผ ๋ฐ๊พธ๋ฉด ๐ค Prompt for AI AgentsSource: Coding guidelines |
||
| } | ||
| val ignored_matchers = mutableListOf<java.nio.file.PathMatcher>() | ||
|
|
||
| ignore_file.useLines { lines -> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐ฉบ Stability & Availability | ๐ Major | โก Quick win
์ฝ๊ธฐ ์ค ๋ฐ์ํ I/O ์ค๋ฅ๋ฅผ ๋๋ ํฐ๋ฆฌ ์ค๋จ ๊ฒฝ๋ก๋ก ๋ณํํ์ธ์.
.html4ignore๊ฐ ๊ฒ์ฌ๋ฅผ ํต๊ณผํ ๋ค ์ญ์ ๋๊ฑฐ๋ ์ฝ์ ์ ์๊ฒ ๋๋ฉด,useLines์ ์ด๊ธฐ ๋๋ ์ฝ๊ธฐ ์ค๋ฅ๋IgnoreFileReadException์ด ์๋๋๋ค. ์ดcatch๋ ์ค๋ฅ๋ฅผ ์ฒ๋ฆฌํ์ง ๋ชปํ๋ฏ๋กgo์ ์ ์ฒด ํฌ๋กค๋ง์ด ์ค๋จ๋ฉ๋๋ค.process_ignore_file์์ ์ด๊ธฐ์ ์ฝ๊ธฐ ์ค๋ฅ๋ฅผ ์์ธ ์์ธ๋ฅผ ๋ณด์กดํIgnoreFileReadException์ผ๋ก ๋ณํํ์ธ์. ํ์ผ ์ฝ๊ธฐ๋ ์ด ๊ฒ์ฌ์ ๋ณ๋๋ก ์ํ๋ฉ๋๋ค. (kotlinlang.org)๐งฐ Tools
๐ช detekt (1.23.8)
[warning] 205-205: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
๐ค Prompt for AI Agents