Skip to content
Closed
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
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,7 @@
**Vulnerability:** 정적 HTML 생성 도구에서 매번 다른 Nonce를 동적으로 생성하여 CSP에 적용하는 것은, 캐싱 효율을 저하시킬 뿐만 아니라 정적 배포 환경(예: GitHub Pages 등)에서 올바른 보안 정책 수립을 방해할 수 있는 안티 패턴입니다.
**Learning:** 정적으로 고정된 인라인 스타일이나 스크립트에는 난수화된 Nonce보다 콘텐츠 자체의 해시(SHA-256 등)를 사용하는 것이 안전하고 일관된 방식임을 배웠습니다.
**Prevention:** 자동 생성되는 정적 HTML의 콘텐츠 보안 정책(CSP)에는 `style-src 'sha256-<HASH>'` 방식을 적용하고, `<style>` 태그에서 불필요한 `nonce` 속성을 제거하여 브라우저의 무결성 검증 기능을 적극 활용하십시오.
## 2024-07-27 - [원자적 파일 교체를 통한 파일 무결성 보장]
**Vulnerability:** 파일 교체 시 `REPLACE_EXISTING`을 사용하면 덮어쓰기 도중 파일에 접근할 경우 불완전한 상태(TOCTOU)에 노출될 수 있음.
**Learning:** `ATOMIC_MOVE` 옵션을 우선 시도하되, 시스템 지원 여부에 따라 `AtomicMoveNotSupportedException`이 발생할 수 있으므로 함수형 매개변수를 활용하여 fallback을 구현하고 테스트에서 예외를 주입함.
**Prevention:** 파일 덮어쓰기 작업은 항상 임시 파일을 작성한 후 원자적으로(Atomic) 이름을 변경하는 방식을 사용하여 안전성을 확보함.
13 changes: 11 additions & 2 deletions src/main/kotlin/html4tree/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import java.security.MessageDigest
import java.nio.file.Files
import java.nio.file.LinkOption
import java.nio.file.StandardCopyOption
import java.nio.file.AtomicMoveNotSupportedException
import java.nio.file.attribute.BasicFileAttributes
import java.util.Base64
import com.github.ajalt.clikt.core.CliktCommand
Expand Down Expand Up @@ -229,12 +230,20 @@ fun process_ignore_file(curr_dir: File, dirFilesNames: Array<String>? = null): S
return files_to_exclude
}

fun write_index_file(curr_dir: File, content: String) {
fun write_index_file(
curr_dir: File,
content: String,
moveFile: (java.nio.file.Path, java.nio.file.Path) -> Unit = { src, dst -> Files.move(src, dst, StandardCopyOption.ATOMIC_MOVE) }
) {
val indexPath = curr_dir.toPath().resolve("index.html")
val tempPath = Files.createTempFile(curr_dir.toPath(), ".index-", ".html")
try {
Files.write(tempPath, content.toByteArray(Charsets.UTF_8))
Files.move(tempPath, indexPath, StandardCopyOption.REPLACE_EXISTING)
try {
moveFile(tempPath, indexPath)
} catch (e: AtomicMoveNotSupportedException) {
Files.move(tempPath, indexPath, StandardCopyOption.REPLACE_EXISTING)
}
} finally {
Files.deleteIfExists(tempPath)
}
Expand Down
11 changes: 11 additions & 0 deletions src/test/kotlin/html4tree/MainTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,17 @@ class MainTest {
assertFalse(excluded.contains("test.txt1001"))
}

@Test
fun testWriteIndexFileAtomicMoveNotSupportedFallback() {
val content = "atomic move test"
write_index_file(tempDir, content) { src, dst ->
throw java.nio.file.AtomicMoveNotSupportedException(src.toString(), dst.toString(), "not supported")
}
val indexPath = File(tempDir, "index.html")
assertTrue(indexPath.exists())
assertEquals(content, indexPath.readText())
}

@Test
fun testToctouSymlinkSwapRejection() {
val subdir = File(tempDir, "toctou_test_dir")
Expand Down
Loading