fix(security): KVE-2026-1914/1915/1919 remediation 전건
위임 관리자(부관리자)가 권한·역할·표현식·비밀 콘텐츠 경계를 우회하던 결함군을 계층 대칭성 원칙으로 전건 차단한다. 약한 경로가 정상 응답을 내보내는 것이 유일한 증상이라, 게이트를 생산 지점 한 곳(SSoT)에 두고 같은 데이터를 내보내는 소비 경로 전부가 그 게이트를 경유하도록 맞췄다. - 등급 상한(rank ceiling): 슈퍼관리자 보호·역할/사용자 역할 배정(추가·제거 대칭)·일괄 상태변경·순서변경을 상세 경로와 동일 강도로 재적용. 가드는 DB 쓰기에 선행하여 거부 시 상태 불변. - 레이아웃 표현식: new Function/with 실행을 AST 화이트리스트 평가기로 교체. 비-문자열 computed 키 정규화(normalizeKey)·Object facade(리플렉션 static 제거)·legacy 접근자 차단. 저장측 검증·정적 검사와 3계층 동형. - secret 게이트: 비밀글의 댓글·첨부·문의 독립 경로 재적용, hash 파일서빙 소유권·비밀·발행 상태 검사 통일. - 신뢰 스크립트 호스트: 확장 선언 기반 + same-origin 브라우저 정규화를 런타임·저장측·정적검사 3층 동형화. - 회귀 감지: 단위·Feature·E2E·시나리오 매니페스트 전축 + audit 룰 4종 신설.
This commit is contained in:
+1
@@ -129,6 +129,7 @@ describe('Admin 본인인증 모달 cancel 흐름 (sirsoft-admin_basic)', () =>
|
||||
expect(onError[0].handler).toBe('suppress');
|
||||
});
|
||||
|
||||
/** @effects admin_basic_modal_cancel_flow_matches_basic_template */
|
||||
it('cancel sequence 가 [apiCall, resolveIdentityChallenge, closeModal] 순서', () => {
|
||||
const seq = findCancelSequence();
|
||||
const inner = (seq!.params!.actions ?? []) as Action[];
|
||||
|
||||
+2
@@ -543,6 +543,7 @@ describe('코드 편집기 ?route= URL 동기화', () => {
|
||||
expect(routeInit.params.selectedNodeKind).toBe('layout');
|
||||
});
|
||||
|
||||
/** @effects code_editor_file_select_syncs_route_query */
|
||||
it('데스크탑 파일 항목 클릭 시 replaceUrl 로 file.route_path 를 ?route= 에 반영한다', () => {
|
||||
// 좌측 트리 레이아웃 파일 iteration (file_var) 의 click 시퀀스
|
||||
const fileIteration = findComponent(layoutEditJson, (n) =>
|
||||
@@ -567,6 +568,7 @@ describe('코드 편집기 ?route= URL 동기화', () => {
|
||||
expect(replaceUrl.params.query.route).toContain('route_path');
|
||||
});
|
||||
|
||||
/** @effects code_editor_extension_select_clears_route_query */
|
||||
it('확장 선택 시 replaceUrl 로 ?route= 를 비운다 (확장은 라우트 없음)', () => {
|
||||
const extIteration = findComponent(layoutEditJson, (n) =>
|
||||
Boolean(n.iteration?.source?.includes('extGroup.extensions'))
|
||||
|
||||
@@ -248,6 +248,7 @@ describe('본인인증 공통 모달 (sirsoft-basic) — engine-v1.46.0', () =>
|
||||
expect(cancelChain).toBeDefined();
|
||||
});
|
||||
|
||||
/** @effects modal_cancel_button_calls_server_cancel_api_first, modal_cancel_apiCall_uses_global_identityChallenge_challenge_id */
|
||||
it('cancel sequence 의 첫 액션이 서버 cancel API 호출 — audit trail 정합', () => {
|
||||
const seq = findCancelSequence();
|
||||
expect(seq).toBeDefined();
|
||||
@@ -262,6 +263,7 @@ describe('본인인증 공통 모달 (sirsoft-basic) — engine-v1.46.0', () =>
|
||||
expect(apiCall!.target!).toContain('_global.identityChallenge.challenge_id');
|
||||
});
|
||||
|
||||
/** @effects modal_cancel_apiCall_skipped_when_challenge_id_absent */
|
||||
it('cancel apiCall 이 challenge 발급된 경우에만 호출 (if 가드)', () => {
|
||||
const seq = findCancelSequence();
|
||||
const inner = (seq!.params!.actions ?? []) as Action[];
|
||||
@@ -269,6 +271,7 @@ describe('본인인증 공통 모달 (sirsoft-basic) — engine-v1.46.0', () =>
|
||||
expect((apiCall as any).if).toContain('_global.identityChallenge?.challenge_id');
|
||||
});
|
||||
|
||||
/** @effects modal_cancel_apiCall_failure_does_not_block_modal_close */
|
||||
it('cancel apiCall 실패해도 모달 닫기는 진행 (onError suppress)', () => {
|
||||
const seq = findCancelSequence();
|
||||
const inner = (seq!.params!.actions ?? []) as Action[];
|
||||
@@ -278,6 +281,7 @@ describe('본인인증 공통 모달 (sirsoft-basic) — engine-v1.46.0', () =>
|
||||
expect(onError[0].handler).toBe('suppress');
|
||||
});
|
||||
|
||||
/** @effects modal_cancel_sequence_order_is_apiCall_resolve_close */
|
||||
it('cancel sequence 가 [apiCall, resolveIdentityChallenge, closeModal] 순서', () => {
|
||||
const seq = findCancelSequence();
|
||||
const inner = (seq!.params!.actions ?? []) as Action[];
|
||||
|
||||
+3
@@ -41,6 +41,7 @@ describe('Footer — G7 표준 responsive 분기', () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
/** @scenario preview_device=desktop, custom_width_input=in_range */
|
||||
it('데스크톱(1280) — 링크 그룹 그리드 5열', () => {
|
||||
setWidth(1280);
|
||||
const { container } = render(<Footer />);
|
||||
@@ -49,6 +50,7 @@ describe('Footer — G7 표준 responsive 분기', () => {
|
||||
expect(grid.style.gridTemplateColumns).toBe('repeat(5, minmax(0, 1fr))');
|
||||
});
|
||||
|
||||
/** @scenario preview_device=tablet, custom_width_input=in_range */
|
||||
it('태블릿(820) — 그리드 2열', () => {
|
||||
setWidth(820);
|
||||
const { container } = render(<Footer />);
|
||||
@@ -56,6 +58,7 @@ describe('Footer — G7 표준 responsive 분기', () => {
|
||||
expect(grid.style.gridTemplateColumns).toBe('repeat(2, minmax(0, 1fr))');
|
||||
});
|
||||
|
||||
/** @scenario preview_device=mobile, custom_width_input=in_range */
|
||||
it('모바일(390) — 그리드 1열 + 저작권 영역 세로(column)', () => {
|
||||
setWidth(390);
|
||||
const { container } = render(<Footer />);
|
||||
|
||||
Reference in New Issue
Block a user