diff --git a/.jules/sentinel.md b/.jules/sentinel.md index fa902ea..a1cb003 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -47,3 +47,7 @@ **Vulnerability:** 사용자 입력값(`lang`)을 검증 없이 `console.warn`과 같은 로그 함수에 그대로 보간하여 출력할 경우, 로그 인젝션(Log Forging) 공격에 노출될 수 있음. **Learning:** 사용자 입력이 포함된 문자열을 직접 보간하면 악의적인 페이로드가 로그 파일에 주입되어 로그 분석 시스템을 방해하거나 다른 취약점을 연계할 수 있음. **Prevention:** 로그를 남길 때는 검증되지 않은 외부 입력값을 동적으로 문자열에 주입(Interpolation)하는 대신, 사전에 정의된 정적이고 안전한 메시지로 대체해야 함. +## 2026-08-27 - DOM 조작 시 방어적 프로그래밍 적용 +**Vulnerability:** 누락된 DOM 요소에 접근할 때 null 참조로 인한 unhandled `TypeError` 발생 위험 (스크립트 실행 중단 초래). +**Learning:** 정적 사이트에서 바닐라 JavaScript를 작성할 때 DOM 요소를 조회한 뒤 결과가 null인지 확인하지 않으면, 특정 요소가 없거나 렌더링되지 않았을 때 전체 스크립트 실행이 중단될 수 있습니다. 이는 취약점이라기보다는 가용성과 견고성(Fail securely) 문제입니다. +**Prevention:** `document.getElementById`나 `closest`와 같은 DOM 조회 API를 사용할 때는 항상 반환된 요소가 존재하는지 null 체크를 수행하는 방어적 프로그래밍(Defensive Programming) 패턴을 적용해야 합니다. diff --git a/components/krds-gallery.js b/components/krds-gallery.js index 672e72f..8314e5d 100644 --- a/components/krds-gallery.js +++ b/components/krds-gallery.js @@ -6,12 +6,23 @@ tabList.forEach((t) => { const sel = t === tab; t.setAttribute("aria-selected", sel); - document.getElementById(t.getAttribute("aria-controls")).hidden = !sel; + const panelId = t.getAttribute("aria-controls"); + if (panelId) { + const panel = document.getElementById(panelId); + if (panel) { + panel.hidden = !sel; + } + } }); }); }); }); // Tag remove document.querySelectorAll(".krds-tag__remove").forEach((btn) => - btn.addEventListener("click", () => btn.closest(".krds-tag").remove()) + btn.addEventListener("click", () => { + const tag = btn.closest(".krds-tag"); + if (tag) { + tag.remove(); + } + }) ); diff --git a/tests/test_component_gallery_security.py b/tests/test_component_gallery_security.py index 8eaf9e4..4ef00d2 100644 --- a/tests/test_component_gallery_security.py +++ b/tests/test_component_gallery_security.py @@ -82,3 +82,11 @@ def test_component_gallery_inputs_have_length_limits() -> None: if 'type="checkbox"' in inp or 'type="radio"' in inp: continue assert 'maxlength=' in inp, f"Input missing maxlength: {inp}" + +def test_component_gallery_script_has_null_checks() -> None: + """Ensure DOM operations practice defensive programming by checking for null.""" + script_path = ROOT / "components" / "krds-gallery.js" + script = script_path.read_text(encoding="utf-8") + assert "if (panelId)" in script + assert "if (panel)" in script + assert "if (tag)" in script