Skip to content
Draft
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- API 에러 응답 생성 시 내부 예외 체인을 억제하여 의존성 오류나 내부 경로가 노출될 가능성을 줄임
- API 응답 미들웨어에 `Cache-Control: no-store, max-age=0` 헤더를 추가하여 민감한 파싱 데이터의 브라우저 및 중간 캐싱을 방지
- `uv.lock`의 의존성을 재잠금하여 실제 `pip-audit`/`trivy-fs` CVE를 제거: 런타임 경로의 `pillow` 12.2.0→12.3.0 (PYSEC-2026-3451/3452/3453/3454/3493/3494/3495/3496, 이미지 파서 취약점 8건), `pypdf>=6.15.0,<7.0` (lock 6.15.0; CVE-2026-59935/59936/59937/59938/71852/71870, PDF 파싱 경로), `click` 8.3.2→8.4.2 (PYSEC-2026-2132) — 모두 스캔 PDF/이미지 파싱 런타임에 직접 관련되며 선언 범위와 lock을 함께 고정함. 빌드 도구 `setuptools` 81.0.0→83.0.0 (CVE-2026-59890). 문서 툴체인의 `pymdown-extensions` 10.21.3→11.0.1 (CVE-2026-61632, MEDIUM)은 `mkdocs-material` 9.6.x의 `pymdown-extensions~=10.2`(`<11`) 상한 때문에 막혀 있었으므로, docs extra 핀을 `mkdocs-material>=9.7,<9.8`로 올려(9.7.x는 상한을 `>=10.2`로 완화) 해소함. `uv run mkdocs build --strict` 통과 확인. 조치 후 전체 잠금(런타임+extras) `pip-audit`: 취약점 0건.
- `pypdf`를 `>=6.16.1,<7.0`(lock 6.16.2)로 재잠금하여 신규 `trivy-fs` MEDIUM 3건을 제거: CVE-2026-84309(순환 `/Next` 아웃라인 트리로 `TreeObject.insert_child`가 무한 루프에 빠짐), CVE-2026-84310(`_get_outline`의 항목 수·중첩 깊이 제한 부재로 장시간·과다 메모리 소비), CVE-2026-84311(재사용 XObject 폼 그래프에 방문 여부 추적이 없어 텍스트 추출 시 경로가 지수적으로 증가) — 모두 신뢰할 수 없는 PDF 업로드를 처리하는 파서 가용성 취약점이며 pypdf 6.16.0/6.16.1에서 각각 수정됨. 조치 후 전체 잠금 `pip-audit`: 취약점 0건.

### Performance
- `newsdom_api.dom_builder._html_safe_text` 함수에 early return과 타입 체크를 도입하여 불필요한 `str()` 캐스팅을 제거함으로써 처리 속도를 개선했습니다.
Expand Down
32 changes: 29 additions & 3 deletions docs/doctoring/dependency-security-baseline.md
Comment thread
seonghobae marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ The adopted floors are:

- `setuptools>=83` for the build backend;
- `Pillow>=12.3,<13.0` for image parsing on the untrusted document-ingestion path;
- `pypdf>=6.15.0,<7.0` for PDF parsing;
- `pypdf>=6.16.1,<7.0` for PDF parsing;
- `mkdocs-material>=9.7,<9.8`, allowing `pymdown-extensions>=11` while the MkDocs
core remains on the supported 1.x line.

The generated lock additionally resolves Click 8.4.2, setuptools 83.0.0,
Pillow 12.3.0, pypdf 6.15.0, mkdocs-material 9.7.7, and
Pillow 12.3.0, pypdf 6.16.2, mkdocs-material 9.7.7, and
pymdown-extensions 11.0.1. Direct floors prevent a later lock refresh from
silently selecting known-vulnerable ranges again.

Expand All @@ -36,6 +36,20 @@ successfully without suppressing either finding. The shared direct floor and loc
therefore move together to 6.15.0 rather than hiding the findings in
`.trivyignore`.

On September 3, 2026, an unrelated PR's `trivy-fs` gate reported three further
MEDIUM findings against the locked 6.15.0 artifact: CVE-2026-84309 (an attacker
-crafted PDF whose cyclic `/Next` outline-tree structure drives
`TreeObject.insert_child` into an infinite loop on a writing code path),
CVE-2026-84310 (unbounded entry-count and nesting-depth traversal in
`_get_outline` allowing long runtimes and large memory use), and CVE-2026-84311
(a reused-XObject form graph without a memoized/visited guard in
`PageObject._extract_text`/`extract_xform_text`, producing exponentially many
traversal paths). All three are availability risks directly on NewsDOM's
untrusted PDF-ingestion path and are fixed upstream in pypdf 6.16.0/6.16.1
respectively. The direct floor and lock move together to 6.16.1 (lock resolves
6.16.2, the latest compatible release at the time of this record) rather than
suppressing the findings.

CVE-2026-59890 affects setuptools versions before 83.0.0. On
normalization-preserving macOS filesystems, specially named files could bypass
`MANIFEST.in` exclusion matching and enter a source distribution. Although this
Expand All @@ -46,7 +60,7 @@ Pillow 12.3.0 and pypdf release artifacts are distributed through PyPI with
published cryptographic file digests. Those artifacts and digests provide
provenance inputs; they do not by themselves establish that a package is safe.
Repository scans, hash-locked resolution, current-head tests, and independent
review remain mandatory. PyPI's official JSON metadata confirms the 6.15.0
review remain mandatory. PyPI's official JSON metadata confirms the 6.16.2
release and the artifact hashes recorded in this repository's generated lock.

## Secure-development and provenance controls
Expand Down Expand Up @@ -129,6 +143,15 @@ Open Source Vulnerabilities. (2026c). *CVE-2026-71852*. Retrieved August 9,
Open Source Vulnerabilities. (2026d). *CVE-2026-71870*. Retrieved August 9,
2026, from https://osv.dev/vulnerability/CVE-2026-71870

Open Source Vulnerabilities. (2026e). *CVE-2026-84309*. Retrieved September 3,
2026, from https://osv.dev/vulnerability/CVE-2026-84309

Open Source Vulnerabilities. (2026f). *CVE-2026-84310*. Retrieved September 3,
2026, from https://osv.dev/vulnerability/CVE-2026-84310

Open Source Vulnerabilities. (2026g). *CVE-2026-84311*. Retrieved September 3,
2026, from https://osv.dev/vulnerability/CVE-2026-84311

Python Packaging Authority. (2026a). *Digital attestations*. PyPI Docs.
Retrieved August 4, 2026, from https://docs.pypi.org/attestations/

Expand All @@ -140,3 +163,6 @@ Python Packaging Authority. (2026c). *pypdf 6.15.0*. Python Package Index.

Python Packaging Authority. (2026d). *setuptools 83.0.0*. Python Package Index.
Retrieved August 4, 2026, from https://pypi.org/project/setuptools/83.0.0/

Python Packaging Authority. (2026e). *pypdf 6.16.2*. Python Package Index.
Retrieved September 3, 2026, from https://pypi.org/project/pypdf/6.16.2/
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ dependencies = [
"python-multipart>=0.0.31,<1.0",
"reportlab>=4.2,<6.0",
"Pillow>=12.3,<13.0",
"pypdf>=6.15.0,<7.0",
"pypdf>=6.16.1,<7.0",
]

[project.optional-dependencies]
Expand Down
4 changes: 2 additions & 2 deletions tests/test_project_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def test_security_dependency_floors_exclude_known_vulnerable_ranges():
dependencies_section = _dependencies_section(text)

assert '"Pillow>=12.3,<13.0"' in dependencies_section
assert '"pypdf>=6.15.0,<7.0"' in dependencies_section
assert '"pypdf>=6.16.1,<7.0"' in dependencies_section
assert 'requires = ["setuptools>=83", "wheel"]' in text


Expand Down Expand Up @@ -202,4 +202,4 @@ def test_uv_lock_does_not_track_external_mineru_pipeline_runtime_stack():


def test_uv_lock_pins_pypdf_at_patched_release():
assert _locked_package_version("pypdf") >= (6, 15, 0)
assert _locked_package_version("pypdf") >= (6, 16, 1)
34 changes: 28 additions & 6 deletions tests/test_pypdf_security_floor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@
import yaml


_REQUIRED_PYPDF_VERSION = (6, 15, 0)
_CURRENT_PYPDF_CVES = ("CVE-2026-71852", "CVE-2026-71870")
_LOCKED_PYPDF_REQUIREMENT = '{ name = "pypdf", specifier = ">=6.15.0,<7.0" },'
_REQUIRED_PYPDF_VERSION = (6, 16, 1)
_CURRENT_PYPDF_CVES = ("CVE-2026-84309", "CVE-2026-84310", "CVE-2026-84311")
# Every literal below derives from the tuple above, so a future floor bump has
# exactly one place to change and the documentation checks cannot keep
# matching a superseded floor by accident.
_PYPDF_SPECIFIER = ">=" + ".".join(str(part) for part in _REQUIRED_PYPDF_VERSION) + ",<7.0"
_LOCKED_PYPDF_REQUIREMENT = f'{{ name = "pypdf", specifier = "{_PYPDF_SPECIFIER}" }},'


def _locked_pypdf_version() -> tuple[int, ...]:
Expand All @@ -24,10 +28,10 @@ def _locked_pypdf_version() -> tuple[int, ...]:


def test_project_declares_current_pypdf_security_floor() -> None:
"""Prevent future lock refreshes from selecting the vulnerable 6.14.x line."""
"""Prevent lock refreshes from selecting pypdf below the current floor."""

project_text = Path("pyproject.toml").read_text(encoding="utf-8")
assert '"pypdf>=6.15.0,<7.0"' in project_text
assert f'"pypdf{_PYPDF_SPECIFIER}"' in project_text


def test_lock_uses_current_pypdf_security_release() -> None:
Expand Down Expand Up @@ -61,7 +65,25 @@ def test_current_pypdf_advisories_and_floor_are_documented() -> None:

for cve_id in _CURRENT_PYPDF_CVES:
assert f"https://osv.dev/vulnerability/{cve_id}" in baseline
assert "`pypdf>=6.15.0,<7.0`" in changelog

# The CHANGELOG entry writes the package and its specifier as separate
# inline-code spans; require both, plus the release the lock resolved,
# on one entry line rather than a single fused code span.
locked_release = ".".join(str(part) for part in _locked_pypdf_version())
changelog_entry = re.compile(
r"`pypdf`[^\n]*`" + re.escape(_PYPDF_SPECIFIER) + r"`[^\n]*" + re.escape(locked_release)
)
assert changelog_entry.search(changelog), (
f"CHANGELOG.md lacks an entry naming `pypdf`, `{_PYPDF_SPECIFIER}` and lock {locked_release}"
)

# Markdown wraps the provenance sentence across lines; compare on
# whitespace-normalized text, and tie the release to the lock file.
provenance = (
f"PyPI's official JSON metadata confirms the {locked_release} release and the "
"artifact hashes recorded in this repository's generated lock."
)
assert provenance in re.sub(r"\s+", " ", baseline)


def test_trivy_registry_exception_is_scoped_to_the_example_manifest() -> None:
Expand Down
8 changes: 4 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading