diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4cc49139..d0427907 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,14 +3,8 @@ name: CI on: push: branches: [master] - paths-ignore: - - "docs/**" - - "*.md" pull_request: branches: [master] - paths-ignore: - - "docs/**" - - "*.md" permissions: contents: read diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md new file mode 100644 index 00000000..2bfe6b35 --- /dev/null +++ b/docs/product-technical-gap-baseline.md @@ -0,0 +1,37 @@ +# Product / Technical Gap Baseline + +이 문서는 html4tree의 live code와 열린 PR을 기준으로 상용화 Gap을 추적한다. 병합되지 않은 branch의 동작을 protected `master`의 현재 기능으로 간주하지 않는다. + +## Pathname portability and resource claims + +### 문제 + +PR #598은 `topDir.length <= 4096`을 보안 경계로 추가하고, 4,097자 경로가 OOM/DoS를 일으킬 수 있다는 HIGH severity 설명을 붙였다. 이 설명과 숫자는 portable filesystem contract가 아니다. + +Java `File`은 pathname string ↔ abstract pathname 변환이 본질적으로 system-dependent라고 명시한다. POSIX 계열은 filesystem/path-resolution 단계에서 `NAME_MAX`/`PATH_MAX`와 `ENAMETOOLONG`을 다루며, Windows는 전통적인 `MAX_PATH` 260 제한과 long-path opt-in 이후의 다른 동작을 함께 가진다. 따라서 Kotlin `String.length`의 UTF-16 code-unit 수 4,096을 모든 지원 환경의 경로 한계처럼 적용하면 실제 filesystem semantics와 분리된 임의 제한이 된다. + +또한 CLI가 이미 `String` 인자를 구성한 뒤 `go()`에 전달하므로 4,096자 검사 자체가 argument allocation OOM을 예방하는 경계도 아니다. 4,097자 문자열은 그 자체로 HIGH severity resource-exhaustion evidence가 아니다. + +### 선택 + +- `go()`는 임의의 4,096 UTF-16 code-unit 상한을 사용하지 않는다. +- 경로의 실제 존재 여부와 symlink/root 정책은 Java/NIO와 underlying filesystem semantics를 통해 검증한다. +- 경로 길이만으로 HIGH/OOM 취약점을 주장하지 않는다. +- 실제 crawl resource exhaustion은 입력 문자열 길이와 분리해 디렉토리 수, entry fan-out, 출력량, I/O 시간/메모리 profile을 기준으로 다뤄야 한다. 이 PR에서 검증되지 않은 arbitrary crawl budget을 새로 정하지 않는다. + +### RED → GREEN evidence + +- RED `a30b1c60d8036546a43140a6c63dd41d56b7c851`: 4,097자 문자열이라는 이유만으로 portable filesystem limit 오류를 반환하지 않고, 기존 filesystem validity contract로 실패해야 한다는 회귀 테스트를 추가했다. 당시 branch의 4,096 검사 때문에 이 계약은 실패한다. +- GREEN `044f68d25b9aa0d7bcde3da16c4d81b4c701337c`: 임의 4,096 length gate를 제거했다. 기존 blank/traversal/root/symlink/directory 검증은 유지한다. + +### 남은 Gap + +`maxLevel=-1`은 깊이 제한이 없으며 실제 대규모 tree에서 디렉토리 수·fan-out·생성 파일 수에 따른 자원 사용량을 별도로 측정하지 않는다. 이를 DoS로 분류하거나 새 기본 상한을 넣기 전에 real/right-cleared representative trees에서 directory count, peak RSS, filesystem calls, elapsed time, generated bytes를 측정하고 buyer contract를 정해야 한다. + +### References + +- Oracle. (2023). *File (Java SE 21 & JDK 21)*. Java Platform, Standard Edition documentation. https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/File.html +- Microsoft. (n.d.). *Maximum Path Length Limitation*. Windows App Development documentation. https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation +- The Open Group / IEEE. (n.d.). *pathconf()* / pathname limit semantics. POSIX interfaces as published in Linux man-pages. https://man7.org/linux/man-pages/man3/fpathconf.3p.html + +위 자료는 pathname semantics가 OS/filesystem에 종속된다는 기술 계약을 확인하는 primary documentation으로 사용한다. 특정 filesystem의 수치를 다른 플랫폼의 universal application limit로 전환하지 않는다. diff --git a/src/test/kotlin/html4tree/PathValidationPortabilityTest.kt b/src/test/kotlin/html4tree/PathValidationPortabilityTest.kt new file mode 100644 index 00000000..37ab5cf2 --- /dev/null +++ b/src/test/kotlin/html4tree/PathValidationPortabilityTest.kt @@ -0,0 +1,19 @@ +package html4tree + +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class PathValidationPortabilityTest { + @Test + fun pathLengthIsNotTreatedAsAPortableFilesystemLimit() { + val error = assertFailsWith { + go("a".repeat(4097), -1) + } + + assertEquals( + "Top directory must be an existing non-symlink directory", + error.message + ) + } +}