Files
llm-wiki/raw/branch-notes/feature-frontend-browser-security-boundary-contract.md

346 lines
48 KiB
Markdown

---
title: branch / feature-frontend-browser-security-boundary-contract
source_type: branch-note
status: raw
id: BR-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-021
kind: project-work-item
project: ca-skeleton-frontend-operational-contract
work_item: WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-021
inherits: [DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-AUTH-BOUNDARY-001@1, DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-TELEMETRY-001@1]
refines: []
overrides: []
depends_on: [WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-008, WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-011, WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-012]
contract_packet: 1
branch: feature-frontend-browser-security-boundary-contract
parent_branch:
related_projects: [ca-skeleton-frontend, ca-skeleton]
governing_docs: [raw/project-notes/ca-skeleton-frontend-operational-contract]
tags: [branch, ca-skeleton, frontend, security, owasp, static-analysis]
created: 2026-07-18
target_merge:
status_label: in-progress
contract_packet_sha256: 67f39972a793315acd8de19be381ec163d5e8843a539a147103b74c6d4324274
imports: [FE-GATE-002@1, FE-GATE-006@1, FE-GATE-013@1, FE-GATE-019@2, FE-OC-004@1, FE-OC-008@1, FE-OC-010@1, FE-OC-013@1, FE-OC-014@1, FE-OC-016@1, FE-OC-018@1, FE-OC-020@1]
accepts_delegations: [DELEG-FE-001@1]
---
# branch: feature-frontend-browser-security-boundary-contract
> Layer: `raw/branch-notes/` — TODO·결정·진행 기록. 구현 결과는 검증 뒤 `/ingest`로만 추출한다.
<!-- section-id: branch-parent -->
## 부모 (필수)
[[raw/project-notes/ca-skeleton-frontend-operational-contract]]
<!-- GENERATED: branch-contract:start -->
<!-- section-id: branch-contract-packet -->
## 브랜치 계약 패킷
- **생성 시 프로젝트 개정**: `1`
- **패킷 스키마**: `contract_packet: 1`
- **완료 조건**: CSP·header·secret·storage·telemetry browser-boundary fixture가 통과한다
<!-- section-id: inherited-project-decisions -->
### 상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-AUTH-BOUNDARY-001@1` | auth lifecycle은 외부 owner가 소유하고 skeleton은 AuthSessionPort만 소비한다 | token·secret browser storage 금지 fixture에 적용 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] |
| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-TELEMETRY-001@1` | telemetry는 best-effort queue와 redaction을 사용하며 sink failure가 UI를 실패시키지 않는다 | telemetry forbidden-attribute leak fixture에 적용 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] |
<!-- section-id: branch-local-decisions -->
### 브랜치 지역 결정
> 기존 branch-local 결정은 아래 `## Decision Evidence Map / 결정-근거 매핑`의 D-row가 소유하며 이 packet에서 복제하지 않는다.
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|---|---|---|---|
<!-- section-id: declared-overrides -->
### 선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|---|---|---|---|
<!-- GENERATED: branch-contract:end -->
<!-- section-id: branch-goal -->
## 목표
- 이 branch는 project-wide contract `FE-OC-019`(browser bundle에 secret을 넣지 않고 untrusted HTML injection을 기본 금지)의 single owner로서, 이를 *되묻지 않아도 코드를 작성할 수 있는* implementation-ready spec으로 내린다. 근거는 hub [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §13.2(browser security boundary)·§6.1(secret 3-way 구분)·§13.1(secret scan)·§15.1(`FE-GATE-013` security gate)·§15.2(negative fixture)다.
- 동시에 `FE-OC-010`(auth session), `FE-OC-013`(storage), `FE-OC-014`(telemetry), `FE-OC-018`(build/bundle supply-chain), `FE-OC-020`(test taxonomy)에 **contribute**한다 — 각 registry의 *schema*는 그 owner branch가 갖고, 본 branch는 그 경계를 넘는 값(secret·token·untrusted HTML·PII)이 브라우저 표면(bundle·env·HTML·storage·telemetry)에 새지 않는지 검증하는 **cross-cutting security fixture와 injection/secret lint+scan**을 소유한다.
- 측정 가능한 완료 조건(§20): `CSP/header/secret/storage/telemetry browser-boundary fixtures`.
- 이슈: 없음 (스캐폴딩 단계)
- PR: 없음
<!-- section-id: branch-scope -->
## 범위
### 포함 범위
- browser bundle을 public artifact로 간주하고 **secret(client secret·private key·refresh token material)을 bundle·env·HTML에 넣지 않도록** 강제하는 계약 — env registry의 name-based 거부 + source/built-asset secret scan.
- **untrusted HTML injection과 dynamic code 실행 기본 금지** — `dangerouslySetInnerHTML`·`eval`·dynamic code(`new Function`)·untrusted script URL의 prohibited import/API lint rule. 불가피한 HTML rendering의 예외 조건(sanitizer owner·allowlist·malicious fixture·CSP interaction evidence) 명세.
- bundle이 `unsafe-inline`/`unsafe-eval` 없는 **strict CSP와 호환**되도록 하는 frontend 측 불변식(inline script·inline handler·eval 미의존) + 선언된 security header 정책의 verification fixture.
- production public path에 **source map 미배포** 기본 정책.
- 위 경계를 넘는 값을 잡는 **cross-cutting security fixture 집합**(secret / storage token-key / telemetry forbidden-attribute / HTML-injection)과 이를 `FE-GATE-013`으로 집계 + `FE-GATE-002`(lint)에 기여.
- **`FE-GATE-006`(component gate)의 `FE-OC-019` 슬라이스 fixture 본문 소유** — hub §15.1 이 component gate 의 Covered FE-OC 에 `FE-OC-019` 를 명시했으므로, render 시점에만 관측 가능한 injection 불변식(예외 sanitizer 경로의 malicious fixture + rendered subtree 의 prohibited-API 산출물 부재)은 본 branch 가 component-level fixture 로 소유한다(D8).
### 제외 범위
> 의도적으로 제외한 것. "이건 범위에 없었습니다"라고 답할 근거.
- **CSP/HSTS/frame/referrer header의 실제 directive 값(production)** — hosting/backend header owner 소유(hub §13.2). 본 branch는 값이 아니라 *호환성*만 본다. "선언 == 실제"의 **검증 위치**는 2026-07-21 에 `FE-GATE-019@2` 로 확정됐다 — D9 참조.
- **hosting header(`Cache-Control`·content-type·security header) 정책의 declared-vs-actual 검증** — release·cache 계약 [[raw/branch-notes/feature-frontend-release-cache-rollback-contract]] 소유(cache 축 `D1`, security-header 축 `D6`). hub §15.1 `FE-GATE-019@2` 가 Covered FE-OC 에 `FE-OC-019` 를 포함하도록 확장돼 security header 도 그 gate 범위다. 본 branch 는 검증 대상 header 정책을 공급한다.
- **storage key/version/classification registry schema** — [[raw/branch-notes/feature-frontend-storage-registry-contract]] `FE-OC-013` 소유. 본 branch는 token/secret 저장 시도가 실패하는 security fixture만 갖는다.
- **token lifecycle(발급·저장 위치·refresh·rotation·logout)** — 외부 auth owner + [[raw/branch-notes/feature-frontend-auth-session-integration-contract]] `FE-OC-010` 소유.
- **telemetry redaction allowlist와 transport-boundary 강제** — [[raw/branch-notes/feature-frontend-observability-logging-trace-contract]] `FE-OC-014` 소유. 본 branch는 forbidden-attribute leak fixture만 기여.
- **secret scanner/vulnerability scanner 도구 선택·severity threshold** — supply-chain 계약 [[raw/branch-notes/feature-frontend-build-bundle-supply-chain-contract]] `FE-OC-018`. hub §13.1에서 도구는 `deferred`.
- **error registry 구조·정규화** — [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]] `FE-OC-008`.
- **backend authorization·CORS enforcement** — 서버/브라우저 책임. route guard는 authorization control이 아니며(hub §13.2), client validation은 backend validation을 대체하지 않는다.
## 근거 (필수, 최소 1개+)
| Source | 정당화하는 결정 |
|---|---|
| [[raw/official-docs/owasp-html5-storage-xss-spa]] | D4 — 단일 XSS로 localStorage/sessionStorage 전체 탈취·주입 가능하므로 token/secret을 browser storage에 두지 않는다(`OWASP-HTML5-C1`~`C3`). |
| [[raw/official-docs/owasp-content-security-policy-cheat-sheet]] | D2·D3 — CSP는 server가 보내는 HTTP response header이고(`OWASP-CSP-C1`), `'unsafe-inline'`/`'unsafe-eval'`이 없으면 inline script·eval이 차단되므로(`OWASP-CSP-C2`·`C3`) bundle이 이를 미의존해야 strict CSP(second layer, `OWASP-CSP-C4`)를 적용할 수 있다. |
| [[raw/official-docs/owasp-hsts-cheat-sheet]] | D3 — HSTS 등 security response header는 response header owner(hosting)의 opt-in 결정이며(`OWASP-HSTS-C1`), frontend는 값이 아닌 호환성만 책임진다는 경계의 근거. |
| [[raw/official-docs/owasp-logging-cheat-sheet]] | D5 — 다른 trust zone에서 온 event data는 untrusted이며(`OWASP-LOG-C1`) sanitization으로 민감정보를 제거해야 한다(`OWASP-LOG-C3`)는 telemetry/error redaction fixture의 원칙 근거. |
| [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | D1·D6·D7 — secret 3-way 구분과 name-based 거부(§6.1), bundle=public artifact·source map 미배포(§13.2), secret scan(§13.1), security gate·negative fixture(§15.1·§15.2)의 project decision 근거. |
## TODO
- [ ] secret-exclusion: env registry의 name-based 거부(`SECRET`/`PASSWORD`/`PRIVATE_KEY`/`TOKEN`) + source·`dist/` built-asset secret scan fixture 정의 — 등급: `planned`
- [ ] injection-lint: `dangerouslySetInnerHTML`·`eval`·dynamic code·untrusted script URL 금지 rule + **금지 rule 1개당 1개**의 deliberately-failing negative fixture(총 3개, D10) — 등급: `planned`
- [ ] injection-render fixture(`FE-GATE-006`): 예외 sanitizer 경로 malicious fixture + rendered subtree 에 inline `<script>`/inline handler 부재 assertion(D8) — 등급: `planned`
- [ ] csp-compat: **no-unsafe 정본 test baseline**(D10) 하에서 bundle의 `unsafe-inline`/`unsafe-eval` 미의존 assertion + CSP violation 0 관측 — 등급: `planned`
- [ ] security-header declared-vs-actual: 선언 CSP/HSTS/frame/referrer 정책 == 실제 hosting 응답 verification. gate 귀속은 `FE-GATE-019@2` 로 확정됐고(D9), 본 branch 는 검증 대상 header 정책을 공급 — 등급: `planned`
- [ ] storage-boundary fixture: token/secret key 등록 시도가 실패함을 증명(§15.2 "storage: token key registration attempt") — 등급: `planned`
- [ ] telemetry-boundary fixture: forbidden attribute(raw URL/query/token 등) 전송 시도가 실패함을 증명(§15.2 "telemetry: event includes raw URL/query") — 등급: `planned`
- [ ] source-map policy: production public path에 source map 미배포 확인 fixture — 등급: `planned`
- [ ] gate wiring: 위 fixture를 `FE-GATE-013`(security)로 집계 + `FE-GATE-002`(lint) 기여 + `FE-GATE-006`(component)의 `FE-OC-019` 슬라이스 소유(D8) — 등급: `planned`
- [x] 상호 개정: test-taxonomy 계약의 `FE-GATE-006` row "Fixture 본문 owner" 에 `browser-security(FE-OC-019 슬라이스)` 추가 — **2026-07-21 완료**(D8)
## 진행 중 메모
- frontend code가 아직 없다(hub §1.2). 본 branch의 모든 항목은 `planned` blueprint이며, 경로·rule 이름 등 hub가 근거하지 않는 detail은 `UNSUPPORTED_IMPL_DECISION`으로 표시했다.
- 핵심 관점: 본 branch는 새 registry를 만들지 않고, 이미 owner가 있는 5개 표면(bundle·storage·telemetry·auth·supply-chain)의 *security 불변식*을 fixture로 집행하는 cross-cutting 계약이다. registry schema를 재정의하면 owner 경계를 침범한다(§15.5 R3).
- CSP directive 값은 배포 환경 header owner 소유 → 본 branch는 "bundle이 strict CSP를 깨지 않는가"만 검증한다.
## 결정 사항
> 대안과 함께 기록. 각 결정 근거는 위 Sources를 가리킨다. 모든 결정은 `planned`(코드 evidence 없음).
- 2026-07-19: **D1 secret은 browser 표면에 미포함** — bundle을 public artifact로 간주하고 obfuscation으로 secret을 보호할 수 있다고 가정하지 않는다. env registry가 `SECRET`/`PASSWORD`/`PRIVATE_KEY`/`TOKEN` 이름 key를 build·runtime 모두에서 거부하고, secret scan을 source와 built asset(`dist/`) 모두에 돌린다. / 이유: browser에 도달한 값은 복원 가능(hub §13.2)이므로 예방이 유일한 통제. / 대안: 값이 browser 가시이나 민감한 endpoint류는 `public-sensitive`로 분류(§5.4) — secret 아님. / 근거: hub §6.1·§13.1·§13.2.
- 2026-07-19: **D2 untrusted HTML/dynamic code 실행 기본 금지**`dangerouslySetInnerHTML`·`eval`·dynamic code(`new Function`)·untrusted script URL을 prohibited import/API lint rule로 막는다. 불가피한 HTML rendering은 sanitizer owner·allowlist·malicious fixture·CSP interaction evidence를 요구한다. / 이유: injection이 XSS의 1차 진입점이고, CSP는 second layer일 뿐 primary가 아니다(`OWASP-CSP-C4`). / 대안: 4종 evidence를 갖춘 예외 rendering 경로만 허용. / 근거: hub §13.2 + `OWASP-CSP-C2`·`C3`.
- 2026-07-19: **D3 strict-CSP 호환은 frontend, header 값은 header owner** — bundle과 그 의존성이 `'unsafe-inline'`/`'unsafe-eval'`을 요구하지 않도록 유지하고, 선언된 header 정책(CSP/HSTS/frame/referrer)이 실제 hosting 응답과 일치하는지 verification fixture로 확인한다. directive 값 자체는 header owner 소유. / 이유: CSP는 server response header이며(`OWASP-CSP-C1`) HSTS도 opt-in header 결정(`OWASP-HSTS-C1`)이라 값은 배포 계층 소유. / 대안: 불가피한 inline이 필요하면 nonce/hash는 header owner가 관리(본 branch 범위 밖). / 근거: hub §13.2 + `OWASP-CSP-C1`·`C4` + `OWASP-HSTS-C1`.
- 2026-07-19: **D4 token/secret은 browser storage 금지(contributes `FE-OC-013`)** — token/secret/PII의 browser storage 저장을 금지하고 token-key 등록 시도가 실패하는 security fixture를 소유한다. classification schema는 storage-registry branch 소유. / 이유: 단일 XSS로 storage 전체 탈취 가능(`OWASP-HTML5-C2`), storage 객체는 trusted가 아님(`OWASP-HTML5-C3`). / 대안: 외부 auth owner가 storage를 반드시 써야 하면 별도 threat model + owner evidence(§6.1) — skeleton default 아님. / 근거: hub §5.5·§13.2 + `OWASP-HTML5-C1`~`C3`.
- 2026-07-19: **D5 telemetry/error에 token·PII·raw payload 미포함(contributes `FE-OC-014`·`FE-OC-008`)** — forbidden attribute(token·email·raw URL/query/body·storage value·stack)가 telemetry event나 normalized failure에 새지 않는지 검사하는 security scan fixture를 소유한다. redaction allowlist와 transport-boundary 강제는 observability branch 소유. / 이유: 외부 trust zone data는 untrusted이며 민감정보는 제거해야 한다(`OWASP-LOG-C1`·`C3`). / 대안: N/A(항상 금지, 분기 없음). / 근거: hub §11.1·§5.8·§8.1 + `OWASP-LOG-C1`·`C3`.
- 2026-07-19: **D6 source map production 미배포** — production public path에 source map을 기본 배포하지 않는다. / 이유: source map은 최소화된 소스·주석·경로를 재노출해 secret/logic leak 표면을 넓힌다(D1과 연속). / 대안: 디버깅 필요 시 authenticated 경로 또는 error-tracking backend에만 업로드(공개 아님) — 예외 결정. / 근거: hub §13.2("source map은 production public path에 기본 배포하지 않는다").
- 2026-07-19: **D7 security gate 집계 + negative fixture 필수(owns `FE-OC-019`, contributes `FE-OC-020`)** — 5개 fixture family(secret·CSP/header·HTML-injection·storage·telemetry)를 `FE-GATE-013`으로 집계하고 각 family는 최소 1개 deliberately-failing negative fixture를 갖는다(rule 존재 확인만으로는 `locally-verified` 불가, §15.2). / 이유: hub 수용 질문 8 "위반 시 어떤 test가 실패하는가"에 답해야 `documented-only`를 넘는다(§2.2). / 대안: N/A. / 근거: hub §15.1·§15.2·§2.2.
- 2026-07-20: **D8 `FE-GATE-006`의 `FE-OC-019` 슬라이스는 본 branch가 소유(contributes `FE-OC-020`)** — hub §15.1의 component gate row가 Covered FE-OC에 `FE-OC-019`를 명시하므로, static lint(`FE-GATE-002`)로는 관측 불가능한 *render 시점* injection 불변식을 component-level fixture로 본 branch가 소유한다. 2종: (a) 예외 sanitizer rendering 경로의 **malicious fixture**(hub §13.2가 예외 승인 조건으로 요구하는 4종 evidence 중 하나), (b) 렌더된 subtree에 inline `<script>` 노드·inline event-handler attribute·`javascript:` URL이 존재하지 않음을 확인하는 assertion. / 이유: lint는 소스에 없는 sink(런타임 문자열 조립·서드파티 컴포넌트 경유)를 못 잡고, hub §15.2는 "rule 존재 확인"을 evidence로 인정하지 않는다. / 대안: component gate가 async/render/keyboard 전용이라 보고 위임 — 채택하지 않음. 위임하면 hub가 요구한 `FE-OC-019` 커버리지의 owner가 공백이 되고, 당시 test-taxonomy 계약의 `FE-GATE-006` row는 fixture 본문 owner로 `async-ui-state / render-recovery`만 등재해 security를 배제하고 있었고, 그대로 두면 이 슬라이스를 아무도 갖지 않게 된다. (2026-07-21 에 그 row 에 `browser-security(FE-OC-019 슬라이스)` 가 등재돼 해소됐다.) / 근거: hub §15.1(`FE-GATE-006` Covered FE-OC)·§13.2·§15.2.
- 2026-07-20: **D9 security header의 declared-vs-actual 검증의 gate 귀속** — 초판은 `FE-GATE-019`에 위임했으나 당시 그 row 는 pass condition 이 Cache-Control/content-type 으로 한정되고 Covered FE-OC 도 `FE-OC-016` 하나뿐이라 실제로는 어느 gate 에도 착지하지 않았다. 그래서 `FE-GATE-013`에 잠정 배치하고 hub 개정을 권고했다. / **2026-07-21 확정**: 권고한 두 안 중 (a)가 채택돼 hub §15.1 `FE-GATE-019`의 Covered FE-OC 에 `FE-OC-019`가 추가되고 pass condition 이 security header 까지 확장됐다(`FE-GATE-019@2`, Owner 는 release-cache). 근거: `FE-GATE-013`은 artifact 를 스캔하는 gate 이고 여기서 필요한 것은 실제 HTTP 응답의 declared-vs-actual 대조로 `FE-GATE-019`와 같은 메커니즘·같은 증거 형식이다. directive *값*은 여전히 header owner 소유. / 근거: hub §15.1(`FE-GATE-019@2` row)·§2.1.1·§13.2 + `OWASP-CSP-C1`·`OWASP-HSTS-C1`.
- 2026-07-20: **D10 CSP 호환 fixture는 no-unsafe 정본 test baseline에서 실행, negative fixture는 금지 rule 1개당 1개** — (a) production directive 값이 header owner 미확정이어도 test가 실행 가능하도록, `'unsafe-inline'`·`'unsafe-eval'`이 없는 **최소 test baseline CSP**를 본 branch가 정본으로 고정하고 compatibility fixture는 이 baseline 하에서 CSP violation 0을 관측한다(production 값과 별개의 test 전용 상수). (b) §2의 금지 API 3종은 서로 다른 rule이 잡으므로 family당 1개가 아니라 **rule당 1개**의 고의 실패 fixture를 둔다. / 이유: (a) 값이 위임되었다는 이유로 "CSP violation 0"을 측정 불가로 남기면 claim이 영구 `needs-confirmation`이 된다. (b) hub §15.2의 "gate당 최소 1개"는 하한이며, 1개만 두면 나머지 2개 rule은 존재만 확인된 상태 = §15.2가 evidence로 불인정하는 상태다. / 대안: (b) family당 1개로 축소 — fixture 3개 유지비는 줄지만 미검증 rule 2개가 남아 채택하지 않음. / 근거: hub §15.2·§13.2 + `OWASP-CSP-C2`·`OWASP-CSP-C3`.
## 결정-근거 매핑
> `Decision ID`는 이 branch-note 안에서 안정적으로 유지한다. `Supporting Claims`는 official-doc의 Claim ID 또는 hub의 §/`FE-OC`/`FE-D` reference.
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | secret은 bundle·env·HTML에 미포함; env registry name-based 거부 + source/`dist/` secret scan (`FE-OC-019`, contributes `FE-OC-018`) | client-only public bundle인 한 항상 예방 통제 / 값이 browser 가시이나 민감한 endpoint류면 secret이 아니라 `public-sensitive` 분류(§5.4)로 다룸 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §6.1(name reject)·§13.2(public artifact)·§13.1(secret scan) · FE-D024 | `project-decision` | scanner 도구·threshold가 `deferred`(§13.1)라 false-negative 가능성 미검증; 값 분류(`public-sensitive` vs secret) 경계 판정 |
| D2 | untrusted HTML/dynamic code 실행 기본 금지 (`dangerouslySetInnerHTML`·`eval`·`new Function`·untrusted script URL) | default는 항상 금지 / 불가피한 HTML rendering은 sanitizer owner+allowlist+malicious fixture+CSP interaction evidence 4종을 갖춘 예외 경로만 허용(hub §13.2) | [[raw/official-docs/owasp-content-security-policy-cheat-sheet]] OWASP-CSP-C2·OWASP-CSP-C3 · [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §13.2 | `official-doc + project-decision` | lint rule/plugin이 dynamic sink(문자열 template→DOM)를 실제로 포착하는지 미검증; 예외 rendering 경로 발생 시 4종 evidence 강제 누락 위험 |
| D3 | bundle의 strict-CSP 호환(`unsafe-inline`/`unsafe-eval` 미의존) + header 정책 verification; directive 값은 header owner 소유 | frontend는 항상 no-unsafe 유지 / 불가피한 inline 필요 시 nonce/hash는 hosting header owner가 관리(본 branch 범위 밖) | [[raw/official-docs/owasp-content-security-policy-cheat-sheet]] OWASP-CSP-C1·OWASP-CSP-C4 · [[raw/official-docs/owasp-hsts-cheat-sheet]] OWASP-HSTS-C1 · [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §13.2 | `official-doc + project-decision` | production directive 값은 header owner 의존(테스트는 D10의 no-unsafe baseline으로 분리); 선언-vs-실제 검증 gate 귀속은 `FE-GATE-019@2` 로 확정(D9); 의존성 중 eval 사용 lib이 CSP를 깰 위험 |
| D4 | token/secret/PII의 browser storage 저장 금지 + token-key 등록 실패 fixture (contributes `FE-OC-013`·`FE-OC-010`) | skeleton default는 항상 금지 / 외부 auth owner가 storage를 반드시 써야 하면 별도 threat model + owner evidence(§6.1)일 때만 예외 | [[raw/official-docs/owasp-html5-storage-xss-spa]] OWASP-HTML5-C1·OWASP-HTML5-C2·OWASP-HTML5-C3 · [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §5.5·§13.2 | `official-doc + project-decision` | classification schema는 storage-registry 소유 → fixture 중복/누락 조율 필요(공동 집행 경계) |
| D5 | telemetry/normalized failure에 token·PII·raw URL/query/body·storage value·stack 미포함 fixture (contributes `FE-OC-014`·`FE-OC-008`) | 분기 없음 — 항상 forbidden. 신규 attribute는 low-cardinality+non-PII 검토 통과 시에만 registry 추가(observability 소유) | [[raw/official-docs/owasp-logging-cheat-sheet]] OWASP-LOG-C1·OWASP-LOG-C3 · [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §11.1·§5.8·§8.1 | `official-doc + project-decision` | redaction 강제는 transport boundary(observability adapter)에서 일어남 → 본 branch fixture는 leak 관측만, 강제 위치는 위임 |
| D6 | production public path에 source map 미배포 | 기본 미배포 / 디버깅 필요 시 authenticated 경로·error-tracking backend 업로드(공개 아님)만 예외 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §13.2(source map 미배포) | `project-decision` | build 도구 flag로 강제하는 구체 메커니즘 미명세(§구현 가이드 5 UNSUPPORTED_IMPL) |
| D7 | 5개 security fixture family를 `FE-GATE-013`으로 집계 + 각 family 최소 1 negative fixture (owns `FE-OC-019`, contributes `FE-OC-020`) | 분기 없음 — negative fixture 없는 rule은 evidence로 불인정(§15.2) | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §15.1(`FE-GATE-013`)·§15.2·§2.2(질문 8) | `project-decision` | test taxonomy/artifact retention은 `FE-OC-020` 소유 → gate 배선은 test branch와 조율 |
| D8 | `FE-GATE-006`(component)의 `FE-OC-019` 슬라이스 fixture 본문 소유 — 예외 sanitizer 경로 malicious fixture + rendered subtree의 inline `<script>`/inline handler/`javascript:` URL 부재 assertion (owns `FE-OC-019`, contributes `FE-OC-020`) | 분기 없음 — hub §15.1이 component gate의 Covered FE-OC에 `FE-OC-019`를 명시하는 한 본 branch 소유 / 위임하려면 hub §15.1에서 `FE-GATE-006``FE-OC-019` 커버리지를 제거하는 개정이 선행돼야 함 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §15.1(`FE-GATE-006` Covered FE-OC = `FE-OC-019` 포함)·§13.2(prohibited API)·§15.2(negative fixture 요건) | `project-decision` | test-taxonomy 계약의 `FE-GATE-006` row 에 `browser-security(FE-OC-019 슬라이스)` 가 2026-07-21 에 등재돼 두 노트의 owner 표기가 일치한다. 이후 그 row 가 다시 바뀌면 여기도 함께 갱신해야 한다 |
| D9 | security header(CSP/HSTS/frame/referrer)의 declared-vs-actual 검증은 `FE-GATE-019@2` 소유이고, 본 branch 는 검증 대상 header 정책을 공급 (`FE-OC-019`) | hub §15.1 `FE-GATE-019@2` 가 Covered FE-OC 에 `FE-OC-019` 를 포함하는 한 이 배치 유지 / hub 가 그 범위를 되돌리면 gate 귀속 재확정 필요 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §15.1(`FE-GATE-019@2` — Covered FE-OC 에 `FE-OC-019` 포함, pass condition 이 security header 포함)·§2.1.1(revision 2)·§13.2 · [[raw/official-docs/owasp-content-security-policy-cheat-sheet]] OWASP-CSP-C1 · [[raw/official-docs/owasp-hsts-cheat-sheet]] OWASP-HSTS-C1 | `project-decision + official-doc` | gate 는 release-cache 소유이므로 fixture 실행 시점·artifact 형식은 그 branch 와 맞춰야 함 |
| D10 | CSP compatibility fixture는 본 branch가 고정한 **no-unsafe test baseline CSP** 하에서 실행; 금지 API는 family당이 아니라 **rule당 1개**의 고의 실패 fixture (`FE-OC-019`, contributes `FE-OC-020`) | 분기 없음 — production 값 확정 여부와 무관하게 test는 baseline으로 실행 / production 값이 확정되면 baseline은 유지하고 실제 값 대조는 D9 fixture가 별도 담당 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §15.2("rule 존재만 확인한 결과는 `locally-verified` 증거로 부족")·§13.2 · [[raw/official-docs/owasp-content-security-policy-cheat-sheet]] OWASP-CSP-C2·OWASP-CSP-C3 | `project-decision + official-doc` | baseline directive 집합 자체는 hub 미명명(§구현 가이드 3 `UNSUPPORTED_IMPL_DECISION`); baseline이 production 값보다 느슨하면 통과해도 실제 환경에서 깨질 수 있음 |
## 구현 가이드
> `planned` blueprint. 경로는 hub §4.6(Planned directory blueprint)·§5.1(registry owner map)에서 인용했으나 repository가 아직 없어 전체가 `planned`다. 3-rule(R1 Trace / R2 UNSUPPORTED_IMPL / R3 no OUT_OF_BRANCH_SCOPE) 준수.
### 1. Secret 배제 강제 (bundle·env·HTML)
> **Trace**: D1 — `FE-OC-019` + hub §6.1·§13.1·§13.2. env name-based 거부 규칙과 secret scan을 결합한다.
>
> - **UNSUPPORTED_IMPL_DECISION**: secret scanner 도구명·정규식 패턴·severity threshold는 hub §13.1에서 `deferred` → 미명세. trade-off: 도구를 지금 고정하면 supply-chain branch(`FE-OC-018`)의 도구 선택과 충돌 → 도구 중립적으로 "source+built asset 스캔이 credential 패턴에 실패"라는 *계약*만 고정.
| 강제 지점 | 규칙 | 근거 |
|---|---|---|
| env registry 등록 시 | key 이름에 `SECRET`/`PASSWORD`/`PRIVATE_KEY`/`TOKEN` 포함 → build·runtime 모두 거부 | hub §6.1 |
| build-time public | `BUILD_ID`·`COMMIT_SHA`·`ROUTER_BASE_PATH` 등 compiler/asset identity 값만 `VITE_*` 허용 | hub §6.1·§5.4 |
| secret scan 대상 | source tree + `dist/`(built asset) 모두 | hub §13.1(secret scan: source + built asset) |
| 값 분류 | browser 가시이나 민감한 endpoint류(`API_BASE_URL`·`TELEMETRY_ENDPOINT`)는 `public-sensitive` — 로그·telemetry에 원문 미기록, secret 아님 | hub §5.4 |
### 2. dynamic code 금지 lint
> **Trace**: D2·D10 — `FE-OC-019` + hub §13.2 + §15.2 + `OWASP-CSP-C2`·`OWASP-CSP-C3`. prohibited import/API 카탈로그 + 예외 경로 조건 + fixture 단위.
>
> - **UNSUPPORTED_IMPL_DECISION**: 구체 lint rule id/plugin(예: ESLint `react/no-danger`, `no-eval`, custom no-restricted-syntax)·`FE-GATE-002` 배선 형식은 hub가 명명하지 않음. trade-off: rule id를 지금 못박으면 test-taxonomy(`FE-OC-020`)의 lint 도구 선택을 침범 → "이 API/import가 금지되고 negative fixture가 실패한다"는 계약만 고정.
> - **UNSUPPORTED_IMPL_DECISION**: fixture 단위를 "family당 1개"가 아니라 **"금지 rule당 1개"**로 강화(D10 (b))한 것은 hub 미명시 — hub §15.2는 *gate당* 최소 1개만 요구한다. trade-off: fixture 3개는 유지비가 늘지만, 1개만 두면 나머지 2개 rule은 "존재만 확인"된 상태로 남아 §15.2가 evidence로 불인정하는 구간에 들어간다 → 유지비를 택함.
**fixture 단위 답 (D10 (b))**: family당 1개로는 부족하다. 아래 3행은 각각 *다른 lint rule*이 잡으므로 **행당 1개씩, 총 3개의 고의 실패 negative fixture**를 둔다.
| 금지 대상 | 성격 | 전용 negative fixture(고의 실패) | 예외 조건 |
|---|---|---|---|
| `dangerouslySetInnerHTML` | prohibited API (default) | `presentation`이 untrusted 문자열을 `dangerouslySetInnerHTML`로 렌더 → lint 실패 | sanitizer owner + allowlist + malicious fixture + CSP interaction evidence 4종 |
| `eval` / `new Function` / dynamic code | prohibited (default) | 모듈이 문자열을 `eval`/`new Function`으로 실행 → lint 실패 | 예외 없음(skeleton) |
| untrusted script URL 주입 | prohibited (default) | 런타임 값으로 `<script src>`/`javascript:` URL 조립 → lint 실패 | 예외 없음(skeleton) |
- 3개 fixture 모두 `FE-GATE-002`(lint) 기여 → `FE-GATE-013` 집계. static lint로 관측 불가능한 *render 시점* 위반은 §6(`FE-GATE-006`)이 담당한다.
### 3. Strict-CSP 호환 + security header verification (production 값만 위임)
> **Trace**: D3·D9·D10 — `FE-OC-019` + hub §13.2 + §15.1(`FE-GATE-013`·`FE-GATE-019` row) + `OWASP-CSP-C1`·`OWASP-CSP-C4`·`OWASP-HSTS-C1`.
>
> - **R3 OUT_OF_BRANCH_SCOPE**: CSP/HSTS/frame/referrer의 **production directive 값**·max-age·preload는 hosting/backend header owner 소유 → 여기 명세하지 않는다.
> - **범위 정정(D9) — 2026-07-21 확정**: 이전 판은 이 검증을 `FE-GATE-013`(security)에 *잠정* 배치했다. 그런데 `FE-GATE-013` 은 artifact 를 스캔하는 gate(secret·vulnerability·license·dependency review)이고, 여기서 필요한 것은 **실제 HTTP 응답의 declared-vs-actual 대조**로 `FE-GATE-019`(hosting header)와 같은 메커니즘·같은 증거 형식이다. 그래서 hub §15.1 `FE-GATE-019` 의 Covered FE-OC 에 `FE-OC-019` 를 추가하고 pass condition 을 security header 까지 넓혔다(`FE-GATE-019@2`, Owner 는 그대로 [[raw/branch-notes/feature-frontend-release-cache-rollback-contract]]). 본 branch 는 gate 를 소유하지 않고 **검증 대상 header 정책을 공급**한다.
> - **UNSUPPORTED_IMPL_DECISION**: 아래 no-unsafe test baseline의 구체 directive 집합은 hub가 명명하지 않음(D10 (a)). trade-off: baseline을 느슨하게 잡으면 통과해도 실제 production 정책에서 깨지고, 과도하게 조이면 존재하지 않는 위반으로 개발을 막는다 → `'unsafe-inline'`/`'unsafe-eval'` 부재라는 *불변식*을 만족하는 최소 집합으로 잡고, production 값 확정 시 대조는 D9 fixture가 별도 담당.
**no-unsafe 정본 test baseline (D10 (a))** — production 값과 무관하게 compatibility fixture가 실행되는 test 전용 상수. `'unsafe-inline'`·`'unsafe-eval'` 미포함이 이 baseline의 불변식이다.
```text
default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:;
connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'
```
| frontend가 소유(assert) | header owner가 소유(값만 위임) |
|---|---|
| bundle·의존성이 `unsafe-inline`/`unsafe-eval` 미의존(inline `<script>`·inline handler·`eval` 없음) | `Content-Security-Policy` production directive 집합 값 |
| 위 baseline 하 sample route에서 CSP violation 0 관측(compatibility fixture) — hosting owner 확정 전에도 실행 가능 | HSTS `max-age`·`includeSubDomains`·`preload` 채택 여부 |
| 선언된 security header 정책 == 실제 hosting 응답인지 verification(`pnpm verify:hosting-headers`(security-header 축)) — `FE-GATE-019@2` 에 배치 확정(2026-07-21) | frame policy·referrer policy 값 |
**hub 개정 (D9) — 반영 완료(2026-07-21)**: 권고했던 두 안 중 (a)가 채택됐다. hub §15.1 `FE-GATE-019` 의 Covered FE-OC 에 `FE-OC-019` 가 추가되고 pass condition 이 security header 까지 확장됐으며, hub §2.1.1 의 `FE-GATE-019` revision 이 2 로 올라갔다. 이 gate 를 pin 한 문서는 revision 이 낡아 자동으로 잡힌다.
### 4. Cross-cutting security fixture + gate 집계 (storage·telemetry)
> **Trace**: D4·D5·D7·D8·D9·D10 — `FE-OC-019`(owns) + contributes `FE-OC-013`·`FE-OC-014`·`FE-OC-020` + hub §5.5·§11.1·§5.8·§15.1·§15.2.
>
> - **R3 OUT_OF_BRANCH_SCOPE**: storage classification schema는 [[raw/branch-notes/feature-frontend-storage-registry-contract]] `FE-OC-013`, redaction allowlist는 [[raw/branch-notes/feature-frontend-observability-logging-trace-contract]] `FE-OC-014` 소유. 본 표는 *security 위반 관측 fixture*만 소유한다.
| Fixture family | Negative fixture(고의 실패) | 기대 결과 | 집계 gate | schema owner(위임) |
|---|---|---|---|---|
| secret | env에 `*_TOKEN` key 등록 / `dist/`에 credential 패턴 | 거부·scan 실패 | `FE-GATE-013` | env(`FE-OC-004`)·supply-chain(`FE-OC-018`) |
| HTML-injection (static, rule당 1개 = 3개) | ①`dangerouslySetInnerHTML` 렌더 ②`eval`/`new Function` ③런타임 script URL 조립 | 각 lint rule 실패 | `FE-GATE-002``FE-GATE-013` | 본 branch (D10 (b)) |
| HTML-injection (render 시점) | 예외 sanitizer 경로에 malicious payload 주입 / subtree에 inline `<script>`·inline handler 존재 | component test 실패 | `FE-GATE-006` | 본 branch (D8) |
| CSP 호환 | no-unsafe baseline(§3) 하 sample route 렌더 시 CSP violation 발생 | compatibility fixture 실패 | `FE-GATE-013` | 본 branch (D10 (a)) |
| security header 선언-vs-실제 | 선언 CSP/HSTS/frame/referrer 정책 != 실제 hosting 응답 | `pnpm verify:hosting-headers`(security-header 축) 실패 | `FE-GATE-019@2` (Owner = release-cache) | 값만 header owner, 정책 공급은 본 branch(D9) |
| storage | token/secret key 등록 시도 | 등록 거부 | `FE-GATE-013` | storage(`FE-OC-013`) |
| telemetry | event에 raw URL/query/token 포함 | 전송 거부·scan 실패 | `FE-GATE-013` | observability(`FE-OC-014`) |
### 5. Source map production 정책
> **Trace**: D6 — `FE-OC-019` + hub §13.2.
>
> - **UNSUPPORTED_IMPL_DECISION**: 강제 메커니즘(예: Vite `build.sourcemap=false` vs post-build strip vs authenticated 경로 업로드)을 hub가 명명하지 않음. trade-off: `build.sourcemap=false`가 가장 단순하나 error-tracking symbolication을 포기 → 미결. "production public path에 `.map`이 존재하지 않는다"는 fixture 계약만 고정.
- fixture: production build 산출물의 public path에 `*.map`이 노출되지 않음.
### 6. `FE-GATE-006`(component gate)의 `FE-OC-019` 슬라이스
> **Trace**: D8 — `FE-OC-019`(owns) + contributes `FE-OC-020` + hub §15.1(`FE-GATE-006` Covered FE-OC에 `FE-OC-019` 포함)·§13.2(prohibited API)·§15.2(negative fixture 요건).
>
> - **소유 근거**: hub §15.1이 component gate의 Covered FE-OC로 `FE-OC-019`를 명시하므로 이 커버리지에는 owner가 있어야 한다. static lint(§2)는 *소스에 나타난* prohibited API만 잡고, 런타임 문자열 조립·서드파티 컴포넌트 경유로 생기는 sink는 렌더 결과에서만 관측된다 → component-level fixture가 필요하다.
> - **UNSUPPORTED_IMPL_DECISION**: component test runner·assertion helper 형태(예: RTL `container.querySelector` 기반 subtree 검사)는 hub 미명명이며 test stack은 test-taxonomy(`FE-OC-020`) 소유. trade-off: helper를 지금 고정하면 그 branch의 도구 선택을 침범 → "렌더된 subtree에 금지 산출물이 없어야 하고, malicious payload는 fixture를 실패시킨다"는 계약만 고정.
| Component fixture | 대상 | 기대 결과 |
|---|---|---|
| malicious payload (positive-guard) | hub §13.2 예외 조건으로 승인된 sanitizer rendering 경로 | 알려진 XSS payload가 실행 가능한 노드로 남지 않음. sanitizer 우회 시 fixture 실패 |
| prohibited 산출물 부재 assertion | sample route/컴포넌트의 렌더된 subtree | inline `<script>` 노드·inline event-handler attribute·`javascript:` URL 0건 |
- 예외 rendering 경로가 하나도 없는 skeleton 초기 상태에서는 첫 fixture가 "예외 경로 부재"를 확인하는 형태로 축약될 수 있으나, 예외가 승인되는 즉시 malicious fixture는 hub §13.2의 4종 evidence 요건상 필수다.
- **상호 개정 완료(2026-07-21)**: [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] (`FE-OC-020`)의 `FE-GATE-006` row 는 fixture 본문 owner 를 `async-ui-state / render-recovery`로만 등재하고 있었다. 그 열(gate → fixture 본문 owner)의 owner 는 test-taxonomy 이므로 그쪽 표에 `browser-security(FE-OC-019 슬라이스)` 를 추가했다.
## 엣지·실패·의존
- **실패·엣지 경로**:
- **예외 HTML rendering**: 불가피한 HTML 렌더가 필요할 때 sanitizer/allowlist/malicious fixture/CSP evidence 4종 중 하나라도 빠지면 → 예외 승인 거부(default 금지 유지). sanitizer 자체가 우회되면 malicious fixture가 실패로 잡아야 함.
- **secret scan false-negative**: 도구·패턴이 `deferred`(§13.1)라 새 credential 형태를 못 잡을 수 있음 → 기대 동작: 도구 확정 시 known-secret 양성 fixture로 탐지율 검증.
- **CSP runtime 위반**: 의존성 lib이 `eval`을 쓰면 strict CSP에서 런타임 깨짐 → 기대 동작: compatibility fixture가 CSP violation을 관측해 실패.
- **storage fallback 노출**: quota 초과·private mode에서 값이 memory-only로 fallback될 때도 sensitive 값은 애초에 storage 대상이 아니어야 함(D4) → fallback이 sensitive 값을 노출하지 않음.
- **telemetry 신규 attribute leak**: registry에 새 attribute 추가 시 PII/token이 섞이면 → forbidden-attribute scan fixture가 실패로 잡음.
- **다른 계약 의존** (sibling의 local Decision ID + contract ID로 링크):
- [[raw/branch-notes/feature-frontend-auth-session-integration-contract]] `D6`(`FE-OC-010`) — token 저장 금지·`AUTH_TOKEN` forbidden을 이미 결정. 본 branch는 그 위반을 security fixture로 관측(공동 집행). 그 계약이 바뀌면 storage/telemetry fixture 조정.
- [[raw/branch-notes/feature-frontend-storage-registry-contract]] `D6`(`FE-OC-013`) — `sensitive-forbidden` classification schema 소유. 본 branch는 token-key 등록 실패 fixture만 제공.
- [[raw/branch-notes/feature-frontend-observability-logging-trace-contract]] `D2`(`FE-OC-014`) — redaction allowlist·transport-boundary 강제 소유. 본 branch는 forbidden-attribute leak fixture 기여.
- [[raw/branch-notes/feature-frontend-build-bundle-supply-chain-contract]] `D5`·`D6`(`FE-OC-018`) — `D5`가 security gate 묶음(secret+vuln+license+dependency review)이되 scanner·severity threshold는 `deferred`(hub §13.1)로 고정, `D6`이 secret scan을 source + built asset 양쪽으로 확정. 도구 자체는 **아직 pinned decision 없음(hub §13.1 `deferred`)** → 본 branch의 secret scan은 도구 중립 계약만.
- [[raw/branch-notes/feature-frontend-release-cache-rollback-contract]] `D1`·`D6`(`FE-OC-016`) — `D1` 이 surface별 cache policy, `D6``FE-GATE-019@2` 의 security-header 축 검증 메커니즘 소유. 그 gate 는 2026-07-21 에 security header 까지 범위가 넓어졌으므로(Covered FE-OC 에 `FE-OC-019` 포함) security header 검증도 그쪽 소유이고, 본 branch 는 검증 대상 정책을 공급한다(D9). production directive **값**에 대해서는 hosting provider 미확정으로 **pinned decision 없음**.
- [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] `D6`·`D3`(`FE-OC-020`) — `D6`이 gate → test level / fixture KIND taxonomy 소유(gate→FE-OC coverage 매핑 자체는 hub §15.1 소유), `D3`이 gate당 ≥1 고의 실패 negative fixture 원칙 소유. 본 branch의 security fixture는 그 taxonomy에 plug-in하고 어느 표도 복제·재정의하지 않는다. `FE-GATE-006` fixture 본문 owner 목록은 그쪽 `D6` 소관이며 2026-07-21 에 `browser-security` 가 추가됐다.
- [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]] `D2`(`FE-OC-008`) — normalized failure가 §8.1 safe 필드만 담고 raw body·token·authorization header·full URL/query·stack·storage value를 drop하도록 소유. 본 branch의 telemetry leak fixture와 경계 공유.
## 검증해야 할 주장
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| env·bundle·HTML 어디에도 secret이 새지 않는다 | 코드·CI 없음; scanner 도구 `deferred` | env name-reject unit fixture + source/`dist/` secret scan에 known-secret 양성 fixture 삽입 후 실패 확인 | `needs-confirmation` |
| `dangerouslySetInnerHTML`·`eval`·dynamic code가 CI에서 차단된다 | lint rule/plugin 미확정 | 금지 rule **3종 각각**의 negative fixture(고의 위반)가 대응 lint rule 실패로 잡히는지 실행(D10 (b)) | `needs-confirmation` |
| bundle이 `unsafe-inline`/`unsafe-eval` 없는 strict CSP에서 동작한다 | 의존성 중 eval 사용 lib 여부 미확인 | §3의 **no-unsafe 정본 test baseline** 하 sample route e2e에서 CSP violation 0 관측 — hosting owner의 production 값 확정을 기다리지 않고 실행 가능(D10 (a)) | `needs-confirmation` |
| 렌더된 subtree에 inline `<script>`/inline handler/`javascript:` URL이 없고 sanitizer 예외 경로가 malicious payload를 실행하지 않는다 | component test stack 미확정, 예외 경로 미존재 | `FE-GATE-006` component fixture 2종(§6) 실행 → malicious payload 실패·prohibited 산출물 0건 확인(D8) | `needs-confirmation` |
| 선언된 security header 정책이 실제 hosting 응답과 일치한다 | header 값은 외부 owner이고 실제 응답이 아직 미측정 (gate 귀속은 `FE-GATE-019@2` 로 확정 — D9) | `pnpm verify:hosting-headers`(security-header 축)로 HTML/config/manifest 응답의 CSP/HSTS/frame/referrer 대조 → `FE-GATE-019@2` | `planned` |
| token/secret key 등록 시도가 실패한다 | storage registry 구현 없음 | storage token-key 등록 negative fixture(§15.2) 실행 → 거부 확인 | `needs-confirmation` |
| telemetry event/normalized failure에 forbidden attribute가 없다 | redaction 강제 위치는 observability adapter | forbidden-attribute(raw URL/query/token) 포함 event negative fixture(§15.2) → 전송/scan 실패 확인 | `needs-confirmation` |
| production public path에 source map이 없다 | build 미실행 | production build 후 public path에 `*.map` 부재 fixture | `needs-confirmation` |
## 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)
- 스캐폴딩 단계: `/coverage` 실행 전 수동 행을 만들지 않는다.
## 마주친 문제
- **위임처 gate 범위 과대 가정(해소 완료)** — 초판은 security header의 declared-vs-actual 검증을 `FE-GATE-019`에 위임했으나, 당시 hub §15.1의 해당 row는 Cache-Control/content-type 전용이고 Covered FE-OC도 `FE-OC-016` 하나뿐이었다. 위임처 노트 본문에도 CSP/HSTS 언급이 0건이라 실제로는 어느 gate에도 착지하지 않는 상태였다. D9로 `FE-GATE-013`에 잠정 배치한 뒤 hub 개정을 권고했고, **2026-07-21 에 그 권고가 채택돼 `FE-GATE-019@2` 로 확정됐다**.
- **`FE-GATE-006``FE-OC-019` 커버리지 무주공산(해소 완료)** — hub §15.1은 component gate가 `FE-OC-019`를 덮도록 요구하지만, 초판은 이를 TODO의 "기여" 한 줄로만 언급하고 Decision·fixture를 두지 않았다. 위임 후보인 test-taxonomy 계약의 `FE-GATE-006` row도 fixture 본문 owner에 security를 넣지 않아 owner가 공백이었다. D8 + §구현 가이드 6으로 본 branch가 소유를 확정했고, **2026-07-21 에 test-taxonomy 의 해당 row 도 갱신됐다**.
- 교훈: 위임 문장을 쓸 때 위임처 *노트*의 존재만이 아니라 hub gate row의 **pass condition과 Covered FE-OC 문자열**까지 확인해야 한다. gate 이름이 그럴듯하다고 범위가 넓은 것은 아니다.
## 묶음 (이 branch에서 파생된 자료)
<!-- GENERATED: received-delegations:start -->
### 수신한 위임
| Delegation Ref | From | Concern | Status |
|---|---|---|---|
| `DELEG-FE-001@1` | [[raw/branch-notes/feature-tailwind-design-token-styling-contract]] | `fe.deleg.dynamic-class-lint` | accepted |
<!-- GENERATED: received-delegations:end -->
<!-- GENERATED: project-contract-imports:start -->
## 가져온 프로젝트 계약
| Ref | Owner | 요약 | Branch 적용 |
|---|---|---|---|
| `FE-GATE-002@1` | [[raw/branch-notes/feature-frontend-architecture-enforcement-lint-contract]] | 금지된 API·import 가 남아 있으면 merge 를 MUST 차단 | import 참조로 적용 |
| `FE-GATE-006@1` | [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] | component 레벨이 실패하면 merge 를 MUST 차단 | import 참조로 적용 |
| `FE-GATE-013@1` | [[raw/branch-notes/feature-frontend-build-bundle-supply-chain-contract]] | secret·vulnerability·license·dependency review 정책 위반이면 merge·release 를 MUST 차단 | import 참조로 적용 |
| `FE-GATE-019@2` | [[raw/branch-notes/feature-frontend-release-cache-rollback-contract]] | 선언한 Cache-Control·content-type·security header 와 실제 응답이 다르면 release 를 MUST 차단 | import 참조로 적용 |
| `FE-OC-004@1` | [[raw/branch-notes/feature-frontend-env-runtime-config-contract]] | build-time, runtime-public, secret config를 MUST 분리하고 boot 전에 runtime config를 검증 | import 참조로 적용 |
| `FE-OC-008@1` | [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]] | 모든 failure는 stable frontend error kind로 MUST 정규화하고 raw body·stack을 UI에 노출하면 안 됨 | import 참조로 적용 |
| `FE-OC-010@1` | [[raw/branch-notes/feature-frontend-auth-session-integration-contract]] | skeleton은 session state를 소비하되 token lifecycle을 MUST 소유하지 않음 | import 참조로 적용 |
| `FE-OC-013@1` | [[raw/branch-notes/feature-frontend-storage-registry-contract]] | storage key는 namespace·version·classification을 MUST 가지며 token/secret 저장을 금지 | import 참조로 적용 |
| `FE-OC-014@1` | [[raw/branch-notes/feature-frontend-observability-logging-trace-contract]] | telemetry는 best-effort이며 render·API success를 차단하면 안 되고 PII·token을 전송하면 안 됨 | import 참조로 적용 |
| `FE-OC-016@1` | [[raw/branch-notes/feature-frontend-release-cache-rollback-contract]] | HTML, asset, runtime config, release manifest cache policy를 MUST 구분 | import 참조로 적용 |
| `FE-OC-018@1` | [[raw/branch-notes/feature-frontend-build-bundle-supply-chain-contract]] | frozen lockfile, dependency review, secret scan, SBOM 또는 dependency inventory를 release gate에 MUST 포함 | import 참조로 적용 |
| `FE-OC-020@1` | [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] | gate 종류별 책임·fixture·artifact를 분리하고 실패를 warning으로 낮추면 안 됨 | import 참조로 적용 |
<!-- GENERATED: project-contract-imports:end -->
<!-- GENERATED: sources:start -->
- [[raw/official-docs/owasp-content-security-policy-cheat-sheet]]
<!-- GENERATED: sources:end -->
- 없음 — 자식 자료는 생성 후 controller가 parent Cluster와 함께 등록한다.
## 관련 일일 노트
- 없음 — daily note는 이 작업에서 수정하지 않는다.
## 완료 후 정리
- PR 링크: 없음
- 리뷰 메모: 없음
- 머지 결과 / 배포 환경: `planned`
- **wiki 추출 대상** (verified만): 없음
- **추출하지 않을 항목** (planned / documented-only / abandoned): 현재 전체