From f9adfa556f201bf258c251d919bb88f10e1d3010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=B0=EC=83=81=EB=B9=88?= Date: Sun, 16 Aug 2026 21:10:58 +0900 Subject: [PATCH] =?UTF-8?q?fix(analytics):=20=EC=9D=B8=EB=9D=BC=EC=9D=B8?= =?UTF-8?q?=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EB=B3=B4=EA=B0=84=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=EB=A1=9C=20XSS=20=EC=9E=A0=EC=9E=AC=20?= =?UTF-8?q?=EC=9C=84=ED=97=98=20=EC=B0=A8=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - measurementId를 인라인 JavaScript 코드에 템플릿 리터럴로 직접 보간하던 방식에서 data-ga-measurement-id 속성 기반 전달 방식으로 개선 - 인라인 스크립트 코드를 완전한 정적 문자열로 고정하여 잠재적인 코드 인젝션 벡터 차단 - GoogleAnalytics 렌더링 검증 단위 테스트 추가 --- src/components/google-analytics.tsx | 9 ++------- tests/stage-7-seo-feed-integrations.test.tsx | 11 +++++++++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/components/google-analytics.tsx b/src/components/google-analytics.tsx index d6db5f6..86e68b0 100644 --- a/src/components/google-analytics.tsx +++ b/src/components/google-analytics.tsx @@ -10,16 +10,11 @@ export function GoogleAnalytics({ config }: GoogleAnalyticsProps) { } const gtagSource = `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(config.measurementId)}` - const inlineConfig = ` -window.dataLayer = window.dataLayer || []; -function gtag(){dataLayer.push(arguments);} -gtag('js', new Date()); -gtag('config', '${config.measurementId}'); -` + const inlineConfig = `window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js',new Date());var el=document.querySelector('script[data-ga-measurement-id]');if(el){var id=el.getAttribute('data-ga-measurement-id');if(id)gtag('config',id);}` return ( <> - ) diff --git a/tests/stage-7-seo-feed-integrations.test.tsx b/tests/stage-7-seo-feed-integrations.test.tsx index f10b2ad..5c7b3a6 100644 --- a/tests/stage-7-seo-feed-integrations.test.tsx +++ b/tests/stage-7-seo-feed-integrations.test.tsx @@ -68,6 +68,17 @@ describe("stage 7 seo feed and integration gates", () => { expect(integrations.kakao.enabled).toBe(true) }) + it("Given enabled GA config When rendering GoogleAnalytics Then scripts are properly configured with data attribute", () => { + const integrations = getPublicIntegrations({ + NEXT_PUBLIC_GA_MEASUREMENT_ID: "G-ABC123DEF4", + }) + const markup = renderToStaticMarkup() + + expect(markup).toContain('data-ga-measurement-id="G-ABC123DEF4"') + expect(markup).toContain("https://www.googletagmanager.com/gtag/js?id=G-ABC123DEF4") + expect(markup).toContain("gtag('config',id)") + }) + it("Given missing public integration env When rendering gates Then no broken scripts or iframes render", () => { const integrations = getPublicIntegrations({}) const markup = renderToStaticMarkup(