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: 0 additions & 4 deletions .jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,3 @@
## 2024-08-01 - ๋„ค์ดํ‹ฐ๋ธŒ ๋ธŒ๋ผ์šฐ์ € UI์˜ ๋‹คํฌ ๋ชจ๋“œ ์ง€์› ๊ฐ•์ œ
**ํ•™์Šต:** CSS ๋ฏธ๋””์–ด ์ฟผ๋ฆฌ(`@media (prefers-color-scheme: dark)`)๋ฅผ ํ†ตํ•ด ๋‹คํฌ ๋ชจ๋“œ๋ฅผ ์ง€์›ํ•˜๋”๋ผ๋„, ๋ธŒ๋ผ์šฐ์ €์˜ ๋„ค์ดํ‹ฐ๋ธŒ UI ์š”์†Œ(์Šคํฌ๋กค๋ฐ”, ๊ธฐ๋ณธ ํผ ์ปจํŠธ๋กค, ๊ธฐ๋ณธ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋“ฑ)๋Š” ํ…Œ๋งˆ ๋ณ€๊ฒฝ์„ ์ธ์‹ํ•˜์ง€ ๋ชปํ•ด ์–ด๋‘์šด ํ…Œ๋งˆ ํ™˜๊ฒฝ์—์„œ ๋ฐ์€ ์Šคํฌ๋กค๋ฐ”๊ฐ€ ํ‘œ์‹œ๋˜๋Š” ๋“ฑ ์‹œ๊ฐ์  ๋ถˆ์ผ์น˜๋ฅผ ์ดˆ๋ž˜ํ•ฉ๋‹ˆ๋‹ค.
**์กฐ์น˜:** ํ•ญ์ƒ HTML ๋ฌธ์„œ์˜ `<head>` ์˜์—ญ์— `<meta name="color-scheme" content="light dark">` ๋ฉ”ํƒ€ ํƒœ๊ทธ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ธŒ๋ผ์šฐ์ € ์ˆ˜์ค€์—์„œ ์‚ฌ์šฉ์ž์˜ ์‹œ์Šคํ…œ ํ…Œ๋งˆ(๋‹คํฌ ๋ชจ๋“œ ๋“ฑ)๋ฅผ ์™„์ „ํžˆ ์ƒ์†๋ฐ›์•„ ์ผ๊ด€์„ฑ ์žˆ๋Š” ๋„ค์ดํ‹ฐ๋ธŒ UI๋ฅผ ๋ Œ๋”๋งํ•˜๋„๋ก ๋ณด์žฅํ•˜์‹ญ์‹œ์˜ค.

## 2024-07-13 - ๋นˆ ๋””๋ ‰ํ† ๋ฆฌ ์ƒํƒœ์˜ ์ ‘๊ทผ์„ฑ(Accessibility) ๊ฐœ์„ 
**Learning:** ์ •์  ํŒŒ์ผ ์„œ๋ฒ„์˜ ๋นˆ ๋””๋ ‰ํ† ๋ฆฌ ์ƒํƒœ๋Š” ์Šคํฌ๋ฆฐ ๋ฆฌ๋” ์‚ฌ์šฉ์ž์—๊ฒŒ ์ปจํ…์ธ  ๋ˆ„๋ฝ์œผ๋กœ ์˜คํ•ด๋ฐ›์„ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์‹œ๊ฐ์ ์œผ๋กœ๋„ ์ผ๋ฐ˜ ๋ฆฌ์ŠคํŠธ ์•„์ดํ…œ๊ณผ ์ •๋ ฌ์ด ๋งž์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
**Action:** ๋นˆ ์ƒํƒœ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ์š”์†Œ์— `role="status"`๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์Šคํฌ๋ฆฐ ๋ฆฌ๋”๊ฐ€ ๋ช…ํ™•ํ•˜๊ฒŒ ์ธ์ง€ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ณ , ์•„์ด์ฝ˜๊ณผ flex ๋ ˆ์ด์•„์›ƒ์„ ํ†ตํ•ด ๋‹ค๋ฅธ ๋ฆฌ์ŠคํŠธ ์•„์ดํ…œ๊ณผ ์ผ๊ด€๋œ ์‹œ๊ฐ์  ํ๋ฆ„์„ ์ œ๊ณตํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,8 @@
**Vulnerability:** ์ •์  HTML ์ƒ์„ฑ ๋„๊ตฌ์—์„œ ๋งค๋ฒˆ ๋‹ค๋ฅธ Nonce๋ฅผ ๋™์ ์œผ๋กœ ์ƒ์„ฑํ•˜์—ฌ CSP์— ์ ์šฉํ•˜๋Š” ๊ฒƒ์€, ์บ์‹ฑ ํšจ์œจ์„ ์ €ํ•˜์‹œํ‚ฌ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ •์  ๋ฐฐํฌ ํ™˜๊ฒฝ(์˜ˆ: GitHub Pages ๋“ฑ)์—์„œ ์˜ฌ๋ฐ”๋ฅธ ๋ณด์•ˆ ์ •์ฑ… ์ˆ˜๋ฆฝ์„ ๋ฐฉํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ์•ˆํ‹ฐ ํŒจํ„ด์ž…๋‹ˆ๋‹ค.
**Learning:** ์ •์ ์œผ๋กœ ๊ณ ์ •๋œ ์ธ๋ผ์ธ ์Šคํƒ€์ผ์ด๋‚˜ ์Šคํฌ๋ฆฝํŠธ์—๋Š” ๋‚œ์ˆ˜ํ™”๋œ Nonce๋ณด๋‹ค ์ฝ˜ํ…์ธ  ์ž์ฒด์˜ ํ•ด์‹œ(SHA-256 ๋“ฑ)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์•ˆ์ „ํ•˜๊ณ  ์ผ๊ด€๋œ ๋ฐฉ์‹์ž„์„ ๋ฐฐ์› ์Šต๋‹ˆ๋‹ค.
**Prevention:** ์ž๋™ ์ƒ์„ฑ๋˜๋Š” ์ •์  HTML์˜ ์ฝ˜ํ…์ธ  ๋ณด์•ˆ ์ •์ฑ…(CSP)์—๋Š” `style-src 'sha256-<HASH>'` ๋ฐฉ์‹์„ ์ ์šฉํ•˜๊ณ , `<style>` ํƒœ๊ทธ์—์„œ ๋ถˆํ•„์š”ํ•œ `nonce` ์†์„ฑ์„ ์ œ๊ฑฐํ•˜์—ฌ ๋ธŒ๋ผ์šฐ์ €์˜ ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆ ๊ธฐ๋Šฅ์„ ์ ๊ทน ํ™œ์šฉํ•˜์‹ญ์‹œ์˜ค.

## 2024-07-28 - [html4tree] index.html ํŒŒ์ผ ์ƒ์„ฑ ์‹œ ์›์ž์  ์“ฐ๊ธฐ(Atomic Move) ์ง€์›
**Vulnerability:** ํŒŒ์ผ ๊ต์ฒด ์‹œ ์›์ž์  ์—ฐ์‚ฐ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด ํŒŒ์ผ์ด ๊ต์ฒด๋˜๋Š” ์งง์€ ์ˆœ๊ฐ„(TOCTOU)์— ์•…์˜์ ์ธ ํ”„๋กœ์„ธ์Šค๋‚˜ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๋ถ€๋ถ„์ ์œผ๋กœ ์“ฐ์ธ ํŒŒ์ผ์„ ์ฝ๊ฑฐ๋‚˜ ์‹ฌ๋ณผ๋ฆญ ๋งํฌ๋กœ ๊ต์ฒดํ•˜์—ฌ ๊ณต๊ฒฉ(์˜ˆ: ์ž„์˜ ํŒŒ์ผ ๋ฎ์–ด์“ฐ๊ธฐ)์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
**Learning:** `Files.move` ์‹œ `StandardCopyOption.REPLACE_EXISTING`๋งŒ์„ ์‚ฌ์šฉํ•˜๋ฉด ํŒŒ์ผ ์‹œ์Šคํ…œ์— ๋”ฐ๋ผ ์›์ž์„ฑ์„ ๋ณด์žฅํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
**Prevention:** ํŒŒ์ผ ์“ฐ๊ธฐ ์‹œ ๋จผ์ € ์ž„์‹œ ํŒŒ์ผ์— ๋‚ด์šฉ์„ ์™„์ „ํžˆ ๊ธฐ๋กํ•œ ํ›„, `StandardCopyOption.ATOMIC_MOVE`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์›์ž์ ์œผ๋กœ ์ตœ์ข… ์œ„์น˜(์˜ˆ: `index.html`)๋กœ ์ด๋™์‹œํ‚ค์‹ญ์‹œ์˜ค. ํŒŒ์ผ ์‹œ์Šคํ…œ์ด ์ด๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ํ™˜๊ฒฝ์„ ์œ„ํ•ด `AtomicMoveNotSupportedException` ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜์—ฌ `REPLACE_EXISTING`์œผ๋กœ ๋Œ€์ฒด(Fallback)ํ•˜๋„๋ก ์„ค๊ณ„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
8 changes: 0 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,3 @@ other coding agent). html4tree is a Kotlin CLI (Gradle build) that generates
code-review-graph MCP tools โ€” BEFORE grep/find, since it surfaces
callers/callees/impact that text search misses.
<!-- END cwl-agent-guidance -->

## Code-owner review gates โ€” disabled (on hold)

As of 2026-08-04, code-owner review requirements (`require_code_owner_reviews` in branch
protection, `require_code_owner_review` in rulesets) are disabled across the ContextualWisdomLab
org: there is a single maintainer (solo developer), so a code-owner approval gate can never be
satisfied. This is ON HOLD until the org has multiple maintainers โ€” do NOT re-enable these
settings or add CODEOWNERS-based merge gates before then.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ repositories {

dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
testCompile 'junit:junit:4.13.2'
testCompile 'junit:junit:4.11'
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
compile "com.github.ajalt:clikt:2.7.1"
}
Expand Down
14 changes: 8 additions & 6 deletions src/main/kotlin/html4tree/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package html4tree

import java.io.File
import java.security.MessageDigest
import java.nio.file.AtomicMoveNotSupportedException
import java.nio.file.Files
import java.nio.file.LinkOption
import java.nio.file.StandardCopyOption
Expand Down Expand Up @@ -229,12 +230,16 @@ 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, Array<java.nio.file.CopyOption>) -> java.nio.file.Path = { src, dest, options -> Files.move(src, dest, *options) }) {
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, arrayOf(StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING))
} catch (e: AtomicMoveNotSupportedException) {
moveFile(tempPath, indexPath, arrayOf(StandardCopyOption.REPLACE_EXISTING))
}
} finally {
Files.deleteIfExists(tempPath)
}
Expand Down Expand Up @@ -304,9 +309,6 @@ fun process_dir(curr_dir: File, excludeSet: Set<String>? = null, dirFiles: Array
}
}
.empty-dir {
display: flex;
align-items: flex-start;
gap: 0.5rem;
padding: 0.5rem;
opacity: 0.7;
font-style: italic;
Expand Down Expand Up @@ -373,7 +375,7 @@ ${cssContent} </style>
}

if(l.isEmpty()){
l.append(""" <li><div class="empty-dir" role="status"><span class="icon" aria-hidden="true">&#8505;</span> <span>์ด ๋””๋ ‰ํ† ๋ฆฌ๋Š” ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.</span></div></li>""")
l.append(""" <li><div class="empty-dir">์ด ๋””๋ ‰ํ† ๋ฆฌ๋Š” ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.</div></li>""")
l.append('\n')
}

Expand Down
19 changes: 18 additions & 1 deletion src/test/kotlin/html4tree/MainTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import org.junit.Test
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.PrintStream
import java.nio.file.AtomicMoveNotSupportedException
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
Expand Down Expand Up @@ -94,7 +96,6 @@ class MainTest {
val htmlContent = indexFile.readText()
assertTrue(htmlContent.contains("<html lang=\"ko\">"))
assertTrue(htmlContent.contains("์ด ๋””๋ ‰ํ† ๋ฆฌ๋Š” ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค."))
assertTrue(htmlContent.contains("role=\"status\""))
assertTrue(htmlContent.contains("role=\"list\""))
}

Expand Down Expand Up @@ -429,6 +430,22 @@ class MainTest {
assertFalse(File(subsubdir, "index.html").exists())
}

@Test
fun testWriteIndexFileAtomicMoveNotSupported() {
val content = "test content"
var fallbackCalled = false
write_index_file(tempDir, content) { src, dest, options ->
if (options.contains(StandardCopyOption.ATOMIC_MOVE)) {
throw AtomicMoveNotSupportedException(src.toString(), dest.toString(), "Mocked exception")
} else {
fallbackCalled = true
Files.move(src, dest, *options)
}
Comment on lines +437 to +443

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

๐ŸŽฏ Functional Correctness | ๐ŸŸก Minor | โšก Quick win

๋Œ€์ฒด ์ด๋™ ์˜ต์…˜์„ ์ •ํ™•ํžˆ ๊ฒ€์ฆํ•˜์„ธ์š”.

ํ˜„์žฌ ํ…Œ์ŠคํŠธ๋Š” ATOMIC_MOVE๊ฐ€ ์—†์œผ๋ฉด ๋ชจ๋“  ์˜ต์…˜์„ ๋Œ€์ฒด ๊ฒฝ๋กœ๋กœ ์ธ์ •ํ•ฉ๋‹ˆ๋‹ค. REPLACE_EXISTING์„ ๋ˆ„๋ฝํ•ด๋„ fallbackCalled์™€ ์ตœ์ข… ๋‚ด์šฉ ๊ฒ€์ฆ๋งŒ์œผ๋กœ ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผํ•ฉ๋‹ˆ๋‹ค. ์˜ต์…˜ ๋ฐฐ์—ด์˜ ํฌ๊ธฐ์™€ options[0] == StandardCopyOption.REPLACE_EXISTING์„ ๊ฒ€์ฆํ•˜์„ธ์š”.

์ˆ˜์ • ์˜ˆ์‹œ
             } else {
                 fallbackCalled = true
+                assertEquals(1, options.size)
+                assertEquals(StandardCopyOption.REPLACE_EXISTING, options[0])
                 Files.move(src, dest, *options)
             }
๐Ÿ“ Committable suggestion

โ€ผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
write_index_file(tempDir, content) { src, dest, options ->
if (options.contains(StandardCopyOption.ATOMIC_MOVE)) {
throw AtomicMoveNotSupportedException(src.toString(), dest.toString(), "Mocked exception")
} else {
fallbackCalled = true
Files.move(src, dest, *options)
}
write_index_file(tempDir, content) { src, dest, options ->
if (options.contains(StandardCopyOption.ATOMIC_MOVE)) {
throw AtomicMoveNotSupportedException(src.toString(), dest.toString(), "Mocked exception")
} else {
fallbackCalled = true
assertEquals(1, options.size)
assertEquals(StandardCopyOption.REPLACE_EXISTING, options[0])
Files.move(src, dest, *options)
}
๐Ÿค– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/kotlin/html4tree/MainTest.kt` around lines 438 - 444, Update the
write_index_file fallback callback in MainTest so it verifies the replacement
move options exactly: require one option and ensure it is
StandardCopyOption.REPLACE_EXISTING before setting fallbackCalled and performing
Files.move. Keep the existing AtomicMoveNotSupportedException path and final
content assertions unchanged.

}
assertTrue(fallbackCalled, "Fallback to REPLACE_EXISTING should have been called")
assertEquals(content, File(tempDir, "index.html").readText())
}

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