From 8d747e9c052e359dc9991e46ec2f60104f62e7e3 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 27 Aug 2026 13:51:50 +0000 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[securi?= =?UTF-8?q?ty=20improvement]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/sentinel.md | 4 ++++ components/krds-gallery.js | 15 +++++++++++++-- tests/test_component_gallery_security.py | 8 ++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) 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 From 0bd4b8e52d4d7a9bb1d60b54e0c6d55eea09e376 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 27 Aug 2026 14:27:54 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[securi?= =?UTF-8?q?ty=20improvement]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 40c41a4b31676646f808d1d8868f3a3ea225e48d Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 27 Aug 2026 15:00:41 +0000 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[securi?= =?UTF-8?q?ty=20improvement]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 9b060bbe3de294447582d2335b25c709ec392f4d Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 27 Aug 2026 15:21:51 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[securi?= =?UTF-8?q?ty=20improvement]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit