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: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2026-07-12 - 컴포넌트 갤러리의 엄격한 CSP 적용
**Vulnerability:** `components/index.html`에 보안 헤더가 누락되어 있고 인라인 스크립트/스타일을 사용하여 XSS 공격 등에 취약할 수 있음.
**Learning:** 엄격한 CSP(`style-src 'self'`)를 추가하면 인라인 HTML 스타일 속성과 CSS 내 인라인 `data:` 이미지 URI가 차단됩니다. 이를 해결하려면 CSS 클래스로 추출하고 명시적으로 스킴을 추가(`img-src 'self' data:;`)해야 합니다.
**Prevention:** 엄격한 CSP를 도입하기 전에 항상 인라인 `<script>` 및 `<style>` 블록을 외부 파일로 리팩터링하고, 인라인 `style="..."` 속성을 유틸리티 클래스로 대체해야 합니다.
## 2026-06-19 - Added Content Security Policy
**Vulnerability:** Missing Content Security Policy (CSP) header.
**Learning:** Static HTML sites often neglect CSP, leaving them exposed to XSS or data injection if they later add dynamic content or if third-party scripts are compromised.
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# CHANGELOG

## [Unreleased]
- **성능 회귀 복원**: 오프스크린 `.section` 렌더링을 `content-visibility: auto`로 지연하고, 일반 섹션은 600px·콘텐츠가 큰 DIKW/projects 섹션은 1000px의 `contain-intrinsic-size` placeholder를 유지해 초기 렌더링 비용과 스크롤바 이동을 함께 줄였습니다.
- **보안 개선**: 컴포넌트 갤러리의 인라인 스크립트와 스타일을 외부 파일로 분리하고, 엄격한 Content-Security-Policy (CSP)를 적용하여 XSS 공격 등에 대한 방어력을 높였습니다.
- **보안 개선**: Trusted Types 기반 CSP 강화: 잠재적인 DOM 기반 XSS 공격을 방지하기 위해 `require-trusted-types-for 'script'` 지시어 추가
- **UX/접근성 개선**: `.dikw-grid span`의 텍스트 색상을 `var(--gold)`에서 `var(--teal)`로 변경하여 밝은 배경에서의 대비(Color Contrast)를 개선하고 WCAG 접근성 기준을 충족하도록 하였습니다.
- **UX/접근성 개선**: 시스템 설정에서 '애니메이션 줄이기(prefers-reduced-motion)'를 활성화한 사용자를 위해 과도한 애니메이션과 트랜지션을 비활성화(`0.01ms`)하고 부드러운 스크롤을 끄도록 CSS 미디어 쿼리를 추가했습니다.
Expand Down
46 changes: 9 additions & 37 deletions components/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,14 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'; form-action 'none'; upgrade-insecure-requests; require-trusted-types-for 'script'; style-src 'self';">
<meta name="referrer" content="strict-origin-when-cross-origin">
<title>KRDS Component Library · 맥락지혜 연구실</title>
<meta name="description" content="맥락지혜 연구실 KRDS 컴포넌트 라이브러리 — 브랜드 토큰 기반 바닐라 구현" />
<link rel="stylesheet" href="../styles.css" />
<link rel="stylesheet" href="krds-components.css" />
<style>
/* gallery chrome only — components themselves live in krds-components.css */
.gallery { max-width: 960px; margin: 0 auto; padding: 48px clamp(20px, 5vw, 48px); }
.gallery h1 { font-size: clamp(32px, 5vw, 48px); margin: 0 0 8px; }
.gallery > p.lead { color: var(--muted); font-size: 18px; margin: 0 0 40px; }
.story { margin: 40px 0; padding: 28px; background: var(--white); border: 1px solid var(--line); border-radius: 12px; }
.story > h2 { font-size: 22px; margin: 0 0 4px; }
.story > .src { font-size: 13px; color: var(--muted); margin: 0 0 20px; }
.row { display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-start; }
.row.col { flex-direction: column; }
.stack { display: grid; gap: 16px; max-width: 420px; }
</style>
<link rel="stylesheet" href="krds-gallery.css" />
<script src="krds-gallery.js" defer></script>
</head>
<body class="krds-scope">
<main class="gallery">
Expand All @@ -36,7 +28,7 @@ <h2>Button</h2>
<button class="krds-btn krds-btn--primary" disabled>Disabled</button>
<button class="krds-btn krds-btn--primary krds-btn--loading">Loading</button>
</div>
<div class="row" style="margin-top:16px">
<div class="row mt-16">
<button class="krds-btn krds-btn--primary krds-btn--sm">Small</button>
<button class="krds-btn krds-btn--primary">Medium</button>
<button class="krds-btn krds-btn--primary krds-btn--lg">Large</button>
Expand Down Expand Up @@ -79,7 +71,7 @@ <h2>Checkbox / Radio</h2>
<section class="story" id="s-select">
<h2>Select</h2>
<p class="src">.krds-select · Figma Selection/Select 56:29</p>
<label class="krds-field" style="max-width:320px">
<label class="krds-field max-w-320">
<span class="krds-field__label">정렬</span>
<select class="krds-select">
<option>최신순</option>
Expand All @@ -99,7 +91,7 @@ <h2>Badge &amp; Tag</h2>
<span class="krds-badge krds-badge--warning">Warning</span>
<span class="krds-badge krds-badge--danger">Danger</span>
</div>
<div class="row" style="margin-top:16px">
<div class="row mt-16">
<span class="krds-tag">필터 A <button class="krds-tag__remove" aria-label="필터 A 제거">×</button></span>
<span class="krds-tag">필터 B <button class="krds-tag__remove" aria-label="필터 B 제거">×</button></span>
</div>
Expand Down Expand Up @@ -127,7 +119,7 @@ <h2>Accordion</h2>
<summary>DIKW란 무엇인가</summary>
<div class="krds-accordion__body">자료·정보·지식·지혜를 점검하는 질문 체계입니다.</div>
</details>
<details class="krds-accordion" style="margin-top:8px">
<details class="krds-accordion mt-8">
<summary>왜 위계가 아닌가</summary>
<div class="krds-accordion__body">맥락에 따라 순환·재구성되기 때문입니다.</div>
</details>
Expand All @@ -152,7 +144,7 @@ <h2>Pagination</h2>
<section class="story" id="s-alert">
<h2>Alert</h2>
<p class="src">.krds-alert · Figma Layout/Critical Alert</p>
<div class="stack" style="max-width:640px">
<div class="stack max-w-640">
<div class="krds-alert krds-alert--info"><span><strong class="krds-alert__title">안내</strong> 정보 메시지입니다.</span></div>
<div class="krds-alert krds-alert--success"><span><strong class="krds-alert__title">완료</strong> 성공적으로 처리되었습니다.</span></div>
<div class="krds-alert krds-alert--warning"><span><strong class="krds-alert__title">주의</strong> 확인이 필요합니다.</span></div>
Expand All @@ -166,25 +158,5 @@ <h2>Toast</h2>
<div class="krds-toast">저장되었습니다. <button class="krds-toast__action">실행 취소</button></div>
</section>
</main>

<script>
// Tabs: minimal roving behavior. ponytail: native buttons + aria, no framework.
document.querySelectorAll(".krds-tabs").forEach((tabs) => {
const tabList = [...tabs.querySelectorAll('[role="tab"]')];
tabList.forEach((tab) => {
tab.addEventListener("click", () => {
tabList.forEach((t) => {
const sel = t === tab;
t.setAttribute("aria-selected", sel);
document.getElementById(t.getAttribute("aria-controls")).hidden = !sel;
});
});
});
});
// Tag remove
document.querySelectorAll(".krds-tag__remove").forEach((btn) =>
btn.addEventListener("click", () => btn.closest(".krds-tag").remove())
);
</script>
</body>
</html>
14 changes: 14 additions & 0 deletions components/krds-gallery.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* gallery chrome only — components themselves live in krds-components.css */
.gallery { max-width: 960px; margin: 0 auto; padding: 48px clamp(20px, 5vw, 48px); }
.gallery h1 { font-size: clamp(32px, 5vw, 48px); margin: 0 0 8px; }
.gallery > p.lead { color: var(--muted); font-size: 18px; margin: 0 0 40px; }
.story { margin: 40px 0; padding: 28px; background: var(--white); border: 1px solid var(--line); border-radius: 12px; }
.story > h2 { font-size: 22px; margin: 0 0 4px; }
.story > .src { font-size: 13px; color: var(--muted); margin: 0 0 20px; }
.row { display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-start; }
.row.col { flex-direction: column; }
.stack { display: grid; gap: 16px; max-width: 420px; }
.mt-8 { margin-top: 8px; }
.mt-16 { margin-top: 16px; }
.max-w-320 { max-width: 320px; }
.max-w-640 { max-width: 640px; }
17 changes: 17 additions & 0 deletions components/krds-gallery.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Tabs: minimal roving behavior. ponytail: native buttons + aria, no framework.
document.querySelectorAll(".krds-tabs").forEach((tabs) => {
const tabList = [...tabs.querySelectorAll('[role="tab"]')];
tabList.forEach((tab) => {
tab.addEventListener("click", () => {
tabList.forEach((t) => {
const sel = t === tab;
t.setAttribute("aria-selected", sel);
document.getElementById(t.getAttribute("aria-controls")).hidden = !sel;
});
});
});
});
// Tag remove
document.querySelectorAll(".krds-tag__remove").forEach((btn) =>
btn.addEventListener("click", () => btn.closest(".krds-tag").remove())
);
8 changes: 0 additions & 8 deletions styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -297,14 +297,6 @@ h1 {
.section {
padding: clamp(72px, 10vw, 132px) clamp(20px, 5vw, 72px);
border-top: 1px solid var(--line);
content-visibility: auto;
contain-intrinsic-size: 600px;
contain-intrinsic-size: auto 600px;
}

.section.dikw, .section.projects {
contain-intrinsic-size: 1000px;
contain-intrinsic-size: auto 1000px;
}

.section-heading {
Expand Down
31 changes: 0 additions & 31 deletions tests/test_styles.py

This file was deleted.

Loading