<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>체인의정석</title>
    <link>https://it-timehacker.tistory.com/</link>
    <description>유튜브 체인의정석의 블록체인 기술 전문 블로그 입니다.
https://www.youtube.com/channel/UCHsRy47P2KlE749oAAjb0Yg</description>
    <language>ko</language>
    <pubDate>Wed, 8 Jul 2026 13:48:02 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>체인의정석</managingEditor>
    <image>
      <title>체인의정석</title>
      <url>https://tistory1.daumcdn.net/tistory/3947502/attach/781e44aaf3f340b694ef229345e4a715</url>
      <link>https://it-timehacker.tistory.com</link>
    </image>
    <item>
      <title>웹 서비스에서 Content-Security-Policy (CSP), Referer Policy 처리를 통한 보안 강화</title>
      <link>https://it-timehacker.tistory.com/576</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모회사에서 별도의 WAF와 백신을 설치한 이후에 관련된 ASM(Attack Surface Management) 대시보드를 볼 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ASM은 외부 공격자가 접근할 수 있는 모든 잠재적 지점을 지속적으로 식별하고 분석하며, 보안 취약점을 최소화하는 프로세스라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보니 referer policy와 Content-Security-Policy가 적용되지 않아서 보안성 경고가 뜬 부분을 볼 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@sejinkim/Referrer-Policy%EC%9D%98-%EC%9D%B4%ED%95%B4&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@sejinkim/Referrer-Policy%EC%9D%98-%EC%9D%B4%ED%95%B4&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1767684139308&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Referrer-Policy의 이해&quot; data-og-description=&quot;사용자 행동 로그 데이터 수집 중 Referer가 유실되었던 사례를 계기로, Referrer-Policy에 관해 자세히 알아보았습니다.&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@sejinkim/Referrer-Policy%EC%9D%98-%EC%9D%B4%ED%95%B4&quot; data-og-url=&quot;https://velog.io/@sejinkim/Referrer-Policy의-이해&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cFBgn4/hyZQ7ZwsNw/dSHHTbOkZKmNRPo9zZT6UK/img.png?width=875&amp;amp;height=200&amp;amp;face=0_0_875_200,https://scrap.kakaocdn.net/dn/5wSUf/hyZQZtE9aV/oiNTsvm4n2oMtB9XJSAkd1/img.png?width=875&amp;amp;height=200&amp;amp;face=0_0_875_200,https://scrap.kakaocdn.net/dn/DSIo4/hyZQX3EEfo/5rbT9li15DVwwlXs4bo1f1/img.png?width=2493&amp;amp;height=241&amp;amp;face=0_0_2493_241&quot;&gt;&lt;a href=&quot;https://velog.io/@sejinkim/Referrer-Policy%EC%9D%98-%EC%9D%B4%ED%95%B4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@sejinkim/Referrer-Policy%EC%9D%98-%EC%9D%B4%ED%95%B4&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cFBgn4/hyZQ7ZwsNw/dSHHTbOkZKmNRPo9zZT6UK/img.png?width=875&amp;amp;height=200&amp;amp;face=0_0_875_200,https://scrap.kakaocdn.net/dn/5wSUf/hyZQZtE9aV/oiNTsvm4n2oMtB9XJSAkd1/img.png?width=875&amp;amp;height=200&amp;amp;face=0_0_875_200,https://scrap.kakaocdn.net/dn/DSIo4/hyZQX3EEfo/5rbT9li15DVwwlXs4bo1f1/img.png?width=2493&amp;amp;height=241&amp;amp;face=0_0_2493_241');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Referrer-Policy의 이해&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;사용자 행동 로그 데이터 수집 중 Referer가 유실되었던 사례를 계기로, Referrer-Policy에 관해 자세히 알아보았습니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Referer Policy - 서버 처리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #0a0a0a; text-align: start;&quot; data-processed=&quot;true&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-processed=&quot;true&quot; data-hveid=&quot;CAMQAA&quot;&gt;&lt;span data-processed=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;&lt;b&gt;목적&lt;/b&gt;: 브라우저가 HTTP 요청 시&lt;span&gt;&amp;nbsp;&lt;/span&gt;Referer&lt;span&gt;&amp;nbsp;&lt;/span&gt;(오타가 있는 원조 헤더 이름) 헤더에 포함할 이전 페이지의 URL 정보(리퍼러 정보)의 양을 제어합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-processed=&quot;true&quot; data-hveid=&quot;CAMQAQ&quot;&gt;&lt;span data-processed=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;&lt;b&gt;작동 방식&lt;/b&gt;: 사용자가 한 웹사이트에서 다른 웹사이트로 이동하거나 리소스를 요청할 때, 목적지 서버에 어떤 출처 정보를 보낼지 결정합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-processed=&quot;true&quot; data-hveid=&quot;CAMQAg&quot;&gt;&lt;span data-processed=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;&lt;b&gt;주요 목표&lt;/b&gt;: URL에 포함될 수 있는 민감한 정보(예: 세션 토큰, 개인 식별자)의 불필요한 노출을 방지하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;사용자 프라이버시를 보호&lt;/b&gt;하는 데 중점을 둡니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-processed=&quot;true&quot; data-hveid=&quot;CAMQAw&quot;&gt;&lt;span data-processed=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;&lt;b&gt;예시&lt;/b&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;Referrer-Policy: no-referrer는 어떠한 리퍼러 정보도 보내지 않도록 지시하며,&lt;span&gt;&amp;nbsp;&lt;/span&gt;Referrer-Policy: strict-origin-when-cross-origin은 동일 출처 요청 시에는 전체 URL을 보내지만 교차 출처 요청 시에는 출처(origin) 정보만 보내도록 합니다.&lt;/span&gt;&lt;span data-processed=&quot;true&quot; data-wiz-uids=&quot;cIfpbb_16,cIfpbb_17&quot;&gt;&lt;span data-processed=&quot;true&quot; data-wiz-attrbind=&quot;class=cIfpbb_15/TKHnVd&quot; data-animation-atomic=&quot;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-processed=&quot;true&quot; data-wiz-uids=&quot;cIfpbb_16,cIfpbb_17&quot;&gt;&lt;/span&gt;&lt;span data-processed=&quot;true&quot; data-wiz-uids=&quot;cIfpbb_16,cIfpbb_17&quot;&gt;&lt;span data-processed=&quot;true&quot; data-wiz-attrbind=&quot;class=cIfpbb_15/TKHnVd&quot; data-animation-atomic=&quot;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 사이트 보안의 기본적인 대전제는 Same Origin Policy(SOP) 동일 출처 정책이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가능한 공격 벡터를 줄이기 위해, 다른 출저와의 상호작용을 제한하는 보안 정책이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 출저가 아닌 경우 Cross-site 또는 Cross-Origin 이라고 하며, 개인정보 보호 및 웹 공격 방어 차원에서 특정 기능이나 정보가 제한됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 referer policy에 대해서 찾아본 결과&amp;nbsp; Referer의 정의는 현재 요청을 보낸 페이지의 절대 혹은 부분 주소라고 한다. 리소스 요청이나 사용자의 링크 클릭에 존재할 수 있으며, 여기서 정보를 제한하지 않으면 사용자의 개인정보가 노출될 수 있기 때문에 제한을 하는 정책이라고 한다. 일단 SOP의 경우에는 문제를 삼지 않지만 cross-origin 요청일 때는 엄격하게 제한하는 것이 기본적인 기조라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따아서 cross-origin요청일 때는 &quot;&lt;span style=&quot;background-color: #e9ecef; color: #212529; text-align: start;&quot;&gt;strict-origin-when-cross-origin&quot; 요청을 주면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반영하는 방법은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kku-jun.tistory.com/48&quot;&gt;https://kku-jun.tistory.com/48&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1767685171508&quot; style=&quot;color: #333333; text-align: start;&quot; contenteditable=&quot;false&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Zi451/dJMb88FXws3/mYzWfSzzyKbHbQVkaOBp3K/img.png?width=495&amp;amp;height=287&amp;amp;face=0_0_495_287,https://scrap.kakaocdn.net/dn/bnaHvy/dJMb88eTffQ/7EFKpXxTyP96aGAYfEpGh1/img.png?width=495&amp;amp;height=287&amp;amp;face=0_0_495_287,https://scrap.kakaocdn.net/dn/bEH7kM/dJMb83klCrC/fpPIO8Z0a5tMHZugTYdIn1/img.png?width=495&amp;amp;height=287&amp;amp;face=0_0_495_287&quot; data-og-url=&quot;https://kku-jun.tistory.com/48&quot; data-og-source-url=&quot;https://kku-jun.tistory.com/48&quot; data-og-host=&quot;kku-jun.tistory.com&quot; data-og-description=&quot;혹시 프로젝트를 진행하면서 이미지 url을 통해 접근은 가능하나 개발중인 서비스앱 내부에서는 호출하지 못한 경험이 있는가? 필자는 다음 경험을 Referer policy를 정의하면서 접근할 수 있게 되&quot; data-og-title=&quot;Referer Policy 적용하기&quot; data-og-type=&quot;article&quot; data-ke-align=&quot;alignCenter&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://kku-jun.tistory.com/48&quot; data-source-url=&quot;https://kku-jun.tistory.com/48&quot;&gt;
&lt;div style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Zi451/dJMb88FXws3/mYzWfSzzyKbHbQVkaOBp3K/img.png?width=495&amp;amp;height=287&amp;amp;face=0_0_495_287,https://scrap.kakaocdn.net/dn/bnaHvy/dJMb88eTffQ/7EFKpXxTyP96aGAYfEpGh1/img.png?width=495&amp;amp;height=287&amp;amp;face=0_0_495_287,https://scrap.kakaocdn.net/dn/bEH7kM/dJMb83klCrC/fpPIO8Z0a5tMHZugTYdIn1/img.png?width=495&amp;amp;height=287&amp;amp;face=0_0_495_287');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;Referer Policy 적용하기&lt;/p&gt;
&lt;p style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;혹시 프로젝트를 진행하면서 이미지 url을 통해 접근은 가능하나 개발중인 서비스앱 내부에서는 호출하지 못한 경험이 있는가? 필자는 다음 경험을 Referer policy를 정의하면서 접근할 수 있게 되&lt;/p&gt;
&lt;p style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;kku-jun.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;해당 방법은 간단한데 웹서버에서&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767685171508&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;		// 보안 헤더 설정
		w.Header().Set(&quot;Referrer-Policy&quot;, &quot;strict-origin-when-cross-origin&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 언어는 go언어지만 해당 방식으로 보안 헤더를 서버에서 설정해 주면&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q9XB8/dJMcad1HUju/f8gQrtkTq5vNXD7KW7oET0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q9XB8/dJMcad1HUju/f8gQrtkTq5vNXD7KW7oET0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q9XB8/dJMcad1HUju/f8gQrtkTq5vNXD7KW7oET0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq9XB8%2FdJMcad1HUju%2Ff8gQrtkTq5vNXD7KW7oET0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;488&quot; height=&quot;56&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;56&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같이 referrer policy가 반영되게 된다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;CSP - 서버 처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 작업한 부분은 CSP 반영이다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #001d35; text-align: start;&quot; data-processed=&quot;true&quot; data-sfc-cp=&quot;&quot; data-animation-nesting=&quot;&quot;&gt;Content-Security-Policy (CSP)&lt;span data-processed=&quot;true&quot; data-wiz-uids=&quot;cIfpbb_1g,cIfpbb_1h&quot;&gt;&lt;span data-processed=&quot;true&quot; data-wiz-attrbind=&quot;class=cIfpbb_1f/TKHnVd&quot; data-animation-atomic=&quot;&quot;&gt;&lt;span style=&quot;color: #001d35;&quot; data-processed=&quot;true&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #0a0a0a; text-align: start;&quot; data-processed=&quot;true&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-processed=&quot;true&quot; data-hveid=&quot;CAUQAA&quot;&gt;&lt;span data-processed=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;&lt;b&gt;목적&lt;/b&gt;: 웹사이트 관리자가 페이지에서 로드할 수 있는 리소스(스크립트, 이미지, 스타일 등)의 출처를 제어할 수 있도록 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-processed=&quot;true&quot; data-hveid=&quot;CAUQAQ&quot;&gt;&lt;span data-processed=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;&lt;b&gt;작동 방식&lt;/b&gt;: 신뢰할 수 있는 콘텐츠 소스(도메인)를 지정하여, 브라우저가 승인되지 않은 출처의 악성 콘텐츠를 로드하거나 실행하는 것을 방지합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-processed=&quot;true&quot; data-hveid=&quot;CAUQAg&quot;&gt;&lt;span data-processed=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;&lt;b&gt;주요 목표&lt;/b&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;교차 사이트 스크립팅(XSS)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;및 데이터 삽입 공격과 같은 특정 유형의 공격을 탐지하고 완화하는 데 효과적인 추가 보안 계층입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-processed=&quot;true&quot; data-hveid=&quot;CAUQAw&quot;&gt;&lt;span data-processed=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;&lt;b&gt;예시&lt;/b&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;Content-Security-Policy: script-src 'self' &lt;a href=&quot;https://trusted-scripts.com;&quot;&gt;https://trusted-scripts.com;&lt;/a&gt;은 브라우저에게 자체 출처와&lt;span&gt;&amp;nbsp;&lt;/span&gt;trusted-scripts.com에서만 스크립트를 로드하도록 지시합니다.&lt;/span&gt;&lt;span data-processed=&quot;true&quot; data-wiz-uids=&quot;cIfpbb_1z,cIfpbb_20&quot;&gt;&lt;span data-processed=&quot;true&quot; data-wiz-attrbind=&quot;class=cIfpbb_1y/TKHnVd&quot; data-animation-atomic=&quot;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span data-processed=&quot;true&quot; data-wiz-uids=&quot;cIfpbb_1z,cIfpbb_20&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 말하는 CSP는 콘텐츠 보안 정책으로 교차 사이트 스크립팅과 데이터 주입 공격 등을 방어하기 위한 추가 보안 계층이라고 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1767598577642&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;There is missing header: Content-Security-Policy. The HTTP Content-Security-Policy response header allows web site administrators to control resources the user agent is allowed to load for a given page.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 http 요청에서 아래와 같이 이미 정책이 있다면 괜찮지만 해당 정책이 없다면 외부로부터의 호출에 있어서 보안 계층이 없어지는 것이기 때문에 활성화를 시켜주어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;CSP를 활성화하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a style=&quot;background-color: #ffffff; color: #044c9f; text-align: start;&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy&quot;&gt;Content-Security-Policy&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;HTTP 헤더를 반환하도록 웹 서버를 구성해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767685473516&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;w.Header().Set(&quot;Content-Security-Policy&quot;, 
    &quot;default-src 'self'; 
     script-src 'self' 'unsafe-inline' 'unsafe-eval'; 
     style-src 'self' 'unsafe-inline'; 
     img-src 'self' data: https:; 
     font-src 'self' data:; 
     connect-src 'self'&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Guides/CSP&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.mozilla.org/ko/docs/Web/HTTP/Guides/CSP&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1767685350533&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;컨텐츠 보안 정책 (CSP) - HTTP | MDN&quot; data-og-description=&quot;CSP의 주요 목표는 XSS 공격을 완화하고 보고하는 것입니다. XSS 공격은 서버에서 받은 콘텐츠를 브라우저가 신뢰한다는 점을 악용합니다. 브라우저는 콘텐츠의 출처를 신뢰하기 때문에 콘텐츠가 &quot; data-og-host=&quot;developer.mozilla.org&quot; data-og-source-url=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Guides/CSP&quot; data-og-url=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Guides/CSP&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Guides/CSP&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Guides/CSP&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;컨텐츠 보안 정책 (CSP) - HTTP | MDN&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;CSP의 주요 목표는 XSS 공격을 완화하고 보고하는 것입니다. XSS 공격은 서버에서 받은 콘텐츠를 브라우저가 신뢰한다는 점을 악용합니다. 브라우저는 콘텐츠의 출처를 신뢰하기 때문에 콘텐츠가&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.mozilla.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Referer Policy &amp;amp; CSP 프론트엔드 처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 두 부분 모두 프론트엔드에서 정적 파일 실행하는 부분도 ASM 모니터링 툴에 걸려서 업데이트를 진행해야 했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 메타 마스크도 들어간다면 프론트엔드에서는 아래와 같이 정책을 짜면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일명 : next.config.mjs&lt;/p&gt;
&lt;pre id=&quot;code_1767686037727&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/** @type {import('next').NextConfig} */
const nextConfig = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [
          {
            key: 'Referrer-Policy',
            value: 'strict-origin-when-cross-origin',
          },
          {
            key: 'Content-Security-Policy',
            value: [
              &quot;default-src 'self'&quot;,
              &quot;script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.yourdomain.com&quot;,
              &quot;style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net&quot;,
              &quot;img-src 'self' data: https:&quot;,
              &quot;font-src 'self' data: https://fonts.gstatic.com https://cdn.jsdelivr.net&quot;,
              &quot;connect-src 'self' https://*.yourdomain.com https://*.metamask.io https://*.metamask-api.io https://*.arbitrum.io https://*.infura.io https://cdn.jsdelivr.net wss://*.metamask.io wss://*.metamask-api.io&quot;,
            ].join('; '),
          },
        ],
      },
    ];
  },
};

export default nextConfig;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 사용되는 외부 도메인 호출을 모두 CSP 정책에 넣어주어야 외부 공격으로부터 더 안전한 서비스를 만들 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 프론트에서 상세하게 CSP정책이 있기 때문에 이렇게 도메인을 하나하나 넣어주는 작업은 백엔드에서는 할 필요가 없다.&lt;/p&gt;</description>
      <category>개발/frontend</category>
      <category>Content-Security-Policy</category>
      <category>CSP</category>
      <category>csp 처리</category>
      <category>http policy</category>
      <category>referrer policy</category>
      <category>referrer policy 처리</category>
      <category>Response Headers</category>
      <author>체인의정석</author>
      <guid isPermaLink="true">https://it-timehacker.tistory.com/576</guid>
      <comments>https://it-timehacker.tistory.com/576#entry576comment</comments>
      <pubDate>Mon, 5 Jan 2026 16:48:21 +0900</pubDate>
    </item>
    <item>
      <title>React, Next.js 취약점 (CVE-2025-55182, CVE-2025-66478)</title>
      <link>https://it-timehacker.tistory.com/575</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 사용중인 내부 admin 백오피스는 react + next.js로 되어 있다. 사내망에서만 접근이 가능하도록 설정이 되어 있으며, 메타마스크 로그인 까지 통해야 요청을 날릴 수 있기 때문에 상대적으로 안전하지만 요즘 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;심각도가 10점 만점에 10점을 기록한&lt;/span&gt;&amp;nbsp;CVE-2025-55182, CVE-2025-66478 취약점이 이슈가 되어서 패치를 진행하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 취약점은 서버사이드 랜더링 과정에서 공격자가 리엑트 서버의 주소를 알아서 접근이 가능하고, 명령어만 http요청을 실행이 되면 공격이 가능하기 때문에 10점짜리 취약점이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 부분을 고려했을때 admin페이지 같은 부분은 항상 내부망에서만 접근이 가능해야 하는것 같으며 react, next.js의 경우 비슷한 류의 공격포인트가 계속 생기기 때문에 주기적으로 공격포인트를 모니터링 해주어야 함을 느꼈다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;요약&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;React Server Components 프레임워크&lt;/span&gt;&lt;/b&gt;&lt;span&gt;는 일반적으로 사용자 인터페이스를 구축하는 데 사용됩니다. 2025년 12월 3일,&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #1a73e8;&quot; href=&quot;https://www.google.com/url?q=http%3A%2F%2Fcve.org&quot;&gt;&lt;span&gt;CVE.org&lt;/span&gt;&lt;/a&gt;&lt;span&gt;는 이 취약점에&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #1a73e8;&quot; href=&quot;https://www.google.com/url?q=https%3A%2F%2Fwww.cve.org%2FCVERecord%3Fid%3DCVE-2025-55182&quot;&gt;&lt;span&gt;CVE-2025-55182&lt;/span&gt;&lt;/a&gt;&lt;span&gt;를 할당했습니다. 공식 공통 취약점 등급 시스템(CVSS) 기본 심각도 점수는 10.0점 만점인 '심각(Critical)'으로 결정되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #202124; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #5f6368;&quot;&gt;&lt;b&gt;&lt;span&gt;취약한 버전&lt;/span&gt;&lt;/b&gt;&lt;span&gt;: React 19.0, 19.1.0, 19.1.1, 19.2.0&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;color: #5f6368;&quot;&gt;&lt;b&gt;&lt;span&gt;패치된 버전&lt;/span&gt;&lt;/b&gt;&lt;span&gt;: React 19.2.1&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;color: #5f6368;&quot;&gt;&lt;b&gt;&lt;span&gt;수정 사항(Fix)&lt;/span&gt;&lt;/b&gt;&lt;span&gt;:&amp;nbsp;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://github.com/facebook/react/commit/7dc903cd29dac55efb4424853fd0442fef3a8700&quot;&gt;https://github.com/facebook/react/commit/7dc903cd29dac55efb4424853fd0442fef3a8700&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;color: #5f6368;&quot;&gt;&lt;b&gt;&lt;span&gt;공지&lt;/span&gt;&lt;/b&gt;&lt;span&gt;:&amp;nbsp;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components&quot;&gt;https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #5f6368; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Next.js는 React에 의존하는 웹 개발 프레임워크이며, 역시 사용자 인터페이스 구축에 널리 사용됩니다. (Next.js 취약점은 중복으로 표시되기 전&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #1a73e8;&quot; href=&quot;https://www.google.com/url?q=https%3A%2F%2Fwww.cve.org%2FCVERecord%3Fid%3DCVE-2025-66478&quot;&gt;&lt;span&gt;CVE-2025-66478&lt;/span&gt;&lt;/a&gt;&lt;span&gt;로 참조되었습니다.)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #202124; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #5f6368;&quot;&gt;&lt;b&gt;&lt;span&gt;취약한 버전&lt;/span&gt;&lt;/b&gt;&lt;span&gt;: Next.js 15.x, Next.js 16.x, Next.js 14.3.0-canary.77 및 이후 canary 릴리스&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;color: #5f6368;&quot;&gt;&lt;b&gt;&lt;span&gt;패치된 버전&lt;/span&gt;&lt;/b&gt;&lt;span&gt;은&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #1a73e8;&quot; href=&quot;https://www.google.com/url?q=https%3A%2F%2Fnextjs.org%2Fblog%2FCVE-2025-66478%23required-action&quot;&gt;&lt;span&gt;여기&lt;/span&gt;&lt;/a&gt;&lt;span&gt;에 나열되어 있습니다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;color: #5f6368;&quot;&gt;&lt;b&gt;&lt;span&gt;수정 사항(Fix)&lt;/span&gt;&lt;/b&gt;&lt;span&gt;:&amp;nbsp;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://github.com/vercel/next.js/commit/6ef90ef49fd32171150b6f81d14708aa54cd07b2&quot;&gt;https://github.com/vercel/next.js/commit/6ef90ef49fd32171150b6f81d14708aa54cd07b2&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;color: #5f6368;&quot;&gt;&lt;b&gt;&lt;span&gt;공지&lt;/span&gt;&lt;/b&gt;&lt;span&gt;:&amp;nbsp;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://nextjs.org/blog/CVE-2025-66478&quot;&gt;https://nextjs.org/blog/CVE-2025-66478&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-sfc-cp=&quot;&quot;&gt;&lt;a href=&quot;https://cloud.google.com/blog/ko/products/identity-security/responding-to-cve-2025-55182&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://cloud.google.com/blog/ko/products/identity-security/responding-to-cve-2025-55182&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1767078065734&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;CVE-2025-55182 보안 취약점, 당신의 React와 Next.js 프로젝트를 직접 겨냥합니다. | Google Cloud 블로그&quot; data-og-description=&quot;CVE-2025-55182 취약점으로 인한 React 및 Next.js의 원격 코드 실행 위험을 최소화하려면 다음 권장 사항을 따르세요.&quot; data-og-host=&quot;cloud.google.com&quot; data-og-source-url=&quot;https://cloud.google.com/blog/ko/products/identity-security/responding-to-cve-2025-55182&quot; data-og-url=&quot;https://cloud.google.com/blog/ko/products/identity-security/responding-to-cve-2025-55182&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/beZxXX/hyZPPxTuVS/uPIQkgmFDWjrZxqd7NXn9k/img.jpg?width=2600&amp;amp;height=1280&amp;amp;face=0_0_2600_1280,https://scrap.kakaocdn.net/dn/NgTPH/hyZQJW8vLG/IKdGAtsx7zmgSzmYjOnkl0/img.jpg?width=2600&amp;amp;height=1280&amp;amp;face=0_0_2600_1280,https://scrap.kakaocdn.net/dn/bsg1Bv/hyZQuAtPGL/tNHvPsdKHukUhq7TVsXOa0/img.png?width=953&amp;amp;height=1200&amp;amp;face=0_0_953_1200&quot;&gt;&lt;a href=&quot;https://cloud.google.com/blog/ko/products/identity-security/responding-to-cve-2025-55182&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cloud.google.com/blog/ko/products/identity-security/responding-to-cve-2025-55182&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/beZxXX/hyZPPxTuVS/uPIQkgmFDWjrZxqd7NXn9k/img.jpg?width=2600&amp;amp;height=1280&amp;amp;face=0_0_2600_1280,https://scrap.kakaocdn.net/dn/NgTPH/hyZQJW8vLG/IKdGAtsx7zmgSzmYjOnkl0/img.jpg?width=2600&amp;amp;height=1280&amp;amp;face=0_0_2600_1280,https://scrap.kakaocdn.net/dn/bsg1Bv/hyZQuAtPGL/tNHvPsdKHukUhq7TVsXOa0/img.png?width=953&amp;amp;height=1200&amp;amp;face=0_0_953_1200');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;CVE-2025-55182 보안 취약점, 당신의 React와 Next.js 프로젝트를 직접 겨냥합니다. | Google Cloud 블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;CVE-2025-55182 취약점으로 인한 React 및 Next.js의 원격 코드 실행 위험을 최소화하려면 다음 권장 사항을 따르세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cloud.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;대처&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 취약점으로부터 이슈를 해결하기 위해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 아래 링크를 통해서 react와 next.js를 해결된 버전으로 다시 깔았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;=&amp;gt;&lt;a href=&quot;https://nextjs.org/blog/CVE-2025-66478#required-action&quot;&gt;https://nextjs.org/blog/CVE-2025-66478#required-action&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1767086122010&quot; contenteditable=&quot;false&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/npdeA/hyZQ0xMlOW/WPvgcD82UqAO0jq4bUWD90/img.jpg?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/bqr76A/hyZQDKW4OA/EYr7vo8KLTb9YejYxrI7j1/img.jpg?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628&quot; data-og-url=&quot;https://nextjs.org/blog/CVE-2025-66478&quot; data-og-source-url=&quot;https://nextjs.org/blog/CVE-2025-66478#required-action&quot; data-og-host=&quot;nextjs.org&quot; data-og-description=&quot;A critical vulnerability (CVE-2025-66478) has been identified in the React Server Components protocol. Users should upgrade to patched versions immediately.&quot; data-og-title=&quot;Security Advisory: CVE-2025-66478&quot; data-og-type=&quot;article&quot; data-ke-align=&quot;alignCenter&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://nextjs.org/blog/CVE-2025-66478#required-action&quot; data-source-url=&quot;https://nextjs.org/blog/CVE-2025-66478#required-action&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;Security Advisory: CVE-2025-66478&lt;/p&gt;
&lt;p style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;A critical vulnerability (CVE-2025-66478) has been identified in the React Server Components protocol. Users should upgrade to patched versions immediately.&lt;/p&gt;
&lt;p style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;nextjs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;pre id=&quot;code_1767086993250&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install next@15.0.5   # for 15.0.x
npm install next@15.1.9   # for 15.1.x
npm install next@15.2.6   # for 15.2.x
npm install next@15.3.6   # for 15.3.x
npm install next@15.4.8   # for 15.4.x
npm install next@15.5.7   # for 15.5.x
npm install next@16.0.7   # for 16.0.x
 
npm install next@15.6.0-canary.58   # for 15.x canary releases
npm install next@16.1.0-canary.12   # for 16.x canary releases&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 각 세부 버전별로 픽스된 버전이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components&quot;&gt;https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1767086122010&quot; contenteditable=&quot;false&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gCeJH/hyZQFIMZCb/K1N1BP7Nkvik4KHrx5Kmsk/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/hpkZg/hyZQv7fY7u/Bu8I7n2eJgKKrZRBkeooJK/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567&quot; data-og-url=&quot;https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components&quot; data-og-source-url=&quot;https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components&quot; data-og-host=&quot;react.dev&quot; data-og-description=&quot;The library for web and native user interfaces&quot; data-og-title=&quot;Critical Security Vulnerability in React Server Components &amp;ndash; React&quot; data-og-type=&quot;website&quot; data-ke-align=&quot;alignCenter&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components&quot; data-source-url=&quot;https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;Critical Security Vulnerability in React Server Components &amp;ndash; React&lt;/p&gt;
&lt;p style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;The library for web and native user interfaces&lt;/p&gt;
&lt;p style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;react.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;span style=&quot;background-color: #ffffff; color: #23272f; text-align: start;&quot;&gt;A fix was introduced in versions &lt;/span&gt;&lt;a style=&quot;background-color: #ffffff; color: #087ea4; text-align: start;&quot; href=&quot;https://github.com/facebook/react/releases/tag/v19.0.1&quot;&gt;19.0.1&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; color: #23272f; text-align: start;&quot;&gt;, &lt;/span&gt;&lt;a style=&quot;background-color: #ffffff; color: #087ea4; text-align: start;&quot; href=&quot;https://github.com/facebook/react/releases/tag/v19.1.2&quot;&gt;19.1.2&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; color: #23272f; text-align: start;&quot;&gt;, and &lt;/span&gt;&lt;a style=&quot;background-color: #ffffff; color: #087ea4; text-align: start;&quot; href=&quot;https://github.com/facebook/react/releases/tag/v19.2.1&quot;&gt;19.2.1&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; color: #23272f; text-align: start;&quot;&gt;. If you are using any of the above packages please upgrade to any of the fixed versions immediately.&quot;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 두 링크로부터 알아낸 fix된 버전은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1767086210260&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; npm install next@15.1.9 react@19.0.1 react-dom@19.0.1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 버전들과 같이 가장 인접한 fix된 버전을 설치하여 충돌이 낮지 않게 해주면 해결이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;참고&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 내용의 경우 아래 영상에 잘 나와있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공격 포인트에 대해서 aws에서는 waf 규칙을 강화하였고 facebook은 react에 유효성 체크 로직을 추가하였다. 따라서 업데이트 된 버전을 통해서 해당 공격포인트를 막는 작업을 진행하였다.&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=jOoUb5Dk6jk&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/ejVJl4/hyZQSGxFdb/oeLhbyHvZjWPOOXFxgQhSK/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360,https://scrap.kakaocdn.net/dn/lnFpF/hyZQqZbdYR/eNMi6yRgUzf14qsLFUivdk/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360&quot; data-video-width=&quot;480&quot; data-video-height=&quot;360&quot; data-video-origin-width=&quot;480&quot; data-video-origin-height=&quot;360&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;React server component 취약점 간단히 분석 - CVE-2025-55182, CVE-2025-66478&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/jOoUb5Dk6jk&quot; width=&quot;480&quot; height=&quot;360&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/frontend</category>
      <category>CVE-2025-55182</category>
      <category>CVE-2025-66478</category>
      <category>next.js 취약점</category>
      <category>리엑트 공격포인트</category>
      <category>리엑트 오류</category>
      <author>체인의정석</author>
      <guid isPermaLink="true">https://it-timehacker.tistory.com/575</guid>
      <comments>https://it-timehacker.tistory.com/575#entry575comment</comments>
      <pubDate>Tue, 30 Dec 2025 18:18:51 +0900</pubDate>
    </item>
    <item>
      <title>AWS EC2에서 이미지 생성하고 업데이트하기</title>
      <link>https://it-timehacker.tistory.com/574</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;하게 된 배경 : 이미 존재하는 ec2에서 이미지를 뽑아내고 기존 시스템에서 업데이트 진행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 프로젝트의 구조상 이미지에 이미 필요한 모듈들이나 환경이 세팅되어 있고 이후 시작템플릿에서 해당 이미지를 통해서 환경별로 배포를 하는 식으로 구성이 되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 보안 관련 문제로 기존 2022 버전의 OS를&amp;nbsp; 2023으로 바꾸어야 하여 해당 부분으로 다시 작업을 하기위하여 2022버전에 깔려있는 주요 프로그램을 분석 한 후 동일한 버전으로 2023버전을 만든 후 이미지를 생성하는 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서 :&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 이미지를 생성하기 전에 설치된 패키지의 자동 시작 등록 및 기존의 캐시와 같은 내역들을 지워준다.&lt;/p&gt;
&lt;pre id=&quot;code_1766395606189&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. 서비스 자동 시작 등록 확인 (부팅될 때 알아서 켜지도록)
sudo systemctl enable firewalld
sudo systemctl enable amazon-cloudwatch-agent

# 2. (선택사항) 지금까지 친 명령어 기록 삭제 (보안상 좋음)
history -c
rm -f ~/.bash_history&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;2단계: [AWS 콘솔] 이미지(AMI) 생성&lt;/h3&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;이제 AWS 웹 콘솔로 가서 &quot;찰칵&quot; 사진을 찍습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;AWS EC2 콘솔&lt;/b&gt; 접속 -&amp;gt; &lt;b&gt;인스턴스(Instances)&lt;/b&gt; 메뉴 클릭.&lt;/li&gt;
&lt;/ol&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;방금 세팅 완료한 새 서버와 동일한 서버를 찾아서 우클릭.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이미지 및 템플릿(Image and templates)&lt;/b&gt; -&amp;gt; &lt;b&gt;이미지 생성(Create image)&lt;/b&gt; 클릭.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;451&quot; data-origin-height=&quot;379&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p0k1L/dJMcagYoyAF/AjvsSS2Q2wgOnh9mrGo2gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p0k1L/dJMcagYoyAF/AjvsSS2Q2wgOnh9mrGo2gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p0k1L/dJMcagYoyAF/AjvsSS2Q2wgOnh9mrGo2gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp0k1L%2FdJMcagYoyAF%2FAjvsSS2Q2wgOnh9mrGo2gk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;451&quot; height=&quot;379&quot; data-origin-width=&quot;451&quot; data-origin-height=&quot;379&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;설정 입력:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11,3,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;이미지 이름:&lt;/b&gt; 알아보기 쉽게 지으세요. (예: AMI-AL2023-Base-FirewalldFixed-20251222)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이미지 설명: 이미지에 설치된 주요 모듈들 쓰기&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;재부팅 안 함(No reboot):&lt;/b&gt; &lt;b&gt;체크 해제 권장&lt;/b&gt; (서버를 잠시 끄고 안전하게 찍는 게 좋습니다. 서비스 중이라면 체크하세요.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이미지 생성(Create image)&lt;/b&gt; 버튼 클릭하면 아래와 같이 생성중이라는 단어가 뜸&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;215&quot; data-origin-height=&quot;70&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A4CFQ/dJMcacuVZnO/V7hBFcskg7GvE98wez4i7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A4CFQ/dJMcacuVZnO/V7hBFcskg7GvE98wez4i7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A4CFQ/dJMcacuVZnO/V7hBFcskg7GvE98wez4i7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA4CFQ%2FdJMcacuVZnO%2FV7hBFcskg7GvE98wez4i7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;215&quot; height=&quot;70&quot; data-origin-width=&quot;215&quot; data-origin-height=&quot;70&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;⏳ &lt;b&gt;완료까지 5~10분 정도 걸립니다.&lt;/b&gt; (왼쪽 메뉴 'AMI'에서 상태가 Pending -&amp;gt; Available로 바뀌면 성공!)&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;이후 메뉴 중&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이미지 및 템플릿(Image and templates)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&amp;gt;&lt;span&gt; 시작템플릿 생성&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;클릭.&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;여기서 새 버전으로는 구버전 OS가 아닌 새로운 버전 OS로 시작템플릿을 생성한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;시작템플릿에서도 만약 해당 이미지 변경에 따른 스크립트 변경이 존재한다면 해당 부분도 반영해서 수정해 주어야 한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;시작템플릿에서 내 AMI를 클릭한 후에 방금 만든 이미지이름을 검색해서 설정을 해주면 된다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;새로운 버전의 시작 템플릿에 이미지를 넣게 되면 시작템플릿의 설정대로 ec2가 시작이 되게 되고 이후에&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;58&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ywZV1/dJMcaionfEG/TOpvn80sAUTfMQ0u5wCEf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ywZV1/dJMcaionfEG/TOpvn80sAUTfMQ0u5wCEf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ywZV1/dJMcaionfEG/TOpvn80sAUTfMQ0u5wCEf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FywZV1%2FdJMcaionfEG%2FTOpvn80sAUTfMQ0u5wCEf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;357&quot; height=&quot;58&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;58&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 작성해 둔 AutoScailing 그룹에 들어가서 인스턴스 재시작을 해야 재배포가 가능해진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;139&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1PlLu/dJMcagjNnnt/cwiENWd589bCAaOwRNuCd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1PlLu/dJMcagjNnnt/cwiENWd589bCAaOwRNuCd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1PlLu/dJMcagjNnnt/cwiENWd589bCAaOwRNuCd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1PlLu%2FdJMcagjNnnt%2FcwiENWd589bCAaOwRNuCd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;622&quot; height=&quot;139&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;139&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 시작템플릿의 버전을 새로 만든 버전으로 바꾸고 인스턴스 재시작을 해야 수정된 이미지 파일이 반영이 되게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Autoscaling group에서 종료 전 시작을 누른 후 인스턴스 워밍업 시간을 넉넉히 설정해 둔 후에 일치 건너뛰기를 비활성화 시켜두면 무중단 재배포가 가능해 진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반영이 된 후에는 cloudwatch log insight에서 오류 구문에 대한 검색을 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 AutoScalingGroup으로 관리가 안되고 있다면 업데이트 된 AMI 이미지를 넣어서 시작템플릿을 만들고 그 템플릿으로 인스턴스를 시작하면 해당 이미지와 설정으로 ec2인스턴스가 시작되게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;407&quot; data-origin-height=&quot;153&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAAovR/dJMcaiPp3pb/0S7aKwfymTluGNgKaJBAL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAAovR/dJMcaiPp3pb/0S7aKwfymTluGNgKaJBAL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAAovR/dJMcaiPp3pb/0S7aKwfymTluGNgKaJBAL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAAovR%2FdJMcaiPp3pb%2F0S7aKwfymTluGNgKaJBAL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;407&quot; height=&quot;153&quot; data-origin-width=&quot;407&quot; data-origin-height=&quot;153&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 여러 환경에서 동일한 인스턴스를 띄울때는 이미지, 시작템플릿을 사용하면 좋고 무중단 배포 등에서는 auto scaling group을 사용하면 더 좋다.&lt;/p&gt;</description>
      <category>개발/Infra</category>
      <category>AWS autoscalinggroup</category>
      <category>AWS image</category>
      <category>AWS 이미지</category>
      <category>aws 이미지 생성</category>
      <author>체인의정석</author>
      <guid isPermaLink="true">https://it-timehacker.tistory.com/574</guid>
      <comments>https://it-timehacker.tistory.com/574#entry574comment</comments>
      <pubDate>Tue, 23 Dec 2025 15:20:25 +0900</pubDate>
    </item>
    <item>
      <title>ssh config 파일로 bastion에서 점프해서 ssh 접속하기</title>
      <link>https://it-timehacker.tistory.com/573</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;보안을 위해 보통 서버에 접속할 때는 바로 접속하는것이 아닌 bastion 서버를 두게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bastion의 경우 pem 키만 있으면 접속이 가능하지만 여기서 다른 서버로 들어가기 위해서는&amp;nbsp;한번 더 접속을 해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 대한 작업이 반복되면 번거롭기 때문에 보통 ssh config를 통해서 각 서버에 맞는 명령어를 통해서 한번에 접속을 하도록 세팅을 해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1765446162540&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Host example-bastion-live
    HostName &quot;Pulic IP넣기&quot;
    User ec2-user
    IdentityFile ~/.ssh/example-live.pem
    
Host example-service-live
    HostName &quot;Private IP넣기&quot;
    User ec2-user
    IdentityFile ~/.ssh/example-service-live.pem
    ProxyJump example-bastion-live&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 example이라는 이름의 서비스를 관리할 때 example-bastion-live를 통해서 live환경의 베스쳔에 접속한다면 이때 public IP와 미리 해당 bastion ec2에 지정된 pem키를 넣어서 접근하면 되며, 이후 ProxyJump를 통해서 실제로 해당 베스쳔에 연결된 다른 서비스에 접근 할 수 있다. 이렇게 하면 bastion에 접속 한 후 해당 bastion에서 pem키를 통해서 다른 서버에 접속하는 과정을 쉽게 단축할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용할 때는&lt;/p&gt;
&lt;pre id=&quot;code_1765446508298&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd ~/.ssh
ssh example-service-live
ssh example-bastion-live&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 지정해 둔 이름의 ec2로 접속이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 각 서버에 pem키를 지정해 둘 필요가 없다면 bastion에서 alias로 바로 접속이 가능하지만 보안상 각 ec2마다 허용되는 pem키들을 두고 사용하는것이 좋아 보인다.&lt;/p&gt;</description>
      <category>개발/Infra</category>
      <category>bastion</category>
      <category>bastion config</category>
      <category>bastion proxy jump</category>
      <category>ec2 ssh 접속</category>
      <category>ssh</category>
      <category>ssh config</category>
      <category>ssh proxy jump</category>
      <category>ssh 접속</category>
      <author>체인의정석</author>
      <guid isPermaLink="true">https://it-timehacker.tistory.com/573</guid>
      <comments>https://it-timehacker.tistory.com/573#entry573comment</comments>
      <pubDate>Thu, 11 Dec 2025 18:50:48 +0900</pubDate>
    </item>
    <item>
      <title>S3를 써서 NFT 메타데이터 업로드 하기</title>
      <link>https://it-timehacker.tistory.com/572</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램에서 S3에 접근하기 위해서는 AWS credentials가 먼저 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;aws credentials 확인의 경우 다음과 같은 경로에 세팅 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1763705525128&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cat ~/.aws/credentials
[default]
aws_access_key_id = 
aws_secret_access_key =&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ts에서 사용하는 패키지&lt;/p&gt;
&lt;pre id=&quot;code_1763705596618&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import S3Helper from '@lib/AWS/S3Helper';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 프로필을 사용해서 객체를 만들어 줘야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1763705628547&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const testS3Helper = new S3Helper('us-west-2', 'default');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 정책에 들어가서 해당 IAM계정이 S3에 어떠한 권한이 있는지 체크해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;151&quot; data-origin-height=&quot;39&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTdsRm/dJMcahQmdAU/KzJKcAJrekFMTWCn6vBoB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTdsRm/dJMcahQmdAU/KzJKcAJrekFMTWCn6vBoB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTdsRm/dJMcahQmdAU/KzJKcAJrekFMTWCn6vBoB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTdsRm%2FdJMcahQmdAU%2FKzJKcAJrekFMTWCn6vBoB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;151&quot; height=&quot;39&quot; data-origin-width=&quot;151&quot; data-origin-height=&quot;39&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보아하니 권한 끝에 /*가 붙어 있지 않으면 s3Object를 다룰 수 없다고 하여 붙여 주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1763713304081&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;          await Promise.all([
            ...tokenIds.map((tokenId) =&amp;gt;
              s3Helper.s3
                .copyObject({
                  Bucket: PUBLIC_S3_BUCKETNAME,
                  CopySource: `/${PRIVATE_S3_BUCKETNAME}/nft1/${tokenId}.png`,
                  Key: `testNFT/${tokenId}.png`,
                })
                .promise(),
            ),
          ]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이런식으로 해두면 private에 미리 올려둔 S3에 접근해서 파일을 옮긴다면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 CopySource인 S3에 해당 데이터가 없다면 &quot;Copy failed The specified key does not exist.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 뜨게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이벤트를 구독하면서 nft민팅이 확인된 순간 프라이빗 S3에서 퍼블릭 S3로 옮기게 되면 NFT 민팅이 완성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하는 이유는 위의 명령어는 S3 내부적으로 파일이 이동하는 것임으로 시간이 얼마 걸리지 않으며, nft가 민팅이 확정된 시점에 프라이빗 S3에서 퍼블릭 S3로 옮기면 짧은 시간 내에 nft민팅을 완료활 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 복사해서 옯기는 행위가 아니라 S3에 접근해서 데이터를 수정하고 업로드를 해야하는 경우 다음과 같이 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1764139335357&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    // 메타데이터 JSON 파일 가져와서 수정 후 업로드
    try {
      // 1. projectName 이라는 경로에 있는 S3에서 원본 JSON 파일 가져오기
      const metadataResponse = await s3Helper.s3
        .getObject({
          Bucket: PRIVATE_S3_BUCKETNAME,
          Key: `projectName/metadata/${exampleID}.json`,
        })
        .promise();

      // 2. JSON 파싱
      const metadata = JSON.parse(metadataResponse.Body.toString());

      // 3. 필요한 필드 수정
      metadata.name = `NFT #${tokenId}`;
      metadata.image = `https://${CDN_DOMAIN}/${projectName}/${nftContractAddress}/image/${tokenId}.png`;
      console.log('metadata: ', metadata);
      // 4. 수정된 JSON 업로드
      await s3Helper.upload({
        bucketName: PUBLIC_S3_BUCKETNAME,
        key: `${CONTRACT_DATA_S3_PATH}/${nftContractAddress}/metadata/${tokenId}.json`,
        body: JSON.stringify(metadata),
        contentType: 'application/json',
      });
    } catch (error) {
      console.log(`Failed to modify and upload metadata for tokenId: ${tokenId}, error);
      return;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 S3에 접근해서 이벤트 또는 db기반의 데이터를 읽어와서 메타데이터를 수정하는것이 가능하며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 형식은 메타데이터가 민팅 전에는 비공개로 있다가 공개가 되는 순간 특정 유저에게 특정 상황에 맞는 nft가 민팅되어야 할 때 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/backend(js,ts)</category>
      <category>nft metadat</category>
      <category>s3 copy</category>
      <category>s3 metadata</category>
      <category>s3 사용</category>
      <category>s3 사용법</category>
      <category>S3 사용하기</category>
      <author>체인의정석</author>
      <guid isPermaLink="true">https://it-timehacker.tistory.com/572</guid>
      <comments>https://it-timehacker.tistory.com/572#entry572comment</comments>
      <pubDate>Fri, 21 Nov 2025 17:29:18 +0900</pubDate>
    </item>
    <item>
      <title>비디오 스트리밍 - HLS 모듈 사용하기</title>
      <link>https://it-timehacker.tistory.com/571</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;비디오 스트리밍의 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/video-dev/hls.js&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/video-dev/hls.js&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1763545902049&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - video-dev/hls.js: HLS.js is a JavaScript library that plays HLS in browsers with support for MSE.&quot; data-og-description=&quot;HLS.js is a JavaScript library that plays HLS in browsers with support for MSE. - video-dev/hls.js&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/video-dev/hls.js&quot; data-og-url=&quot;https://github.com/video-dev/hls.js&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/p0QOM/hyZNKbFNNH/dqi03ucKxAxlBGZYOo8i61/img.png?width=640&amp;amp;height=320&amp;amp;face=0_0_640_320,https://scrap.kakaocdn.net/dn/ba1IGT/hyZNA1dA2d/BIgZN8nE10pQmPV4ngK1v1/img.png?width=640&amp;amp;height=320&amp;amp;face=0_0_640_320&quot;&gt;&lt;a href=&quot;https://github.com/video-dev/hls.js&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/video-dev/hls.js&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/p0QOM/hyZNKbFNNH/dqi03ucKxAxlBGZYOo8i61/img.png?width=640&amp;amp;height=320&amp;amp;face=0_0_640_320,https://scrap.kakaocdn.net/dn/ba1IGT/hyZNA1dA2d/BIgZN8nE10pQmPV4ngK1v1/img.png?width=640&amp;amp;height=320&amp;amp;face=0_0_640_320');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - video-dev/hls.js: HLS.js is a JavaScript library that plays HLS in browsers with support for MSE.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;HLS.js is a JavaScript library that plays HLS in browsers with support for MSE. - video-dev/hls.js&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 모듈을 써서 구현하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1763545932367&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/hls.js@1&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;!-- Or if you want the latest version from the main branch --&amp;gt;
&amp;lt;!-- &amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/hls.js@canary&quot;&amp;gt;&amp;lt;/script&amp;gt; --&amp;gt;
&amp;lt;video id=&quot;video&quot;&amp;gt;&amp;lt;/video&amp;gt;
&amp;lt;script&amp;gt;
  var video = document.getElementById('video');
  var videoSrc = 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8';
  if (Hls.isSupported()) {
    var hls = new Hls();
    hls.loadSource(videoSrc);
    hls.attachMedia(video);
  }
  // HLS.js is not supported on platforms that do not have Media Source
  // Extensions (MSE) enabled.
  //
  // When the browser has built-in HLS support (check using `canPlayType`),
  // we can provide an HLS manifest (i.e. .m3u8 URL) directly to the video
  // element through the `src` property. This is using the built-in support
  // of the plain video element, without using HLS.js.
  else if (video.canPlayType('application/vnd.apple.mpegurl')) {
    video.src = videoSrc;
  }
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 CDN을 사용하면 바로 구현이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 html자체로만 띄우면 cors에러가 나기 때문에 pm2로 띄우거나&lt;/p&gt;
&lt;pre id=&quot;code_1763546083747&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python3 -m http.server 8080 --bind 0.0.0.0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 서버를 띄워서 시도해야 한다.&lt;/p&gt;</description>
      <category>개발/backend(js,ts)</category>
      <category>HLS</category>
      <category>hls.js</category>
      <category>비디오 스트리밍</category>
      <category>비디오 스트리밍 모듈</category>
      <author>체인의정석</author>
      <guid isPermaLink="true">https://it-timehacker.tistory.com/571</guid>
      <comments>https://it-timehacker.tistory.com/571#entry571comment</comments>
      <pubDate>Wed, 19 Nov 2025 18:54:54 +0900</pubDate>
    </item>
    <item>
      <title>Live 배포 시 Release Note 작성하기</title>
      <link>https://it-timehacker.tistory.com/570</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;다른 환경과는 다르게 실제 live 배포시에는 릴리즈 노트를 작성해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;들어가야 할 항목은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 새로 추가한 기능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 수정 사항 (PR링크)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 알려진 이슈들 정리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 배포 정보 (젠킨스 잡, git Tag 등과 같이 배포 프로세스별로 구분되는 태그와 릴리스 번호)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 대상환경, 배포서비스 ,Genkins Job, Git tag&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 배포 시간 / 프로세스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 배포시간이 얼마나 걸렸는지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 배포 프로세스에서 체크해야할 사항이 있다면 해당 부분을 체크하였는지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 배포된 서버&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 배포된 서버의 ip 주소 (고정 ip가 아닐 경우 매번 업데이트 진행, 현재 시스템은 bastion만 고정이며 나머지는 고정으로 두지 않음) 및 인스턴스 ID 및 링크 (동일한 서버가 여러대 있을 경우 각 인프라 별로 모두 정리)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 배포시 오토스케일링을 활용하였는데 이러한 경우 다음과 같은 옵션을 미리 정리해둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스 재시작을 할지 (중단 배포, 인스턴스 개수를 0으로 변경 하면 오토스케일링에서 다시 띄움)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스 새로고침을 통하여 업데이트를 할지(무중단 배포, 각 인스턴스 별로 하나를 더 띄운 후 대기 시간을 가지고 새로운 인스턴스가 다 띄워진 이후에 이전 인스턴스를 종료하는 식으로 진행하는 과정, 이때 일치 건너뛰기 옵션을 해제하여 진행하였음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 상황마다 다르게 활용이 가능하다.&lt;/p&gt;</description>
      <category>개발/Infra</category>
      <category>Release Note</category>
      <category>배포 릴리즈노트</category>
      <category>배포기록 관리</category>
      <category>배포기록 관리하기</category>
      <category>서비스 릴리즈 노트</category>
      <category>코드 배포기록 관리</category>
      <author>체인의정석</author>
      <guid isPermaLink="true">https://it-timehacker.tistory.com/570</guid>
      <comments>https://it-timehacker.tistory.com/570#entry570comment</comments>
      <pubDate>Wed, 22 Oct 2025 14:52:48 +0900</pubDate>
    </item>
    <item>
      <title>React) 재랜더링과 메모이제이션  Memo, useMemo &amp;amp; useEffect</title>
      <link>https://it-timehacker.tistory.com/569</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;재랜더링 패턴 3개&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서는 재랜더링이 발생하는 3가지 패턴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. State 업데이트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Props가 변경된 컴포넌트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 재랜더링 된 컴포넌트 아래의 모든 컴포넌트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 재랜더링을 계속하게 될 경우 부하가 많이 발생하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;메모이제이션&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서 컴포넌트, 변수, 함수 등을 재렌더링할 때 제어가 필요한 경우에는 메모이제이션을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모이제이션은 이전 처리 결과를 저장해 둠으로써 처리 속도를 높이는 기술로 필요할 때만 다시 계산하게 하여 불필요한 처리를 줄일 수 있다. 만약 부모가 재랜더링을 하더라도 메모이제이션이 되어 있다면 자녀 컴포넌트의 재랜더링을 막을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 컴포넌트 메모이제이션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;memo를 사용하면 컴포넌트 자체가 메모이제이션이 되면서 이러한 랜더링 과정에서 부모가 바뀌더라도 자식은 바뀌지 않게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 함수 메모이제이션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 함수를 Props로 받은 경우 자식 컴포넌트 입장에서는 Props가 변화했다고 판정해서 카운트업할 때마다 재랜더링을 하게 된다. 리액트에서는 함수 메모이제이션 기능인 UseCallBack을 제공하며 useCallBack의 경우 첫 번째 인수에 함수, 두번ㄷ째 인수에 useEffect와 같은 의존 배열을 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 변수 메모이제이션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useMemo를 사용하면 변수 정의를 반복해서 하는 부하를 줄일 수 있다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;사용 패턴&amp;gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;span&gt;패턴 1:&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;의&lt;/span&gt;&lt;span&gt;존성이 없는 경우 (가&lt;/span&gt;&lt;span&gt;장 일반적)&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1758792356439&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ✅ 가장 일반적 - 의존성 배열이 비어있음
useEffect(() =&amp;gt; {
    fetchData();
}, []); // 빈 배열로 마운트 시에만 실행

const fetchData = async () =&amp;gt; {
    // API 호출
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;span&gt;패턴&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;2: 의&lt;/span&gt;&lt;span&gt;존성이 있는&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;경우&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;div id=&quot;4a80f71d-1672-48c0-b9fb-942a750951bb&quot;&gt;
&lt;div style=&quot;background-color: #141414;&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1758792392940&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ✅ 의존성이 있을 때는 useCallback 사용
const fetchData = useCallback(async () =&amp;gt; {
    // API 호출
}, [userId, page]); // 의존성 명시

useEffect(() =&amp;gt; {
    fetchData();
}, [fetchData]);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;span&gt;패턴&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;3: 직접 useEffec&lt;/span&gt;&lt;span&gt;t 내&lt;/span&gt;&lt;span&gt;부에서 처리&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1758792445550&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ✅ 간단한 경우 직접 처리
useEffect(() =&amp;gt; {
    const fetchData = async () =&amp;gt; {
        const res = await api.getData();
        setData(res);
    };
    fetchData();
}, [userId]); // 의존성만 명시&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&quot;606aaefe-28fb-4ca5-941c-0e954ab641c5&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #141414;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;언제&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;메모&lt;/span&gt;&lt;span&gt;이제&lt;/span&gt;&lt;span&gt;이션을&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;사용해야&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;할까?&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;span&gt;us&lt;/span&gt;&lt;span&gt;eCal&lt;/span&gt;&lt;span&gt;lback&lt;/span&gt;&lt;span&gt;이&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;필요한 경&lt;/span&gt;&lt;span&gt;우:&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-indent=&quot;0&quot;&gt;&lt;span&gt;함수가 여러&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;곳에서&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;재&lt;/span&gt;&lt;span&gt;사&lt;/span&gt;&lt;span&gt;용됨&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-indent=&quot;0&quot;&gt;&lt;span&gt;함&lt;/span&gt;&lt;span&gt;수가 다른&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;useEffect&lt;/span&gt;의 의존성으로&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;사용됨&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-indent=&quot;0&quot;&gt;&lt;span&gt;함수&lt;/span&gt;&lt;span&gt;가 자식&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;컴포넌트에&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;props로&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;전달&lt;/span&gt;&lt;span&gt;됨&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-indent=&quot;0&quot;&gt;&lt;span&gt;함수 내부에서&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;많&lt;/span&gt;&lt;span&gt;은 상태값을&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;참조함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;span&gt;us&lt;/span&gt;&lt;span&gt;eCa&lt;/span&gt;&lt;span&gt;llbac&lt;/span&gt;&lt;span&gt;k이&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;불필&lt;/span&gt;&lt;span&gt;요한&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;경&lt;/span&gt;&lt;span&gt;우&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-indent=&quot;0&quot;&gt;&lt;span&gt;함수가&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;useEffect&lt;/span&gt;&amp;nbsp;내부에서만 사용됨&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-indent=&quot;0&quot;&gt;&lt;span&gt;함수가&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;단순&lt;/span&gt;&lt;span&gt;하&lt;/span&gt;&lt;span&gt;고 의&lt;/span&gt;&lt;span&gt;존성이&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;적음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-indent=&quot;0&quot;&gt;&lt;span&gt;컴포넌트가&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;자주 리렌더링되지 않음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;3.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;실제 예시&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;비교&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;span&gt;❌ 문제가&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;있는 코&lt;/span&gt;&lt;span&gt;드:&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1758792602131&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const MyComponent = () =&amp;gt; {
    const [data, setData] = useState([]);
    const [userId, setUserId] = useState(1);

    // 매번 새로운 함수가 생성됨
    const fetchData = async () =&amp;gt; {
        const res = await api.getData(userId);
        setData(res);
    };

    // 무한 루프 발생!
    useEffect(() =&amp;gt; {
        fetchData();
    }, [fetchData]); // fetchData가 매번 변경됨
};&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&quot;1da5f2f7-00f3-432b-a0e2-4cb928707a84&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #141414;&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;span&gt;✅&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;해결&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;방법 1:&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;useCallback&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;사&lt;/span&gt;&lt;span&gt;용&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1758792545925&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const MyComponent = () =&amp;gt; {
    const [data, setData] = useState([]);
    const [userId, setUserId] = useState(1);

    const fetchData = useCallback(async () =&amp;gt; {
        const res = await api.getData(userId);
        setData(res);
    }, [userId]); // userId가 변경될 때만 함수 재생성

    useEffect(() =&amp;gt; {
        fetchData();
    }, [fetchData]);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;span&gt;✅ 해&lt;/span&gt;&lt;span&gt;결 방법 2&lt;/span&gt;&lt;span&gt;: 직접 처&lt;/span&gt;&lt;span&gt;리 (더 간단&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1758792567425&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const MyComponent = () =&amp;gt; {
    const [data, setData] = useState([]);
    const [userId, setUserId] = useState(1);

    useEffect(() =&amp;gt; {
        const fetchData = async () =&amp;gt; {
            const res = await api.getData(userId);
            setData(res);
        };
        fetchData();
    }, [userId]); // userId가 변경될 때만 실행
};&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;4.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;권장사항&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;span&gt;간단한&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;경우&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(권장):&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1758792660378&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useEffect(() =&amp;gt; {
    const fetchData = async () =&amp;gt; {
        // API 호출
    };
    fetchData();
}, [dependency]);&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&quot;4f42f108-b154-4767-8257-5496ad7dda79&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #141414;&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;span&gt;복잡한&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;경우:&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;div id=&quot;ea9c0dfe-d30c-4da7-9432-88504c3fbf63&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #141414;&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1758792668139&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const fetchData = useCallback(async () =&amp;gt; {
    // 복잡한 로직
}, [dependencies]);

useEffect(() =&amp;gt; {
    fetchData();
}, [fetchData]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/frontend</category>
      <category>react</category>
      <category>react callback</category>
      <category>react useCallback</category>
      <category>react useEffect</category>
      <category>react 메모이제이션</category>
      <category>react 메모이제이션 설명</category>
      <category>useMemo</category>
      <author>체인의정석</author>
      <guid isPermaLink="true">https://it-timehacker.tistory.com/569</guid>
      <comments>https://it-timehacker.tistory.com/569#entry569comment</comments>
      <pubDate>Thu, 25 Sep 2025 18:33:35 +0900</pubDate>
    </item>
    <item>
      <title>로컬에서 go 공통 모듈을 수정하고 싶을 때 (replace)</title>
      <link>https://it-timehacker.tistory.com/568</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;go로 백엔드를 작업할 때 보통 자체적인 go 모듈을 go common과 같은 이름으로 넣고 프로젝트에서 쓰는 레포에서 공통적으로 사용하는 경우가 많다고 한다. node.js나 nest를 할 때는 거의 퍼블릭 모듈만 썼었는데 go는 자체적으로 구현하는 회사들이 더 많은거 같다. 이렇게 자체 모듈을 쓰는 경우 코드를 수정할 때 common모듈과 함께 프로젝트 수정까지 들어가는 경우가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴 때는 go.mod에서 원래대로 정의를 해준 후에 하단에&lt;/p&gt;
&lt;pre id=&quot;code_1758092102147&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;replace github.com/레포이름/go-common =&amp;gt; /Users/chohyunki/go/src/go-common&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 replace 명령어를 써서 정의를 해두면 로컬에 있는 레포 기준으로 작동이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작동이 모두 잘 되는것을 로컬에서 확인하면 go-common과 함께 원래 부분을 수정해서 올리고 배포하면 된다.&lt;/p&gt;</description>
      <category>개발/backend(go)</category>
      <category>go common</category>
      <category>go replace</category>
      <category>go 공통 모듈 수정</category>
      <category>go 공통 모듈 테스트</category>
      <category>go 공통모듈 관리</category>
      <category>go-common</category>
      <author>체인의정석</author>
      <guid isPermaLink="true">https://it-timehacker.tistory.com/568</guid>
      <comments>https://it-timehacker.tistory.com/568#entry568comment</comments>
      <pubDate>Wed, 17 Sep 2025 15:57:03 +0900</pubDate>
    </item>
    <item>
      <title>Ts에서 export가 하나도 정의되어 있지 않은 경우 (모듈 vs 파일)</title>
      <link>https://it-timehacker.tistory.com/567</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;현상 : 평소에 모듈의 경우 모듈 정의를 다 하고 상용하였으나, 인계 받은 코드에서 export에 대한 선언 없이 모듈처럼 특정 파일을 사용하고 있는 상황. 하지만 하나라도 export를 붙이면 컴파일 오류가 발생. 해당 방식으로 사용되는 케이스는 처음봐서 어떠한 원리로 동작하는지 파악&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과 : typescript가 파일 자체를 모듈로 취급할지 아니면 스크립트로 취급할지를 결정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 안에 export나 import가 한 줄도 없으면 해당 파일은 모듈이 아닌 전역스코프에 포함되는 스크립트로 취급 이에 따라서 import 구문 없이 바로 사용가능하지만 충돌할 위험이 커지기 때문에 사용이 권장되지는 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통은 export를 정의하고 import를 정의해서 사용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TypeScript에서는 ECMAScript 2015(ES6)와 마찬가지로, 최상위 레벨에 &lt;b&gt;import나 export가 있는 모든 파일은 모듈로 간주&lt;/b&gt;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로, 최상위 import나 export 선언이 없는 파일은 그 내용이 &lt;b&gt;전역 스코프에서 사용 가능한 스크립트로 취급&lt;/b&gt;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.typescriptlang.org/ko/docs/handbook/modules.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.typescriptlang.org/ko/docs/handbook/modules.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1755676790484&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Documentation - Modules&quot; data-og-description=&quot;How modules work in TypeScript&quot; data-og-host=&quot;www.typescriptlang.org&quot; data-og-source-url=&quot;https://www.typescriptlang.org/ko/docs/handbook/modules.html&quot; data-og-url=&quot;https://www.typescriptlang.org/ko/docs/handbook/modules.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.typescriptlang.org/ko/docs/handbook/modules.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.typescriptlang.org/ko/docs/handbook/modules.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Documentation - Modules&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;How modules work in TypeScript&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.typescriptlang.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*일단 기존의 코드의 수정사항이 많기 때문에 내버려두고 export를 후에 처리하기로 결정, 작동은 하지만 매우 안좋은 코딩 방식으로 생각 됨&lt;/p&gt;</description>
      <category>개발/frontend</category>
      <category>export 선언</category>
      <category>ts export 활용</category>
      <category>ts export선언</category>
      <category>ts 모듈과 파일 차이</category>
      <category>ts 파일과 모듈 차이</category>
      <category>타입스크립트 모듈 사용</category>
      <author>체인의정석</author>
      <guid isPermaLink="true">https://it-timehacker.tistory.com/567</guid>
      <comments>https://it-timehacker.tistory.com/567#entry567comment</comments>
      <pubDate>Wed, 20 Aug 2025 17:01:58 +0900</pubDate>
    </item>
  </channel>
</rss>