fix(seo,engine): 7.0.7 검증 발견 결함 수정 — 표현식 구조분해 회귀·타이틀 접미사 조립·JSON-LD 이스케이프
- SafeExpressionEvaluator 교체(engine-v1.60.0)가 구 평가기가 허용하던 화살표 파라미터 배열 구조분해(([k, v]) / ([, vid]))를 누락해 해당 문법을 쓰는 액션 params 가 미평가 원문으로 전송, 장바구니 담기/바로 구매가 전 상품 불능이던 회귀를 수정 (engine-v1.60.5, production 재빌드 동반). 저장소 레이아웃 54파일 104곳 사용 형태 전수를 단위/E2E 테스트로 잠금 - TrimStrings 가 타이틀 접미사의 선행 공백을 제거해 봇 렌더 제목이 "제목| 사이트명" 으로 접착되고 빈 제목 화면은 "| 사이트명" 이 매달리던 기존 결함 수정 — 조립 규칙을 composeTitleSuffix 단일 지점(SSoT)으로 정규화 - JSON-LD 직렬화에 JSON_HEX_TAG 미적용으로 검색어의 </script> 가 스크립트 컨텍스트를 조기 종료해 실행 가능한 script 요소가 재생성되던 반사형 XSS(기존 결함) 차단 — 태그 문자 이스케이프, 구조화 데이터 의미는 보존
This commit is contained in:
1 parent
b3d0da9355
commit
0bb2ab9b49
10 files changed
+301
-38
No files matched your search
@@ -374,6 +374,35 @@ class SeoMetaResolverTest extends TestCase
|
||||
$this->assertSame('나이키 에어맥스', $jsonLd['description']);
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON-LD 는 `<script type="application/ld+json">` 안에 임베드되므로, 사용자
|
||||
* 제어 값(검색어 등)의 `</script>` 가 스크립트 컨텍스트를 조기 종료해 실행 가능한
|
||||
* script 요소를 재생성하지 못하도록 `<`·`>` 를 유니코드 이스케이프해야 한다.
|
||||
* (봇 렌더는 _escaped_fragment_ 로 일반 UA 도 강제되므로 반사형 XSS 벡터가 된다.)
|
||||
*/
|
||||
public function test_structured_data_escapes_script_breakout_in_json_ld(): void
|
||||
{
|
||||
$seoConfig = [
|
||||
'structured_data' => [
|
||||
'@type' => 'SearchResultsPage',
|
||||
'name' => 'G7 - {{query.q}}',
|
||||
],
|
||||
];
|
||||
|
||||
$context = ['query' => ['q' => '</script><script>alert(1)</script>']];
|
||||
|
||||
$result = $this->resolver->resolve($seoConfig, $context, null, null, []);
|
||||
|
||||
// 원문 태그가 그대로 실리면 스크립트 컨텍스트가 조기 종료돼 실행 가능한 script 요소가
|
||||
// 재생성된다(반사형 XSS). JSON_HEX_TAG 가 태그 문자를 유니코드 이스케이프하므로 산출물에는
|
||||
// 리터럴 태그 열림/닫힘 문자가 없어야 한다.
|
||||
$this->assertDoesNotMatchRegularExpression('#</?script#', $result['jsonLd']);
|
||||
$this->assertStringContainsString(chr(0x5C).'u003C', $result['jsonLd']);
|
||||
// 이스케이프에도 불구하고 JSON 파싱 시 원래 값이 복원된다(구조화 데이터 의미 보존).
|
||||
$decoded = json_decode($result['jsonLd'], true);
|
||||
$this->assertSame('G7 - </script><script>alert(1)</script>', $decoded['name']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 구조화 데이터가 없으면 jsonLd는 null입니다.
|
||||
*/
|
||||
@@ -1562,7 +1591,7 @@ class SeoMetaResolverTest extends TestCase
|
||||
'page_type' => 'product',
|
||||
'vars' => [
|
||||
// 표현식이 다국어 객체를 직접 반환 — substituteVars 까지 array 전달
|
||||
'product_name' => "{{product.data.name}}",
|
||||
'product_name' => '{{product.data.name}}',
|
||||
'commerce_name' => '$module_settings:basic_info.shop_name',
|
||||
],
|
||||
];
|
||||
@@ -1638,4 +1667,32 @@ class SeoMetaResolverTest extends TestCase
|
||||
$this->assertSame('운동화', $result['og']['description']);
|
||||
$this->assertSame('https://e.co/ko.jpg', $result['og']['image']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 접미사 조립: 저장 시 TrimStrings 가 선행 공백을 제거하므로("| 그누보드7"),
|
||||
* 제목이 있으면 공백을 복원해 잇고, 제목이 비면 매달린 구분자를 떼어낸다.
|
||||
* (blade 는 title.titleSuffix 단순 연결 — 조립 규칙은 이 메서드가 SSoT)
|
||||
*/
|
||||
public function test_compose_title_suffix_restores_space_between_title_and_trimmed_suffix(): void
|
||||
{
|
||||
$this->assertSame(' | 그누보드7', SeoMetaResolver::composeTitleSuffix('가죽 크로스백', '| 그누보드7'));
|
||||
}
|
||||
|
||||
public function test_compose_title_suffix_keeps_explicit_leading_space_as_is(): void
|
||||
{
|
||||
$this->assertSame(' | 그누보드7', SeoMetaResolver::composeTitleSuffix('가죽 크로스백', ' | 그누보드7'));
|
||||
}
|
||||
|
||||
public function test_compose_title_suffix_strips_dangling_separator_when_title_is_empty(): void
|
||||
{
|
||||
$this->assertSame('그누보드7', SeoMetaResolver::composeTitleSuffix('', '| 그누보드7'));
|
||||
$this->assertSame('그누보드7', SeoMetaResolver::composeTitleSuffix('', ' - 그누보드7'));
|
||||
}
|
||||
|
||||
public function test_compose_title_suffix_returns_empty_when_suffix_is_blank(): void
|
||||
{
|
||||
$this->assertSame('', SeoMetaResolver::composeTitleSuffix('가죽 크로스백', ''));
|
||||
$this->assertSame('', SeoMetaResolver::composeTitleSuffix('', ' '));
|
||||
$this->assertSame('', SeoMetaResolver::composeTitleSuffix('', '| '));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user