Files
Gnuboard7/resources/views/app.blade.php
T
HeuJung 71abdeaf60 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종 신설.
2026-08-17 01:45:09 +09:00

111 lines
6.5 KiB
PHP

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
{!! g7_meta_generator_tag() !!}
<title>{{ config('app.name', '그누보드7') }}</title>
<!-- 템플릿 외부 리소스 (template.json의 externals) -->
@include('partials.template-externals-head')
<!-- Fallback UI 스타일 -->
@if(empty($activeUserTemplate))
@include('partials.error-fallback-styles')
@endif
{{-- 자산 URL 자가 복구 헬퍼 — CSS <link> 의 onerror 보다 먼저 정의되어야 한다 --}}
@include('partials.asset-url-recovery')
<!-- 템플릿 컴포넌트 스타일 -->
@if(!empty($activeUserTemplate))
{{-- onerror: 정적 최적화 서버에서 확장자 붙은 CSS 가 가로채였을 때
확장자 없는 형태로 1회 교체한다(무스타일 화면 방지). 링크당 1회. --}}
<link rel="stylesheet"
href="{{ \App\Support\AssetUrl::templateAsset($activeUserTemplate, 'css/components.css', $extensionCacheVersion) }}"
onerror="window.__g7AssetUrl && window.__g7AssetUrl.recoverStylesheet(this);">
@endif
</head>
<body>
<!-- React 렌더링 루트 -->
<div id="app" data-template-id="{{ $activeUserTemplate ?? '' }}">
<!-- Progressive Enhancement: 템플릿 없음 Fallback UI -->
@if(empty($activeUserTemplate))
@include('partials.error-fallback-ui')
@endif
</div>
@if(!empty($activeUserTemplate))
<!-- G7 설정 전역 변수 -->
<script>
window.G7Config = {
settings: @json($frontendSettings ?? []),
plugins: @json($pluginSettings ?? []),
modules: @json($moduleSettings ?? []),
moduleAssets: @json($moduleAssets ?? []),
pluginAssets: @json($pluginAssets ?? []),
bundleUrls: @json($bundleUrls ?? null),
activeModules: @json($activeModulesMeta ?? []),
activePlugins: @json($activePluginsMeta ?? []),
trustedScriptHosts: @json($trustedScriptHosts ?? []),
appConfig: @json($appConfig ?? []),
// 레이아웃 편집기 lazy 번들 URL — `/admin/layout-editor/*` 진입 시에만 런타임
// <script> 주입으로 로드된다(초기 접속 payload 에 미포함). filemtime 캐시버스팅,
// 미빌드 상태 대비 file_exists 가드.
coreEditorAsset: '{{ asset('build/core/layout-editor.min.js') }}?v={{ file_exists(public_path('build/core/layout-editor.min.js')) ? filemtime(public_path('build/core/layout-editor.min.js')) : 0 }}',
// DevTools lazy 번들 URL — 디버그 모드에서만 런타임 <script> 주입으로 로드.
coreDevToolsAsset: '{{ asset('build/core/devtools.min.js') }}?v={{ file_exists(public_path('build/core/devtools.min.js')) ? filemtime(public_path('build/core/devtools.min.js')) : 0 }}',
// 확장 캐시 버전 SSoT — 클라이언트 fetch (`?v=`) 동반 필수.
// 자세한 설명은 admin.blade.php 참조.
cache_version: {{ (int) ($extensionCacheVersion ?? 0) }},
// 자산 URL 모드 — 'extension'(기본) | 'extensionless'.
// 정적 최적화 블록(location ~* \.(js|css|json)$)이 동적 응답을 가로채는
// 서버에서 확장자 없는 형태로 전환한다. 부트스트랩 자가 복구가 실패 시
// 이 값을 런타임에 뒤집으므로 최상위 키로 노출한다(설정값 SSoT 는
// G7Config.settings.general.asset_url_mode).
// 자가 복구가 <head> 에서 이미 전환을 확정했다면 그 값을 잇는다.
// 이 대입은 G7Config 객체를 통째로 교체하므로, 독립 전역을 읽지 않으면
// 복원/전환 결과가 여기서 덮여 사라진다.
assetUrlMode: window.__g7AssetUrlMode || '{{ \App\Support\AssetUrl::mode() }}'
};
@if(isset($errorCode) && isset($errorLayout))
// 에러 상태 정보 (503 의존성 미충족 등)
window.G7Error = {
code: {{ $errorCode }},
layout: '{{ $errorLayout }}',
data: @json($unmetDependencies ?? [])
};
@endif
</script>
@include('partials.template-externals-scripts', ['position' => 'before-core'])
{{-- 코어 엔진 + 템플릿 컴포넌트 번들 로드 → 초기화 (재시도 + 폴백 UI) --}}
@include('partials.bootstrap-scripts', [
'templateType' => 'user',
{{-- 코어 엔진 번들은 public/ 의 실물 정적 파일이라 자산 URL 이중 모드 대상이 아니다.
미빌드 상태에서 filemtime() 이 warning 을 내지 않도록 file_exists 가드
(coreEditorAsset/coreDevToolsAsset 와 동일 패턴). --}}
'coreEngineSrc' => asset('build/core/template-engine.min.js') . '?v=' . (file_exists(public_path('build/core/template-engine.min.js')) ? filemtime(public_path('build/core/template-engine.min.js')) : 0),
'componentsSrc' => \App\Support\AssetUrl::templateAsset($activeUserTemplate, 'js/components.iife.js', $extensionCacheVersion),
'initConfig' => array_filter([
'templateId' => $activeUserTemplate,
'templateType' => 'user',
'locale' => app()->getLocale(),
'debug' => (bool) config('app.debug'),
'websocket' => config('broadcasting.connections.reverb.key') ? [
'appKey' => config('broadcasting.connections.reverb.key'),
'host' => config('g7.websocket.client.host', config('broadcasting.connections.reverb.options.host', 'localhost')),
'port' => (int) config('g7.websocket.client.port', config('broadcasting.connections.reverb.options.port', 80)),
'scheme' => config('g7.websocket.client.scheme', config('broadcasting.connections.reverb.options.scheme', 'https')),
] : null,
], fn ($value) => $value !== null),
])
@include('partials.template-externals-scripts', ['position' => 'body-end'])
@endif
</body>
</html>