KISA 제보 취약점(KVE-2026-1914/1919/2019/2029/2041/2042/2043/2044)과 그 수정 과정에서 드러난 자격증명 전송로 결함을 함께 해소한다. globalHeaders 는 데이터소스와 apiCall 핸들러에만 적용되는데, 코어 ApiClient 를 직접 부르는 경로들이 그 사실을 모른 채 게이트된 엔드포인트를 호출하고 있었다. 서버는 정당한 사용자를 거부하고 화면은 이미 버튼을 내준 뒤라, 예외도 로그도 없이 그 자리만 비는 형태로만 드러났다. 전송로 10축을 전수 열거해 6건을 고치고, 같은 실수가 반복되지 않도록 규정과 coverage 에 등재했다. 아웃바운드 프록시가 사이트 자기 자신으로 가는 내부 요청까지 가로채 저장이 수십 초씩 걸리던 문제도 함께 고쳤다. 실패가 폴백으로 삼켜져 화면에는 지연으로만 나타났다.
123 lines
4.6 KiB
JSON
123 lines
4.6 KiB
JSON
{
|
|
"version": "1.0.0",
|
|
"layout_name": "board/form",
|
|
"permissions": [],
|
|
"extends": "_user_base",
|
|
"meta": {
|
|
"title": "{{route.id ? '$t:board.edit_post' : '$t:board.new_post'}} - {{form_meta?.data?.board?.name || ''}}",
|
|
"description": "{{route.id ? '$t:board.edit_post_description' : '$t:board.new_post_description'}}"
|
|
},
|
|
"data_sources": [
|
|
{
|
|
"id": "form_data",
|
|
"label_key": "$t:editor.data_source.form_data",
|
|
"type": "api",
|
|
"endpoint": "/api/modules/sirsoft-board/boards/{{route.slug}}/posts/form-data",
|
|
"method": "GET",
|
|
"auto_fetch": true,
|
|
"auth_mode": "optional",
|
|
"cache": false,
|
|
"refetchOnMount": true,
|
|
"initLocal": "form",
|
|
"loading_strategy": "blocking",
|
|
"_comment_verify_token": "검증 토큰은 자격증명이라 헤더로 보낸다 — 쿼리로 실으면 웹서버 접근 기록과 Referer 에 그대로 남는다. 빈 값은 엔진이 헤더에서 제외한다.",
|
|
"headers": {
|
|
"X-Board-Post-Verify-Token": "{{_local.verificationToken ?? ''}}"
|
|
},
|
|
"params": {
|
|
"post_id": "{{route?.id ?? ''}}",
|
|
"parent_id": "{{query?.parent_id ?? ''}}"
|
|
},
|
|
"onSuccess": {
|
|
"comment": "refetch 후 _local.form을 응답값으로 직접 덮어씌우기 (비밀글 content 반영)",
|
|
"handler": "setState",
|
|
"params": {
|
|
"target": "local",
|
|
"form": "{{response.data.data}}"
|
|
}
|
|
},
|
|
"errorHandling": {
|
|
"403": {
|
|
"comment": "권한 없음 - 에러 페이지 표시 (errorHandling에서 route 컨텍스트 미지원)",
|
|
"handler": "showErrorPage",
|
|
"params": {
|
|
"target": "content"
|
|
}
|
|
},
|
|
"404": {
|
|
"handler": "showErrorPage",
|
|
"params": {
|
|
"target": "content"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"id": "form_meta",
|
|
"label_key": "$t:editor.data_source.form_meta",
|
|
"type": "api",
|
|
"endpoint": "/api/modules/sirsoft-board/boards/{{route.slug}}/posts/form-meta",
|
|
"method": "GET",
|
|
"auto_fetch": true,
|
|
"auth_mode": "optional",
|
|
"cache": false,
|
|
"refetchOnMount": true,
|
|
"_comment_verify_token": "검증 토큰은 자격증명이라 헤더로 보낸다 — 쿼리로 실으면 웹서버 접근 기록과 Referer 에 그대로 남는다. 빈 값은 엔진이 헤더에서 제외한다.",
|
|
"headers": {
|
|
"X-Board-Post-Verify-Token": "{{_local.verificationToken ?? ''}}"
|
|
},
|
|
"params": {
|
|
"post_id": "{{route?.id ?? ''}}",
|
|
"parent_id": "{{query?.parent_id ?? ''}}"
|
|
},
|
|
"errorHandling": {
|
|
"403": {
|
|
"comment": "권한 없음 - 에러 페이지 표시 (errorHandling에서 route 컨텍스트 미지원)",
|
|
"handler": "showErrorPage",
|
|
"params": {
|
|
"target": "content"
|
|
}
|
|
},
|
|
"404": {
|
|
"handler": "showErrorPage",
|
|
"params": {
|
|
"target": "content"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"init_actions": [
|
|
{
|
|
"comment": "tempKey 생성",
|
|
"handler": "setState",
|
|
"params": {
|
|
"target": "local",
|
|
"tempKey": "{{('temp_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9))}}"
|
|
}
|
|
}
|
|
],
|
|
"slots": {
|
|
"content": [
|
|
{
|
|
"comment": "비회원 수정 시 비밀번호 확인 모달",
|
|
"partial": "partials/board/form/_password_verify_modal.json"
|
|
},
|
|
{
|
|
"type": "layout",
|
|
"name": "Container",
|
|
"blur_until_loaded": true,
|
|
"dataKey": "form",
|
|
"trackChanges": true,
|
|
"props": {
|
|
"className": "py-8 max-w-4xl mx-auto"
|
|
},
|
|
"children": [
|
|
{
|
|
"partial": "partials/board/form/_type_renderer.json"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
} |