<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Security on Yarang's Tech Lair</title><link>https://blog.agentthread.dev/ko/tags/security/</link><description>Recent content in Security on Yarang's Tech Lair</description><generator>Hugo -- gohugo.io</generator><language>ko</language><lastBuildDate>Sat, 27 Jun 2026 09:01:26 +0900</lastBuildDate><atom:link href="https://blog.agentthread.dev/ko/tags/security/index.xml" rel="self" type="application/rss+xml"/><item><title>[MicroVM] Firecracker를 활용한 고립형 샌드박스 구축 가이드</title><link>https://blog.agentthread.dev/ko/post/microvm-firecracker%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EA%B3%A0%EB%A6%BD%ED%98%95-%EC%83%8C%EB%93%9C%EB%B0%95%EC%8A%A4-%EA%B5%AC%EC%B6%95-%EA%B0%80%EC%9D%B4%EB%93%9C/</link><pubDate>Sat, 27 Jun 2026 09:01:26 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/microvm-firecracker%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EA%B3%A0%EB%A6%BD%ED%98%95-%EC%83%8C%EB%93%9C%EB%B0%95%EC%8A%A4-%EA%B5%AC%EC%B6%95-%EA%B0%80%EC%9D%B4%EB%93%9C/</guid><description>&lt;h1 id="microvm-firecracker를-활용한-고립형-샌드박스-구축-가이드"&gt;[MicroVM] Firecracker를 활용한 고립형 샌드박스 구축 가이드
&lt;/h1&gt;&lt;p&gt;최근 &lt;strong&gt;MicroVM&lt;/strong&gt; 기술이 주목받고 있습니다. 전통적인 컨테이너(Docker 등)는 커널을 호스트와 공유하기 때문에 보안 격리(Isolation) 측면에서 완벽하다고 할 수 없습니다. 반면, AWS의 Firecracker와 같은 MicroVM은 가벼운 가상머신을 제공하여 &amp;lsquo;컨테이너의 속도&amp;rsquo;와 &amp;lsquo;VM의 보안&amp;rsquo;을 동시에 잡을 수 있습니다.&lt;/p&gt;
&lt;p&gt;이번 글에서는 Rust 기반 에이전트 런타임인 ZeroClaw의 통신 프로토콜 설계 과정에서 고려한 &lt;strong&gt;Firecracker MicroVM&lt;/strong&gt;을 사용하여, 신뢰할 수 없는 코드를 안전하게 실행하는 방법을 실용적인 코드와 함께 소개합니다.&lt;/p&gt;
&lt;h2 id="왜-microvm인가"&gt;왜 MicroVM인가?
&lt;/h2&gt;&lt;p&gt;기존의 블로그 API 서버나 MCP(Model Context Protocol) 클라이언트 개발에서는 주로 컨테이너 환경을 사용했습니다. 하지만 멀티 에이전트 시스템이나 외부 코드를 실행해야 하는 환경에서는 보안이 더욱 중요해집니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;단일 프로세스 격리:&lt;/strong&gt; 각 MicroVM은 독립적인 커널 공간을 가집니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;빠른 부팅:&lt;/strong&gt; 밀리초 단위의 부팅 시간으로 컨테이너와 유사한 속도를 냅니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;리소스 제한:&lt;/strong&gt; CPU 및 메모리 사용량을 엄격하게 제한할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="사전-준비-사항"&gt;사전 준비 사항
&lt;/h2&gt;&lt;p&gt;이 가이드를 따라하려면 Linux 환경(KVM 활성화)과 Rust 툴체인이 필요합니다. Firecracker는 Linux KVM(Kernel-based Virtual Machine) 위에서 동작합니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# KVM 권한 확인&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ls -la /dev/kvm
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 그룹에 추가 (권한이 없는 경우)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo usermod -aG kvm $USER
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="1-firecracker-다운로드-및-준비"&gt;1. Firecracker 다운로드 및 준비
&lt;/h2&gt;&lt;p&gt;Firecracker는 단일 바이너리로 제공되어 배포가 매우 간편합니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd /tmp
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -L -o firecracker-v1.9.1 https://github.com/firecracker-microvm/firecracker/releases/download/v1.9.1/firecracker-v1.9.1-x86_64
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;chmod +x firecracker-v1.9.1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mv firecracker-v1.9.1 firecracker
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="2-microvm-부팅을-위한-루트-파일시스템-생성"&gt;2. MicroVM 부팅을 위한 루트 파일시스템 생성
&lt;/h2&gt;&lt;p&gt;MicroVM을 실행하려면 간단한 Linux 커널과 루트 파일시스템(이미지)이 필요합니다. 테스트를 위해 Ubuntu 이미지를 다운로드하겠습니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 작업 디렉토리 생성&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir -p fc-demo
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd fc-demo
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Ubuntu 22.04 루트 파일시스템 다운로드&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;wget https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64-root.tar.xz
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 이미지 변환 (raw 형식)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;xz -d ubuntu-22.04-server-cloudimg-amd64-root.tar.xz
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="3-rust로-제어-로직-작성하기"&gt;3. Rust로 제어 로직 작성하기
&lt;/h2&gt;&lt;p&gt;이제 Rust를 사용하여 Firecracker 인스턴스를 구동하고, 네트워크를 설정하며, 요청을 보내보겠습니다. &lt;code&gt;serde_json&lt;/code&gt;을 사용하여 설정을 JSON으로 전달하는 방식이 가장 일반적입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cargo.toml dependencies:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#a6e22e"&gt;dependencies&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;serde&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;derive&amp;#34;&lt;/span&gt;] }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;serde_json&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;reqwest&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;0.12&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;blocking&amp;#34;&lt;/span&gt;] }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Rust 구현 코드 (&lt;code&gt;main.rs&lt;/code&gt;):&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::fs::{self, File};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::io::Write;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::process::{Command, Stdio};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; serde::Serialize;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Firecracker 설정 구조체
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[derive(Serialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;BootSource&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; kernel_image_path: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; boot_args: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[derive(Serialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Drive&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; drive_id: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; path_on_host: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; is_root_device: &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; is_read_only: &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[derive(Serialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MachineConfig&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vcpu_count: &lt;span style="color:#66d9ef"&gt;u8&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mem_size_mib: &lt;span style="color:#66d9ef"&gt;u64&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ht_enabled: &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[derive(Serialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NetworkInterface&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if_id: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; guest_mac: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; host_dev_name: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 1. 작업 디렉토리 설정
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; vm_id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;my-first-microvm&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; fs::create_dir_all(&lt;span style="color:#a6e22e"&gt;format!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/tmp/fc-demo/&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, vm_id)).expect(&lt;span style="color:#e6db74"&gt;&amp;#34;Failed to create vm dir&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 2. Firecracker 프로세스 실행 (백그라운드)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 실제 환경에서는 데몬으로 관리하거나 API를 통해 소켓을 생성합니다.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 여기서는 간단히 API 서버를 소켓으로 실행한다고 가정하고 설정을 전송합니다.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 주의: 실제 실행하려면 커널 이미지(vmlinux)가 필요합니다. 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// AWS Firecracker GitHub 릴리즈에서 다운로드 가능합니다.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; kernel_path &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/tmp/fc-demo/vmlinux&amp;#34;&lt;/span&gt;; &lt;span style="color:#75715e"&gt;// 경로 확인 필요
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; rootfs_path &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/tmp/fc-demo/ubuntu-22.04-server-cloudimg-amd64-root.tar&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// (실제 튜토리얼에서는 네트워크 설정이 복잡하므로 TAP 디바이스 생성 생략)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// API 설정 생성 예시
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; boot_config &lt;span style="color:#f92672"&gt;=&lt;/span&gt; BootSource {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; kernel_image_path: &lt;span style="color:#a6e22e"&gt;kernel_path&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; boot_args: &lt;span style="color:#e6db74"&gt;&amp;#34;console=ttyS0 reboot=k panic=1 pci=off&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; drive_config &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Drive {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; drive_id: &lt;span style="color:#e6db74"&gt;&amp;#34;rootfs&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; path_on_host: &lt;span style="color:#a6e22e"&gt;rootfs_path&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; is_root_device: &lt;span style="color:#a6e22e"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; is_read_only: &lt;span style="color:#a6e22e"&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// JSON 파일로 설정 저장 (Firecracker는 HTTP API나 JSON 파일 입력을 받음)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; config_json &lt;span style="color:#f92672"&gt;=&lt;/span&gt; serde_json::to_string_pretty(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;vec!&lt;/span&gt;[&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;boot_config, &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;drive_config]).unwrap();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; file &lt;span style="color:#f92672"&gt;=&lt;/span&gt; File::create(&lt;span style="color:#a6e22e"&gt;format!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/tmp/fc-demo/&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/vm_config.json&amp;#34;&lt;/span&gt;, vm_id)).unwrap();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; file.write_all(config_json.as_bytes()).unwrap();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;MicroVM 설정 완료: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, vm_id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Firecracker는 보통 TAP 네트워크와 마운트 설정이 필요합니다.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;CLI 사용법 예시:&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;./firecracker --api-sock /tmp/fc-demo/&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/api.sock&amp;#34;&lt;/span&gt;, vm_id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;이후 curl을 통해 http://localhost/... 로 설정을 전송합니다.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="4-실행-및-제어-cli-방식"&gt;4. 실행 및 제어 (CLI 방식)
&lt;/h2&gt;&lt;p&gt;Rust 코드로 자동화하는 것이 이상적이지만, 개발 단계에서는 CLI로 직접 제어하며 동작을 확인하는 것이 좋습니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 1. TAP 네트워크 인터페이스 생성 (호스트와 게스트 연결용)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo ip tuntap add tap0 mode tap
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo ip addr add 169.254.0.1/30 dev tap0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo ip link set tap0 up
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 2. Firecracker 실행 (API 소켓 모드)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;./firecracker --api-sock /tmp/fc-demo/my-api.sock &amp;amp;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 3. 부팅 소스 설정 전송&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl --unix-socket /tmp/fc-demo/my-api.sock -i &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -X PUT &lt;span style="color:#e6db74"&gt;&amp;#39;http://localhost/boot-source&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#39;Accept: application/json&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#39;Content-Type: application/json&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -d &lt;span style="color:#e6db74"&gt;&amp;#39;{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;kernel_image_path&amp;#34;: &amp;#34;/path/to/vmlinux&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;boot_args&amp;#34;: &amp;#34;console=ttyS0 reboot=k panic=1 pci=off&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; }&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 4. 마운트 설정 전송&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl --unix-socket /tmp/fc-demo/my-api.sock -i &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -X PUT &lt;span style="color:#e6db74"&gt;&amp;#39;http://localhost/drives/rootfs&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#39;Accept: application/json&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#39;Content-Type: application/json&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -d &lt;span style="color:#e6db74"&gt;&amp;#39;{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;drive_id&amp;#34;: &amp;#34;rootfs&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;path_on_host&amp;#34;: &amp;#34;/path/to/rootfs.ext4&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;is_root_device&amp;#34;: true,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;is_read_only&amp;#34;: false
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; }&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 5. 인스턴스 시작&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl --unix-socket /tmp/fc-demo/my-api.sock -i &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -X PUT &lt;span style="color:#e6db74"&gt;&amp;#39;http://localhost/actions&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#39;Accept: application/json&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#39;Content-Type: application/json&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -d &lt;span style="color:#e6db74"&gt;&amp;#39;{ &amp;#34;action_type&amp;#34;: &amp;#34;InstanceStart&amp;#34; }&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="결론-및-zeroclaw-적용-계획"&gt;결론 및 ZeroClaw 적용 계획
&lt;/h2&gt;&lt;p&gt;위에서 살펴본 것처럼 MicroVM은 강력한 격리성을 제공합니다. &lt;strong&gt;ZeroClaw&lt;/strong&gt; 프로젝트의 &amp;lsquo;멀티 에이전트 아키텍처&amp;rsquo;에서 이 기술은 매우 유용합니다. 각 에이전트가 수행하는 작업(예: 파일 시스템 접근, 외부 네트워크 요청)을 독립된 Firecracker 인스턴스 내에서 실행하게 하여, 시스템 전체의 안정성을 해치지 않고 &amp;lsquo;신뢰할 수 없는 플러그인&amp;rsquo;을 실행할 수 있게 됩니다.&lt;/p&gt;
&lt;p&gt;다음 포스트에서는 이 MicroVM 위에서 동작하는 에이전트 간의 통신을 어떻게 최적화할지에 대해 다루겠습니다.&lt;/p&gt;
&lt;h2 id="참고자료"&gt;참고자료
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/firecracker-microvm/firecracker" target="_blank" rel="noopener"
 &gt;Firecracker GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[MicroVMs: Run isolated sandboxes with full lifecycle control](Hacker News Link)&lt;/li&gt;
&lt;li&gt;[ZeroClaw 아키텍처 설계문서](내부 블로그 링크)&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Zero-Touch OAuth 구현을 위한 MCP 인증 아키텍처 설계</title><link>https://blog.agentthread.dev/ko/post/zero-touch-oauth-%EA%B5%AC%ED%98%84%EC%9D%84-%EC%9C%84%ED%95%9C-mcp-%EC%9D%B8%EC%A6%9D-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%84%A4%EA%B3%84/</link><pubDate>Fri, 19 Jun 2026 09:00:58 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/zero-touch-oauth-%EA%B5%AC%ED%98%84%EC%9D%84-%EC%9C%84%ED%95%9C-mcp-%EC%9D%B8%EC%A6%9D-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%84%A4%EA%B3%84/</guid><description>&lt;p&gt;최근 &amp;lsquo;Zero-Touch OAuth for MCP&amp;rsquo;라는 흥미로운 기술 트렌드를 접했습니다. 우리가 개발 중인 &lt;code&gt;ZeroClaw&lt;/code&gt; 런타임과 &lt;code&gt;blog-api-server&lt;/code&gt;의 MCP(Model Context Protocol) 통합 과정에서, 사용자 경험을 저해하지 않으면서도 보안성을 확보하는 것은 필수적인 과제입니다. 이번 포스트에서는 별도의 복잡한 설정 없이 사용자가 MCP 클라이언트를 연결할 수 있도록 돕는 &amp;lsquo;Zero-Touch&amp;rsquo; 인증 흐름을 설계하고, 이를 실제 코드로 구현하는 방법을 살펴보겠습니다.&lt;/p&gt;
&lt;h3 id="왜-zero-touch인가"&gt;왜 Zero-Touch인가?
&lt;/h3&gt;&lt;p&gt;기존의 OAuth 2.0 흐름은 사용자에게 &amp;lsquo;Client ID&amp;rsquo;와 &amp;lsquo;Client Secret&amp;rsquo;을 발급받아 설정 파일에 입력하도록 요구하곤 했습니다. 하지만 일반 사용자나 개발자에게 이는 큰 진입 장벽입니다. Zero-Touch OAuth는 클라이언트가 사전에 등록된 정보(Pre-registered metadata)를 통해 자동으로 인증을 시도하고, 사용자는 단순히 &amp;lsquo;허용&amp;rsquo; 버튼만 누르면 되는 흐름을 지향합니다.&lt;/p&gt;
&lt;h3 id="아키텍처-설계"&gt;아키텍처 설계
&lt;/h3&gt;&lt;p&gt;우리는 &lt;code&gt;blog-api-server&lt;/code&gt;를 MCP Provider로, &lt;code&gt;Claude Code&lt;/code&gt;나 커스텀 클라이언트를 MCP Consumer로 구성합니다. 핵심은 &lt;strong&gt;PKCE (Proof Key for Code Exchange)&lt;/strong&gt; 확장을 사용하여 Secret을 저장하지 않는 Public Client 환경을 구축하는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;핵심 구성 요소:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;MCP Client (Consumer)&lt;/strong&gt;: 사용자의 로컬 환경에서 실행되는 에이전트입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auth Server&lt;/strong&gt;: &lt;code&gt;blog-api-server&lt;/code&gt; 내에 구현된 OAuth2 인증 서버입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resource Server&lt;/strong&gt;: 실제 블로그 API입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="구현-단계-1-서버-사이드-blog-api-server"&gt;구현 단계 1: 서버 사이드 (blog-api-server)
&lt;/h3&gt;&lt;p&gt;먼저, Rust 기반의 &lt;code&gt;blog-api-server&lt;/code&gt;에 간단한 인증 엔드포인트를 추가해야 합니다. 이전 글인 *&amp;rsquo;[blog-api-server] MCP 블로그 클라이언트 언어 파라미터 추가&amp;rsquo;*에서 언급된 아키텍처를 확장하여 Auth 모듈을 독립시킵니다.&lt;/p&gt;
&lt;p&gt;여기서는 핵심 로직인 &lt;strong&gt;코드 검증(Verification)&lt;/strong&gt; 로직을 보여드리겠습니다. 클라이언트로부터 받은 &lt;code&gt;code_verifier&lt;/code&gt;를 검증하는 과정입니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// blog-api-server/src/auth/handler.rs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; axum::{extract::State, Json};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; serde::{Deserialize, Serialize};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; sha2::{Digest, Sha256};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; base64::Engine &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; _;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[derive(Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TokenRequest&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; code: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; code_verifier: String, &lt;span style="color:#75715e"&gt;// PKCE를 통해 클라이언트가 생성한 난수
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; redirect_uri: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[derive(Serialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TokenResponse&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; access_token: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; token_type: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; expires_in: &lt;span style="color:#66d9ef"&gt;u64&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;exchange_token&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; State(state): &lt;span style="color:#a6e22e"&gt;State&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AppState&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Json(payload): &lt;span style="color:#a6e22e"&gt;Json&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;TokenRequest&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Json&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;TokenResponse&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;, String&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 1. DB에서 Authorization Code 조회
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; auth_record &lt;span style="color:#f92672"&gt;=&lt;/span&gt; state.db.get_auth_code(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;payload.code)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .map_err(&lt;span style="color:#f92672"&gt;|&lt;/span&gt;_&lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Invalid code&amp;#34;&lt;/span&gt;.to_string())&lt;span style="color:#f92672"&gt;?&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 2. PKCE Challenge 검증 (핵심 보안 로직)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; hasher &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Sha256::new();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hasher.update(payload.code_verifier.as_bytes());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; hashed_verifier &lt;span style="color:#f92672"&gt;=&lt;/span&gt; hasher.finalize();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; encoded_verifier &lt;span style="color:#f92672"&gt;=&lt;/span&gt; base64::engine::general_purpose::&lt;span style="color:#66d9ef"&gt;URL_SAFE_NO_PAD&lt;/span&gt;.encode(hashed_verifier);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; encoded_verifier &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; auth_record.code_challenge {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Err(&lt;span style="color:#e6db74"&gt;&amp;#34;Code verifier mismatch&amp;#34;&lt;/span&gt;.to_string());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 3. Access Token 발급
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; access_token &lt;span style="color:#f92672"&gt;=&lt;/span&gt; generate_jwt(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;state.secrets, &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;auth_record.user_id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(Json(TokenResponse {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; access_token,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; token_type: &lt;span style="color:#e6db74"&gt;&amp;#34;Bearer&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; expires_in: &lt;span style="color:#ae81ff"&gt;3600&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;이 코드는 클라이언트가 보내온 &lt;code&gt;code_verifier&lt;/code&gt;를 해싱하여, 처음 인증 요청을 보낼 때 생성된 &lt;code&gt;code_challenge&lt;/code&gt;와 일치하는지 확인합니다. 이 과정이 &amp;lsquo;Zero-Touch&amp;rsquo;를 보안적으로 가능하게 만드는 핵심입니다.&lt;/p&gt;
&lt;h3 id="구현-단계-2-클라이언트-사이드-mcp-client"&gt;구현 단계 2: 클라이언트 사이드 (MCP Client)
&lt;/h3&gt;&lt;p&gt;이제 클라이언트는 사용자 개입 없이(혹은 최소한의 개입으로) 토큰을 발급받을 수 있습니다. Python을 사용한 MCP 클라이언트 예제를 작성해보겠습니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; hashlib
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; base64
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; requests
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; urllib.parse &lt;span style="color:#f92672"&gt;import&lt;/span&gt; urlencode
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Zero-Touch 설정 (하드코딩 혹은 환경 변수)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;CLIENT_ID &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;zeroclaw-mcp-client&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;AUTH_URL &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;https://api.myblog.com/oauth/authorize&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TOKEN_URL &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;https://api.myblog.com/oauth/token&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;REDIRECT_URI &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:3000/callback&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# PKCE Code Verifier 생성&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;random_bytes &lt;span style="color:#f92672"&gt;=&lt;/span&gt; os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;urandom(&lt;span style="color:#ae81ff"&gt;32&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;code_verifier &lt;span style="color:#f92672"&gt;=&lt;/span&gt; base64&lt;span style="color:#f92672"&gt;.&lt;/span&gt;urlsafe_b64encode(random_bytes)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;rstrip(&lt;span style="color:#e6db74"&gt;b&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;=&amp;#39;&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;decode(&lt;span style="color:#e6db74"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Code Challenge 생성&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;challenge_digest &lt;span style="color:#f92672"&gt;=&lt;/span&gt; hashlib&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sha256(code_verifier&lt;span style="color:#f92672"&gt;.&lt;/span&gt;encode(&lt;span style="color:#e6db74"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;))&lt;span style="color:#f92672"&gt;.&lt;/span&gt;digest()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;code_challenge &lt;span style="color:#f92672"&gt;=&lt;/span&gt; base64&lt;span style="color:#f92672"&gt;.&lt;/span&gt;urlsafe_b64encode(challenge_digest)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;rstrip(&lt;span style="color:#e6db74"&gt;b&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;=&amp;#39;&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;decode(&lt;span style="color:#e6db74"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;get_auth_url&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; params &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;response_type&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;code&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;client_id&amp;#34;&lt;/span&gt;: CLIENT_ID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;redirect_uri&amp;#34;&lt;/span&gt;: REDIRECT_URI,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;scope&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;mcp:read mcp:write&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;code_challenge&amp;#34;&lt;/span&gt;: code_challenge,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;code_challenge_method&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;S256&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;AUTH_URL&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;?&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;urlencode(params)&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;exchange_code_for_token&lt;/span&gt;(auth_code):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;grant_type&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;authorization_code&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;code&amp;#34;&lt;/span&gt;: auth_code,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;redirect_uri&amp;#34;&lt;/span&gt;: REDIRECT_URI,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;client_id&amp;#34;&lt;/span&gt;: CLIENT_ID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;code_verifier&amp;#34;&lt;/span&gt;: code_verifier &lt;span style="color:#75715e"&gt;# 서버로 전송하여 검증받음&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; response &lt;span style="color:#f92672"&gt;=&lt;/span&gt; requests&lt;span style="color:#f92672"&gt;.&lt;/span&gt;post(TOKEN_URL, data&lt;span style="color:#f92672"&gt;=&lt;/span&gt;data)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; response&lt;span style="color:#f92672"&gt;.&lt;/span&gt;json()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 사용 예시&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 1. 사용자가 get_auth_url()를 통해 브라우저에서 인증 (최초 1회)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 2. 리다이렉션된 쿼리 파라미터에서 &amp;#39;code&amp;#39; 추출&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 3. token = exchange_code_for_token(code)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="zeroclaw-런타임-통합-고찰"&gt;ZeroClaw 런타임 통합 고찰
&lt;/h3&gt;&lt;p&gt;지난 회고록인 *&amp;rsquo;[ZeroClaw] 2026 상반기 발전방향 회의록&amp;rsquo;*에서 언급했듯, 우리의 에이전트 런타임은 보안과 확장성을 최우선으로 고려해야 합니다. 위에서 구현한 OAuth 흐름을 ZeroClaw의 &lt;code&gt;Multi-Agent&lt;/code&gt; 아키텍처에 통합한다면, 각 에이전트는 독립된 Identity를 가질 수 있습니다.&lt;/p&gt;
&lt;p&gt;특히 *&amp;rsquo;[ZeroClaw] 멀티 에이전트 통신 프로토콜 설계&amp;rsquo;*에서 논의된 바와 같이, Agent 간 통신 시 이 Access Token을 사용하여 다른 에이전트의 리소스(API)에 안전하게 접근할 수 있는 기반을 마련하게 됩니다. 이는 단순한 블로그 자동화를 넘어, 에이전트가 에이전트를 신뢰하고 작업을 위임하는 분산 시스템의 첫걸음이 됩니다.&lt;/p&gt;
&lt;h3 id="마치며"&gt;마치며
&lt;/h3&gt;&lt;p&gt;Zero-Touch OAuth는 사용자의 편의성과 보안이라는 두 마리 토끼를 잡는 훌륭한 접근 방식입니다. &lt;code&gt;blog-api-server&lt;/code&gt;와 같은 작은 프로젝트부터 시작하여, 점차 &lt;code&gt;ZeroClaw&lt;/code&gt;의 핵심 인증 모듈로 발전시켜 나갈 계획입니다. 위 코드는 기본적인 골격이므로, 실제 운영 환경에서는 State 검증, Refresh Token Rotation 등의 로직을 추가해야 합니다.&lt;/p&gt;
&lt;p&gt;다음 포스트에서는 이렇게 확보된 토큰을 활용하여 MCP 서버와 실제로 데이터를 주고받는 &amp;lsquo;핸드셰이크(Handshake)&amp;rsquo; 과정을 디버깅해보겠습니다.&lt;/p&gt;</description></item><item><title>Stop Using JWTs: 세션 기반 인증의 실용성과 구현</title><link>https://blog.agentthread.dev/ko/post/stop-using-jwts-%EC%84%B8%EC%85%98-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D%EC%9D%98-%EC%8B%A4%EC%9A%A9%EC%84%B1%EA%B3%BC-%EA%B5%AC%ED%98%84/</link><pubDate>Wed, 17 Jun 2026 09:00:39 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/stop-using-jwts-%EC%84%B8%EC%85%98-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D%EC%9D%98-%EC%8B%A4%EC%9A%A9%EC%84%B1%EA%B3%BC-%EA%B5%AC%ED%98%84/</guid><description>&lt;h1 id="stop-using-jwts-세션-기반-인증의-실용성과-구현"&gt;Stop Using JWTs: 세션 기반 인증의 실용성과 구현
&lt;/h1&gt;&lt;p&gt;최근 Hacker News와 같은 커뮤니티에서 &amp;ldquo;Stop Using JWTs&amp;quot;라는 주제가 다시 한번 뜨거운 감자로 떠올랐습니다. JWT(JSON Web Token)는 처음 등장했을 때만 해도 &amp;ldquo;무상태(Stateless)&amp;ldquo;라는 마법의 주문 덕분에 마이크로서비스 아키텍처(MSA)의 표준처럼 여겨졌습니다. 하지만 현실의 웹 서비스를 운영하면서 무상태의 함정에 빠지는 경우가 너무나 많습니다.&lt;/p&gt;
&lt;p&gt;이 글에서는 왜 많은 개발자가 JWT에서 발을 떼고, 전통적인 세션 기반 인증(혹은 DB 기반 토큰)으로 돌아가고 있는지, 그리고 이를 실전에서 어떻게 구현하는지 살펴보겠습니다.&lt;/p&gt;
&lt;h2 id="jwt의-달콤한-함정"&gt;JWT의 달콤한 함정
&lt;/h2&gt;&lt;p&gt;JWT의 가장 큰 장점은 서버에 사용자 상태를 저장하지 않아도 된다는 점입니다. 토큰 자체에 모든 정보가 담겨 있으니, 데이터베이스 조회 없이 서명만 검증하면 됩니다. 이론적으로는 확장성이 뛰어납니다.&lt;/p&gt;
&lt;p&gt;하지만 이 &amp;ldquo;장점&amp;quot;은 곧 &amp;ldquo;단점&amp;quot;이 됩니다.&lt;/p&gt;
&lt;h3 id="1-폐기-불가능성revocation-problem"&gt;1. 폐기 불가능성(Revocation Problem)
&lt;/h3&gt;&lt;p&gt;JWT는 발급되면 유효기간이 끝날 때까지 유효합니다. 만약 사용자가 로그아웃을 하거나, 관리자가 강제로 탈퇴시켜야 한다면 어떻게 할까요?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Option 1:&lt;/strong&gt; 토큰을 즉시 파기할 수 없으니 냅두기 (보험상 불가능)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Option 2:&lt;/strong&gt; 블랙리스트를 도입하기 (결국 DB/Redis 조회 필요 -&amp;gt; Stateless의 장점 상실)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;블랙리스트를 도입하는 순간, JWT 검증을 위해 매번 블랙리스트를 확인해야 하므로, &amp;ldquo;데이터베이스 조회 없이&amp;quot;라는 장점이 사라집니다.&lt;/p&gt;
&lt;h3 id="2-토큰-크기와-부하"&gt;2. 토큰 크기와 부하
&lt;/h3&gt;&lt;p&gt;JWT는 헤더, 페이로드, 서명을 포함하여 일반적인 세션 ID보다 훨씬 큽니다. 사용자별로 다양한 클레임(Role, 권한 등)을 담을수록 토큰은 비대해집니다. 이는 매 요청마다 네트워크 대역폭을 낭비하게 만듭니다.&lt;/p&gt;
&lt;h2 id="세션-기반-인증으로의-회귀"&gt;세션 기반 인증으로의 회귀
&lt;/h2&gt;&lt;p&gt;결론적으로, 우리는 **&amp;ldquo;서버 측에서 상태를 관리한다&amp;rdquo;**는 사실을 받아들이는 것이 낫습니다. Redis와 같은 인메모리 DB를 사용하면 매우 빠르게 세션을 관리할 수 있으며, 로그아웃이나 강제 탈퇴가 즉시 가능합니다.&lt;/p&gt;
&lt;p&gt;이번 섹션에서는 Go 언어와 Redis를 사용하여 간단하지만 강력한 세션 기반 인증 시스템을 구축해 보겠습니다.&lt;/p&gt;
&lt;h3 id="사전-준비"&gt;사전 준비
&lt;/h3&gt;&lt;p&gt;Redis가 로컬에 설치되어 있고 실행 중이라고 가정합니다.&lt;/p&gt;
&lt;h3 id="구현-코드-go"&gt;구현 코드 (Go)
&lt;/h3&gt;&lt;p&gt;아래 코드는 &lt;code&gt;/login&lt;/code&gt;, &lt;code&gt;/protected&lt;/code&gt;, &lt;code&gt;/logout&lt;/code&gt; 엔드포인트를 제공하는 간단한 HTTP 서버입니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;context&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;encoding/json&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;log&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;net/http&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;time&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;github.com/go-redis/redis/v8&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;github.com/google/uuid&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;github.com/gorilla/mux&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;golang.org/x/crypto/bcrypt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;context&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Background&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rdb&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;redis&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Client&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 사용자 정보 (실제로는 DB에서 조회)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;userDB&lt;/span&gt; = &lt;span style="color:#66d9ef"&gt;map&lt;/span&gt;[&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;]&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;user1&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;$2a$14$ajq8Q7fbnFR0nXf8bA7HcuiJ/6V.Z.yzX1lYh8g8h5x6z7x8x9x0x&amp;#34;&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// password: &amp;#34;password&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Credentials&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;Username&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`json:&amp;#34;username&amp;#34;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;Password&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`json:&amp;#34;password&amp;#34;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// Redis 클라이언트 초기화&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;rdb&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;redis&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewClient&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;redis&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Options&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Addr&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;localhost:6379&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Password&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// no password set&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;DB&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// use default DB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;mux&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewRouter&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;HandleFunc&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/login&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;loginHandler&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Methods&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;POST&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;HandleFunc&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/protected&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;authMiddleware&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;protectedHandler&lt;/span&gt;)).&lt;span style="color:#a6e22e"&gt;Methods&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;GET&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;HandleFunc&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/logout&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;authMiddleware&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;logoutHandler&lt;/span&gt;)).&lt;span style="color:#a6e22e"&gt;Methods&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;POST&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Server starting on port 8000...&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Fatal&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ListenAndServe&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;:8000&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 로그인 핸들러&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;loginHandler&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ResponseWriter&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Request&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;creds&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Credentials&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;json&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewDecoder&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Body&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Decode&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;creds&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusBadRequest&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// 사용자 존재 및 비밀번호 확인&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;hashedPassword&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;exists&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;userDB&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;creds&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Username&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; !&lt;span style="color:#a6e22e"&gt;exists&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusUnauthorized&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;bcrypt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CompareHashAndPassword&lt;/span&gt;([]byte(&lt;span style="color:#a6e22e"&gt;hashedPassword&lt;/span&gt;), []byte(&lt;span style="color:#a6e22e"&gt;creds&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Password&lt;/span&gt;)); &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusUnauthorized&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// 새로운 세션 ID 생성&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;sessionToken&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;uuid&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewString&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// Redis에 세션 저장 (만료 24시간)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;rdb&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Set&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;sessionToken&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;creds&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Username&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;24&lt;/span&gt;&lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Hour&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Err&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusInternalServerError&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// 쿠키 설정 (HttpOnly, Secure 권장)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCookie&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;, &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Cookie&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Name&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;session_token&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Value&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;sessionToken&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Expires&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Now&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;Add&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;24&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Hour&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;HttpOnly&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Secure&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// HTTPS 환경에서만&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Path&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;SameSite&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SameSiteStrictMode&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusOK&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;json&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewEncoder&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Encode&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;map&lt;/span&gt;[&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;]&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Login successful&amp;#34;&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 인증 미들웨어&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;authMiddleware&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;next&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;HandlerFunc&lt;/span&gt;) &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;HandlerFunc&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ResponseWriter&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Request&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Cookie&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;session_token&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ErrNoCookie&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;				&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusUnauthorized&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;				&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusBadRequest&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;sessionToken&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#75715e"&gt;// Redis에서 세션 조회&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;response&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rdb&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Get&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;sessionToken&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Result&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;redis&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;				&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusUnauthorized&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;				&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusInternalServerError&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#75715e"&gt;// 요청 컨텍스트에 사용자 이름 저장 (선택 사항)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#75715e"&gt;// r = r.WithContext(context.WithValue(r.Context(), &amp;#34;username&amp;#34;, response))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;next&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 보호된 리소스 핸들러&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;protectedHandler&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ResponseWriter&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Request&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Cookie&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;session_token&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;username&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rdb&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Get&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Value&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Result&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;json&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewEncoder&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Encode&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;map&lt;/span&gt;[&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;]&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Sprintf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Hello %s, you accessed a protected route!&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;username&lt;/span&gt;)})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 로그아웃 핸들러&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;logoutHandler&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ResponseWriter&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Request&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Cookie&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;session_token&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;sessionToken&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// Redis에서 세션 삭제 (즉시 폐기)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;rdb&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Del&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;sessionToken&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#75715e"&gt;// 쿠키 무효화&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCookie&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;, &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Cookie&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Name&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;session_token&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Value&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Expires&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Now&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;Add&lt;/span&gt;(&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Hour&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;HttpOnly&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Path&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusOK&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;json&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewEncoder&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Encode&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;map&lt;/span&gt;[&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;]&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Logout successful&amp;#34;&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="코드-설명-및-적용-팁"&gt;코드 설명 및 적용 팁
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;안전한 쿠키 사용:&lt;/strong&gt; &lt;code&gt;HttpOnly&lt;/code&gt;와 &lt;code&gt;Secure&lt;/code&gt; 플래그를 사용하여 XSS(교차 사이트 스크립팅) 및 중간자 공격을 방지했습니다. 클라이언트(브라우저)가 토큰을 LocalStorage가 아닌 쿠키에 저장하도록 유도하는 것이 안전합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Redis 활용:&lt;/strong&gt; Redis는 싱글 스레드 기반이므로 원자적 연산이 빠르며, 세션 저장소로 사용하기에 최적입니다. 만약 Redis가 다운되더라도 영구적으로 데이터가 필요한 것은 아니므로(재로그인하면 됨), 빠른 응답 속도에 집중하여 설정합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;즉시 로그아웃:&lt;/strong&gt; &lt;code&gt;logoutHandler&lt;/code&gt;를 보면 Redis에서 키를 삭제하는 코드(&lt;code&gt;rdb.Del&lt;/code&gt;)가 있습니다. 이는 JWT의 가장 큰 약점인 &amp;ldquo;로그아웃이 안 됨&amp;quot;을 완벽하게 해결합니다. 키가 삭제되는 순간, 해당 토큰(세션 ID)은 더 이상 유효하지 않습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="결론-무엇을-선택해야-할까"&gt;결론: 무엇을 선택해야 할까?
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;JWT가 좋은 경우:&lt;/strong&gt; 당신이 구축하는 시스템이 진정으로 &lt;strong&gt;독립적인 마이크로서비스&lt;/strong&gt;들로 구성되어 있고, 서비스 간 통신에서 사용자 인증이 필요하며, 로그아웃/재발급 로직이 중요하지 않은 단순 API 통신인 경우.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;세션/Cookie가 좋은 경우:&lt;/strong&gt; 일반적인 웹 서비스, SaaS, 사용자의 보안(계정 탈��� 시 즉각적인 조치 필요)이 중요한 대부분의 경우.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;ldquo;Stop Using JWTs&amp;quot;라는 말은 JWT가 나쁜 기술이라서가 아니라, &lt;strong&gt;상황에 맞지 않게 오남당되고 있기 때문&lt;/strong&gt;입니다. 간단한 세션 저장소 하나면 해결될 문제에 복잡한 JWT 라이브러리와 블랙리스트 로직을 추가하는 것은 기술 부채를 쌓는 행위일 수 있습니다.&lt;/p&gt;
&lt;p&gt;위의 Go 코드 예제를 프로젝트에 적용해 보시고, 관리의 용이성과 보안성을 직접 경험해 보시길 권장합니다.&lt;/p&gt;</description></item><item><title>AI 에이전트 신뢰성 강화: Forge Guardrails와 MCP 통합 가이드</title><link>https://blog.agentthread.dev/ko/post/ai-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EC%8B%A0%EB%A2%B0%EC%84%B1-%EA%B0%95%ED%99%94-forge-guardrails%EC%99%80-mcp-%ED%86%B5%ED%95%A9-%EA%B0%80%EC%9D%B4%EB%93%9C/</link><pubDate>Wed, 20 May 2026 09:00:35 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/ai-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EC%8B%A0%EB%A2%B0%EC%84%B1-%EA%B0%95%ED%99%94-forge-guardrails%EC%99%80-mcp-%ED%86%B5%ED%95%A9-%EA%B0%80%EC%9D%B4%EB%93%9C/</guid><description>&lt;h1 id="ai-에이전트-신뢰성-강화-forge-guardrails와-mcp-통합-가이드"&gt;AI 에이전트 신뢰성 강화: Forge Guardrails와 MCP 통합 가이드
&lt;/h1&gt;&lt;p&gt;최근 LLM(Large Language Model)을 활용한 에이전트 시스템이 급격히 발전하고 있습니다. Hacker News에서는 &amp;ldquo;Forge – Guardrails take an 8B model from 53% to 99% on agentic tasks&amp;quot;라는 흥미로운 사례가 화제가 되었습니다. 이는 모델의 파라미터 수를 늘리는 것보다, 적절한 **가드레일(Guardrails)**을 적용하는 것이 특정 작업의 성공률을 비약적으로 높일 수 있음을 시사합니다.&lt;/p&gt;
&lt;p&gt;저희 팀에서 진행 중인 &lt;strong&gt;ZeroClaw&lt;/strong&gt; 및 &lt;strong&gt;MCP(Model Context Protocol)&lt;/strong&gt; 기반 프로젝트들에 있어 에이전트의 신뢰성은 필수적입니다. 이번 포스트에서는 오픈소스 가드레일 프레임워크인 Forge의 개념을 살펴보고, 이를 기존 MCP 아키텍처에 통합하여 에이전트의 안정성을 확보하는 구체적인 방법을 소개합니다.&lt;/p&gt;
&lt;h2 id="문제-정의-자율성의-딜레마"&gt;문제 정의: 자율성의 딜레마
&lt;/h2&gt;&lt;p&gt;에이전트에게 자율성을 부여할수록 예상치 못한 행동을 할 위험이 커집니다. 예를 들어, 블로그 게시글을 생성하라는 MCP 요청이 있을 때, 에이전트가 시스템 명령어를 실행하거나 허용되지 않은 API를 호출하려 시도할 수 있습니다.&lt;/p&gt;
&lt;p&gt;기존의 &lt;strong&gt;[blog-api-server]&lt;/strong&gt; 구현에서는 프롬프트 엔지니어링과 간단한 JSON 스키마 검증으로 이를 방어하려 했으나, 복잡한 멀티 에이전트 환경에서는 한계가 있었습니다. 이를 해결하기 위해 입력과 출력을 사전에 필터링하는 &lt;strong&gt;L1 가드레일&lt;/strong&gt; 레이어를 도입하기로 결정했습니다.&lt;/p&gt;
&lt;h2 id="솔루션-guardrails-패턴-적용"&gt;솔루션: Guardrails 패턴 적용
&lt;/h2&gt;&lt;p&gt;Forge가 보여주듯, 에이전트 작업의 성공률(53% → 99%)을 높이는 핵심은 &lt;strong&gt;실행 전 검증&lt;/strong&gt;입니다. 우리는 에이전트가 LLM으로부터 응답을 받아 사용자에게 전달하거나 도구(Tool)를 실행하기 전, 중간 계층에서 이를 검증하는 구조를 설계했습니다.&lt;/p&gt;
&lt;h3 id="아키텍처-개요"&gt;아키텍처 개요
&lt;/h3&gt;&lt;p&gt;기존 MCP 클라이언트와 LLM 사이에 &lt;code&gt;Validator&lt;/code&gt; 계층을 두어 다음을 수행합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;입력 검증 (Input Validation):&lt;/strong&gt; 사용자의 요청이 시스템 정책을 위반하지 않는지 확인 (예: 공격적인 프롬프트 필터링).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;출력 검증 (Output Validation):&lt;/strong&gt; LLM이 생성한 JSON이나 함수 호출 인자가 스키마에 부합하는지 확인.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="실전-코드-예제-rust로-구현하는-안전장치"&gt;실전 코드 예제: Rust로 구현하는 안전장치
&lt;/h2&gt;&lt;p&gt;ZeroClaw의 Rust 환경에서 가벼운 출력 검증기를 구현해 보겠습니다. 복잡한 외부 라이브러리 없이, &lt;code&gt;serde&lt;/code&gt;와 &lt;code&gt;regex&lt;/code&gt;를 활용하여 LLM이 생성한 코드 실행 명령을 안전하게 감싸는 예제입니다.&lt;/p&gt;
&lt;h3 id="1-검증-로직-구현하기"&gt;1. 검증 로직 구현하기
&lt;/h3&gt;&lt;p&gt;먼저, 에이전트가 생성한 명령어가 안전한지 판별하는 간단한 검증기입니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; regex::Regex;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; serde::{Deserialize, Serialize};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 에이전트가 생성할 수 있는 명령어 구조
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[derive(Debug, Serialize, Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AgentCommand&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tool_name: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; parameters: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guardrail&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;impl&lt;/span&gt; Guardrail {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 위험한 문자열 필터링 (간단한 예시)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;is_dangerous&lt;/span&gt;(input: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;) -&amp;gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; dangerous_patterns &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;vec!&lt;/span&gt;[&lt;span style="color:#e6db74"&gt;&amp;#34;rm -rf&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;sudo&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;eval&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;__import__&amp;#34;&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dangerous_patterns.iter().any(&lt;span style="color:#f92672"&gt;|&amp;amp;&lt;/span&gt;pat&lt;span style="color:#f92672"&gt;|&lt;/span&gt; input.contains(pat))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 명령어 실행 전 검증 로직
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;validate_command&lt;/span&gt;(cmd: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;AgentCommand&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&amp;amp;&lt;/span&gt;AgentCommand, String&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 1. 도구 이름 화이트리스트 확인
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; allowed_tools &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;vec!&lt;/span&gt;[&lt;span style="color:#e6db74"&gt;&amp;#34;blog_post&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;search&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;read_file&amp;#34;&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;!&lt;/span&gt;allowed_tools.contains(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;cmd.tool_name.as_str()) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Err(&lt;span style="color:#a6e22e"&gt;format!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;허용되지 않은 도구 사용 시도: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, cmd.tool_name));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 2. 파라미터 내 위험 키워드 검사
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; Self::is_dangerous(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;cmd.parameters) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Err(&lt;span style="color:#e6db74"&gt;&amp;#34;파라미터에 잠재적으로 위험한 명령어가 포함되어 있습니다.&amp;#34;&lt;/span&gt;.to_string());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 3. 안전하다면 명령어 승인
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(cmd)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="2-에이전트-루프에-통합하기"&gt;2. 에이전트 루프에 통합하기
&lt;/h3&gt;&lt;p&gt;이제 검증기를 MCP 서버의 요청 처리 루프에 연결합니다. 에이전트가 응답을 생성하면, 실제 시스템이 이를 실행하기 전에 &lt;code&gt;Guardrail&lt;/code&gt;을 거쳐야 합니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 가상의 에이전트 실행 함수
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;execute_agent_task&lt;/span&gt;(llm_output: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String, String&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 1. LLM 출력 파싱 (실제로는 JSON 파싱 등)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 여기서는 간단히 파싱되었다고 가정합니다.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; command &lt;span style="color:#f92672"&gt;=&lt;/span&gt; AgentCommand {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tool_name: &lt;span style="color:#e6db74"&gt;&amp;#34;blog_post&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; parameters: &lt;span style="color:#e6db74"&gt;&amp;#34;title: &amp;#39;Hello World&amp;#39;&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 2. 가드레일 통과 전
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[System] LLM 응답 수신: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, command.tool_name);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 3. 가드레일 검증 실행
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; safe_command &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Guardrail::validate_command(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;command)&lt;span style="color:#f92672"&gt;?&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 4. 검증된 명령어 실행
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[System] 안전한 명령어 실행 중...&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 실제 도구 실행 로직 (예: 블로그 API 호출)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(&lt;span style="color:#e6db74"&gt;&amp;#34;게시글이 성공적으로 생성되었습니다.&amp;#34;&lt;/span&gt;.to_string())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 정상 케이스
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; execute_agent_task(&lt;span style="color:#e6db74"&gt;&amp;#34;valid_response&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(msg) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;성공: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, msg),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Err(e) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;차단됨: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, e),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 비정상 케이스 시뮬레이션
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; malicious_cmd &lt;span style="color:#f92672"&gt;=&lt;/span&gt; AgentCommand {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tool_name: &lt;span style="color:#e6db74"&gt;&amp;#34;system_shell&amp;#34;&lt;/span&gt;.to_string(), &lt;span style="color:#75715e"&gt;// 화이트리스트에 없음
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; parameters: &lt;span style="color:#e6db74"&gt;&amp;#34;rm -rf /&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; Guardrail::validate_command(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;malicious_cmd) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(_) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;오류: 해커가 침입했습니다!&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Err(e) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;보호됨: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, e), &lt;span style="color:#75715e"&gt;// &amp;#34;보호됨: 허용되지 않은 도구 사용 시도: system_shell&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="효과-및-전망"&gt;효과 및 전망
&lt;/h2&gt;&lt;p&gt;이러한 &lt;strong&gt;L1 방어선&lt;/strong&gt;을 구축함으로써 우리는 다음과 같은 이점을 얻을 수 있습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;안정성 향상:&lt;/strong&gt; Forge 사례처럼 8B 모델도 충분히 안전하게 활용할 수 있어 추론 비용 절감.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;투명성 확보:&lt;/strong&gt; 에이전트가 왜 특정 작업을 거부했는지 로그를 통해 명확히 파악 가능.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;유지보수성:&lt;/strong&gt; 보안 정책이 변경되어도 &lt;code&gt;Guardrail&lt;/code&gt; 모듈만 수정하면 됨.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;앞으로 &lt;strong&gt;ZeroClaw&lt;/strong&gt; 프로젝트에서는 이 검증 로직을 비동기 런타임에 통합하여, 멀티 에이전트 간 통신([Discord MCP], [Cloud Monitor]) 시에도 실시간으로 안전성을 모니터링할 계획입니다.&lt;/p&gt;
&lt;p&gt;단순히 모델의 성능을 높이는 것에 집착하기보다, 이처럼 &lt;strong&gt;시스템 레벨에서의 안전장치&lt;/strong&gt;를 어떻게 설계하느냐가 AI 에이전트를 실제 프로덕션에 배포하는 관건이 될 것입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;이 글은 ZeroClaw 및 MCP 관련 아키텍처 설계 문서들을 참고하여 작성되었습니다.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>ZeroClaw 아키텍처 심화: Rust 기반 안전한 에이전트 간 통신 구현하기</title><link>https://blog.agentthread.dev/ko/post/zeroclaw-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%8B%AC%ED%99%94-rust-%EA%B8%B0%EB%B0%98-%EC%95%88%EC%A0%84%ED%95%9C-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EA%B0%84-%ED%86%B5%EC%8B%A0-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0/</link><pubDate>Mon, 11 May 2026 09:01:26 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/zeroclaw-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%8B%AC%ED%99%94-rust-%EA%B8%B0%EB%B0%98-%EC%95%88%EC%A0%84%ED%95%9C-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EA%B0%84-%ED%86%B5%EC%8B%A0-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0/</guid><description>&lt;h1 id="zeroclaw-아키텍처-심화-rust-기반-안전한-에이전트-간-통신-구현하기"&gt;ZeroClaw 아키텍처 심화: Rust 기반 안전한 에이전트 간 통신 구현하기
&lt;/h1&gt;&lt;p&gt;안녱하세요! ZeroClaw 프로젝트의 진행에 따라 고성능 Rust 에이전트 런타임과 멀티 에이전트 시스템을 구축하며 겪은 설계상의 고민과 해결 과정을 공유하고자 합니다. 최근 &amp;lsquo;Obsidian 플러그인을 악용한 원격 접속 트로이어나(RAT)&amp;rsquo; 사건과 같은 보안 이슈들은 플러그인 및 에이전트 시스템에서 **보안(Security)**과 **신뢰성(Trust)**이 얼마나 중요한지를 다시금 상기시켜 줍니다.&lt;/p&gt;
&lt;p&gt;오늘은 ZeroClaw의 코드베이스를 분석하며 설계한, &lt;strong&gt;안전하고 확장 가능한 에이전트 간 통신 프로토콜&lt;/strong&gt;을 구체적인 Rust 코드 예제와 함께 소개하겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="1-서론-보안을-고려한-에이전트-통신의-필요성"&gt;1. 서론: 보안을 고려한 에이전트 통신의 필요성
&lt;/h2&gt;&lt;p&gt;기존의 MCP(Model Context Protocol)나 블로그 자동화 시스템 구축 과정에서 우리는 단순한 데이터 전송을 넘어, 에이전트 간의 **신원 확인(Authentication)**과 **권한 부여(Authorization)**가 필수적임을 깨달았습니다. 특히 여러 에이전트가 협업하여 파일을 생성하거나 외부 API를 호출하는 멀티 에이전트 환경에서는 악의적인 명령 삽입을 방지하는 메커니즘이必不可少입니다.&lt;/p&gt;
&lt;p&gt;ZeroClaw는 이를 위해 Rust의 타입 안전성(Type Safety)과 소유권(Ownership) 시스템을 적극 활용하여, 컴파일 타임에 많은 버그를 잡고 런타임 오버헤드를 최소화하는 아키텍처를 채택했습니다.&lt;/p&gt;
&lt;h2 id="2-통신-프로토콜-설계-ipc와-메시지-큐"&gt;2. 통신 프로토콜 설계: IPC와 메시지 큐
&lt;/h2&gt;&lt;p&gt;ZeroClaw의 통신 플랫폼은 크게 **로컬 IPC(Inter-Process Communication)**와 &lt;strong&gt;비동기 메시지 큐&lt;/strong&gt;로 나뉩니다. 로그 시스템 개선 과정에서 얻은 경험을 바탕으로, 모든 통신은 비동기(async)로 처리되며 각 에이전트의 독립성을 보장합니다.&lt;/p&gt;
&lt;h3 id="주요-설계-원칙"&gt;주요 설계 원칙
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Zero-Copy Serialization:&lt;/strong&gt; &lt;code&gt;serde&lt;/code&gt;와 &lt;code&gt;bincode&lt;/code&gt;를 활용하여 데이터 직렬화 오버헤드를 최소화합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Message Validation:&lt;/strong&gt; 들어오는 모든 메시지를 검증하는 레이어를 두어, 변조된 데이터가 시스템 코어에 도달하는 것을 방지합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Capability-Based Security:&lt;/strong&gt; 단순한 토큰 기반 인증 넘어, 에이전트가 수행할 수 있는 &lt;strong&gt;행위(Capability)&lt;/strong&gt; 자체를 제한합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="3-rust로-구현하는-안전한-메시징-핸들러"&gt;3. Rust로 구현하는 안전한 메시징 핸들러
&lt;/h2&gt;&lt;p&gt;이제 실제 코드를 통해 어떻게 구현했는지 살펴보겠습니다. 이 예제는 &lt;code&gt;tokio&lt;/code&gt;를 기반으로 하며, 안전한 메시지 전달을 위한 기본 구조를 보여줍니다.&lt;/p&gt;
&lt;h3 id="31-메시지-정의-및-검증"&gt;3.1 메시지 정의 및 검증
&lt;/h3&gt;&lt;p&gt;먼저, 에이전트 간에 오가는 데이터 구조를 정의하고, &lt;code&gt;validator&lt;/code&gt; 크레이트를 사용하여 입력값을 검증합니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// message.rs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; serde::{Deserialize, Serialize};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; validator::Validate;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;/// 에이전트 간 통신 메시지 타입
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[derive(Debug, Clone, Serialize, Deserialize)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;enum&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AgentMessage&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TaskRequest { id: String, payload: &lt;span style="color:#a6e22e"&gt;TaskPayload&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TaskResponse { id: String, result: String },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Heartbeat,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;/// 작업 페이로드 (검증 로직 포함)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[derive(Debug, Clone, Serialize, Deserialize, Validate)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TaskPayload&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;#[validate(length(min = 1, max = 100))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; command: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// JSON 형식의 인자를 받지만, 구조적으로 안전하게 파싱해야 함
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; args: &lt;span style="color:#a6e22e"&gt;serde_json&lt;/span&gt;::Value, 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;impl&lt;/span&gt; AgentMessage {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;/// 바이트 배열로부터 안전하게 메시지를 역직렬화합니다.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from_bytes&lt;/span&gt;(bytes: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;[&lt;span style="color:#66d9ef"&gt;u8&lt;/span&gt;]) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Self, Box&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;dyn&lt;/span&gt; std::error::Error&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 1. 기본 직렬화 검증
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; msg: &lt;span style="color:#a6e22e"&gt;AgentMessage&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; bincode::deserialize(bytes)&lt;span style="color:#f92672"&gt;?&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 2. 비즈니스 로직 검증 (예: Command Injection 방지)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; AgentMessage::TaskRequest { payload, &lt;span style="color:#f92672"&gt;..&lt;/span&gt; } &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;msg {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; payload.validate()&lt;span style="color:#f92672"&gt;?&lt;/span&gt;; &lt;span style="color:#75715e"&gt;// validator 크레이트 사용
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 보안: 허용되지 않는 쉘 명령어 필터링 예시
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; payload.command.contains(&lt;span style="color:#e6db74"&gt;&amp;#34;rm &amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;||&lt;/span&gt; payload.command.contains(&lt;span style="color:#e6db74"&gt;&amp;#34;sudo&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Err(&lt;span style="color:#e6db74"&gt;&amp;#34;Blocked potentially dangerous command&amp;#34;&lt;/span&gt;.into());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(msg)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="32-액터-모델-기반-에이전트-구조"&gt;3.2 액터 모델 기반 에이전트 구조
&lt;/h3&gt;&lt;p&gt;각 에이전트는 자신의 상태를 가지며, 메시지를 받아 처리하는 &lt;code&gt;Actor&lt;/code&gt; 패턴을 따릅니다. Rust의 &lt;code&gt;tokio::sync::mpsc&lt;/code&gt; 채널을 사용하여 메일박스를 구현합니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// agent.rs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; tokio::sync::mpsc;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;super&lt;/span&gt;::message::AgentMessage;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Agent&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; receiver: &lt;span style="color:#a6e22e"&gt;mpsc&lt;/span&gt;::Receiver&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AgentMessage&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 에이전트의 상태는 여기에 캡슐화되어 외부에서 직접 접근 불가
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;impl&lt;/span&gt; Agent {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;new&lt;/span&gt;(id: String, receiver: &lt;span style="color:#a6e22e"&gt;mpsc&lt;/span&gt;::Receiver&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AgentMessage&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;) -&amp;gt; &lt;span style="color:#a6e22e"&gt;Self&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Self { id, receiver }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;/// 에이전트의 메인 루프
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;run&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; self) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Agent started&amp;#34;&lt;/span&gt;, self.id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Some(msg) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self.receiver.recv().&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Err(e) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self.handle_message(msg).&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;eprintln!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Error handling message: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{:?}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, self.id, e);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handle_message&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self, msg: &lt;span style="color:#a6e22e"&gt;AgentMessage&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;(), Box&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;dyn&lt;/span&gt; std::error::Error&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; msg {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; AgentMessage::TaskRequest { id, payload } &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;] Executing task &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, self.id, id, payload.command);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 여기에 실제 작업 실행 로직 (예: LLM 호출, 파일 쓰기 등)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 안전 장치: 샌드박스 환경에서 실행하거나 권한을 제한합니다.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; AgentMessage::Heartbeat &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 생존 신호 처리
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _ &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Err(&lt;span style="color:#e6db74"&gt;&amp;#34;Unknown message type&amp;#34;&lt;/span&gt;.into()),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="33-런타임-및-메시지-라우팅"&gt;3.3 런타임 및 메시지 라우팅
&lt;/h3&gt;&lt;p&gt;마지막으로, 여러 에이전트를 생성하고 메시지를 라우팅하는 간단한 런타임 시스템입니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// runtime.rs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; tokio::sync::mpsc;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::collections::HashMap;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;super&lt;/span&gt;::agent::Agent;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;super&lt;/span&gt;::message::AgentMessage;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Runtime&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; agents: &lt;span style="color:#a6e22e"&gt;HashMap&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String, mpsc::Sender&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AgentMessage&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;impl&lt;/span&gt; Runtime {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;new&lt;/span&gt;() -&amp;gt; &lt;span style="color:#a6e22e"&gt;Self&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Self { agents: &lt;span style="color:#a6e22e"&gt;HashMap&lt;/span&gt;::new() }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;spawn_agent&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; self, id: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; (tx, rx) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; mpsc::channel(&lt;span style="color:#ae81ff"&gt;32&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; agent &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Agent::new(id.to_string(), rx);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 에이전트를 별도의 태스크로 실행
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tokio::spawn(agent.run());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self.agents.insert(id.to_string(), tx);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Runtime: Agent &amp;#39;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39; spawned&amp;#34;&lt;/span&gt;, id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;broadcast&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self, msg: &lt;span style="color:#a6e22e"&gt;AgentMessage&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (id, tx) &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; self.agents.iter() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Err(e) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; tx.send(msg.clone()).&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;eprintln!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Failed to send to &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{:?}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, id, e);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="4-마무리-및-다음-단계"&gt;4. 마무리 및 다음 단계
&lt;/h2&gt;&lt;p&gt;위에서 구현한 코드는 ZeroClaw 멀티 에이전트 시스템의 핵심인 **&amp;lsquo;안전한 통신&amp;rsquo;**과 **&amp;lsquo;격리된 상태 관리&amp;rsquo;**를 보여줍니다. Obsidian 플러그인 사건처럼, 외부 입력이나 스크립트를 실행하는 시스템은 언제나 공격의 대상이 될 수 있습니다.&lt;/p&gt;
&lt;p&gt;ZeroClaw는 Rust의 강력한 타입 시스템을 통해 이러한 위험을 컴파일 타임에 대부분 차단하고, 런타임에도 추가적인 검증 레이어를 두어 방어합니다. 다음 포스트에서는 이 에이전트 시스템이 실제로 &lt;strong&gt;Discord나 MCP와 같은 외부 게이트웨이와 어떻게 연동되는지&lt;/strong&gt;, 그리고 &lt;strong&gt;LLM 설정을 통해 어떻게 제어되는지&lt;/strong&gt;에 대해 다루겠습니다.&lt;/p&gt;
&lt;p&gt;지금까지 ZeroClaw의 아키텍처 설계 현황을 공유했습니다. 고성능이면서도 안전한 에이전트 시스템을 구축하려는 분들께 도움이 되기를 바랍니다!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;참고:&lt;/strong&gt; 위 코드는 개념 증명(POC) 수준의 예제이며, 실제 프로덕션 환경에서는 더욱 견고한 에러 핸들링과 로깅 시스템(이전 포스트에서 개선한 로깅 설정 참조)이 통합되어야 합니다.&lt;/p&gt;</description></item><item><title>블로그 AI 자동 댓글 시스템 구축기 (2/3) — 보안 강화</title><link>https://blog.agentthread.dev/ko/post/ai-auto-comment-system-part2-security/</link><pubDate>Sun, 03 May 2026 01:10:00 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/ai-auto-comment-system-part2-security/</guid><description>&lt;h2 id="개요"&gt;개요
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://blog.agentthread.dev/ko/post/ai-auto-comment-system-part1-architecture/" &gt;1부&lt;/a&gt;에서 AI 자동 댓글 시스템의 아키텍처와 구현을 다뤘습니다. 이번 2부에서는 보안 측면을 집중적으로 다룹니다.&lt;/p&gt;
&lt;p&gt;외부 Webhook을 수신하고, GitHub API 토큰을 관리하며, 사용자 입력을 처리하는 시스템은 보안이 특히 중요합니다. 환경 변수 대신 파일 기반 인증으로 전환한 과정과 그 이유, 각 보안 계층의 설계를 설명합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="보안-위협-모델"&gt;보안 위협 모델
&lt;/h2&gt;&lt;p&gt;이 시스템이 방어해야 하는 위협:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;위협&lt;/th&gt;
					&lt;th&gt;공격 벡터&lt;/th&gt;
					&lt;th&gt;방어 수단&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;위장 Webhook&lt;/td&gt;
					&lt;td&gt;공격자가 가짜 Webhook 전송&lt;/td&gt;
					&lt;td&gt;HMAC-SHA256 시그니처 검증&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;토큰 유출&lt;/td&gt;
					&lt;td&gt;환경 변수 노출, 로그 노출&lt;/td&gt;
					&lt;td&gt;파일 기반 인증 + 권한 제한&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;XSS/인젝션&lt;/td&gt;
					&lt;td&gt;악의적인 댓글 내용&lt;/td&gt;
					&lt;td&gt;입력 sanitization&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;과도한 요청&lt;/td&gt;
					&lt;td&gt;DDoS, 남용&lt;/td&gt;
					&lt;td&gt;Flask-Limiter 속도 제한&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;권한 상승&lt;/td&gt;
					&lt;td&gt;워커 프로세스 탈취&lt;/td&gt;
					&lt;td&gt;systemd 보안 디렉티브&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;무한 루프&lt;/td&gt;
					&lt;td&gt;AI가 자신에게 응답&lt;/td&gt;
					&lt;td&gt;마커 기반 댓글 감지&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="파일-기반-인증-관리"&gt;파일 기반 인증 관리
&lt;/h2&gt;&lt;h3 id="환경-변수의-문제점"&gt;환경 변수의 문제점
&lt;/h3&gt;&lt;p&gt;처음에는 GitHub 토큰과 Webhook 시크릿을 환경 변수로 관리했습니다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 초기 (안전하지 않음)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Environment&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;GITHUB_TOKEN=ghp_xxxxx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Environment&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;GITHUB_WEBHOOK_SECRET=my-secret-key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;환경 변수 방식의 문제점:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/proc/PID/environ&lt;/code&gt;&lt;/strong&gt;: Linux에서 프로세스의 환경 변수가 파일로 노출됨&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;로그 노출&lt;/strong&gt;: 디버깅 중 환경 변수가 로그에 기록될 위험&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자식 프로세스 상속&lt;/strong&gt;: &lt;code&gt;subprocess.run&lt;/code&gt;으로 Claude Code 실행 시 모든 환경 변수가 상속됨&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;systemd 설정 파일&lt;/strong&gt;: 서비스 파일에 평문 시크릿이 포함되면 git에 커밋될 위험&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="파일-기반으로-전환"&gt;파일 기반으로 전환
&lt;/h3&gt;&lt;p&gt;인증 정보를 파일 시스템에 저장하고, 환경 변수에는 &lt;strong&gt;파일 경로만&lt;/strong&gt; 지정합니다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 개선 후 — 경로만 노출&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Environment&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;GITHUB_TOKEN_FILE=/etc/auto-comment-worker/github-token&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Environment&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;GITHUB_WEBHOOK_SECRET_FILE=/etc/auto-comment-worker/credentials/webhook-secret&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;서버의 디렉토리 구조:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/etc/auto-comment-worker/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── github-token # GitHub Personal Access Token (640)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;└── credentials/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; └── webhook-secret # GitHub Webhook HMAC Secret (600)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="토큰-파일-로드-코드"&gt;토큰 파일 로드 코드
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; stat
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;GITHUB_TOKEN_FILE &lt;span style="color:#f92672"&gt;=&lt;/span&gt; os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;environ&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#39;GITHUB_TOKEN_FILE&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; GITHUB_TOKEN_FILE &lt;span style="color:#f92672"&gt;and&lt;/span&gt; os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;path&lt;span style="color:#f92672"&gt;.&lt;/span&gt;exists(GITHUB_TOKEN_FILE):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 파일 권한 검증&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; st &lt;span style="color:#f92672"&gt;=&lt;/span&gt; os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;stat(GITHUB_TOKEN_FILE)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; st&lt;span style="color:#f92672"&gt;.&lt;/span&gt;st_mode &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; stat&lt;span style="color:#f92672"&gt;.&lt;/span&gt;S_IWOTH:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;raise&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;PermissionError&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Token file must not be world-writable&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; open(GITHUB_TOKEN_FILE, &lt;span style="color:#e6db74"&gt;&amp;#39;r&amp;#39;&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; f:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; GITHUB_TOKEN &lt;span style="color:#f92672"&gt;=&lt;/span&gt; f&lt;span style="color:#f92672"&gt;.&lt;/span&gt;read()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;strip()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; GITHUB_TOKEN &lt;span style="color:#f92672"&gt;=&lt;/span&gt; os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;environ&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#39;GITHUB_TOKEN&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;핵심 설계 결정:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;파일 존재 시 파일 우선&lt;/strong&gt;: 환경 변수는 폴백으로만 사용&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;권한 검증&lt;/strong&gt;: 파일을 읽기 전에 권한을 확인&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;strip()&lt;/strong&gt;: 파일 끝의 개행 문자 제거&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="파일-권한-검증--삽질의-기록"&gt;파일 권한 검증 — 삽질의 기록
&lt;/h3&gt;&lt;p&gt;이 부분에서 가장 많은 시간을 소비했습니다. 초기 코드는 지나치게 엄격했습니다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 초기 코드 — 너무 엄격함&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; st&lt;span style="color:#f92672"&gt;.&lt;/span&gt;st_mode &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; (stat&lt;span style="color:#f92672"&gt;.&lt;/span&gt;S_IRWXO &lt;span style="color:#f92672"&gt;|&lt;/span&gt; stat&lt;span style="color:#f92672"&gt;.&lt;/span&gt;S_IRWXG):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;raise&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;PermissionError&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Token file must be 600 or 400&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;이 코드의 문제: &lt;code&gt;S_IRWXO | S_IRWXG&lt;/code&gt;는 &lt;strong&gt;그룹의 모든 권한&lt;/strong&gt;(읽기/쓰기/실행)과 &lt;strong&gt;기타의 모든 권한&lt;/strong&gt;을 검사합니다. 즉, 파일 권한이 &lt;code&gt;640&lt;/code&gt; (소유자 읽기/쓰기, 그룹 읽기)이면 거부됩니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# 비트 마스크 분석
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;S_IRWXG = 0o070 # 그룹의 읽기+쓰기+실행
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;S_IRWXO = 0o007 # 기타의 읽기+쓰기+실행
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# 640 = 0o640
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;0o640 &amp;amp; (0o070 | 0o007) = 0o640 &amp;amp; 0o077 = 0o040 # 0이 아님 → 거부!
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;실제 보안상 중요한 것은 &lt;strong&gt;다른 사용자(other)가 파일을 수정할 수 없는 것&lt;/strong&gt;입니다. 그룹 읽기 권한은 같은 그룹 사용자가 파일을 읽을 수 있게 해주며, 보안상 문제가 되지 않습니다.&lt;/p&gt;
&lt;p&gt;수정 후:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 수정 후 — 실제 위협에 집중&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; st&lt;span style="color:#f92672"&gt;.&lt;/span&gt;st_mode &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; stat&lt;span style="color:#f92672"&gt;.&lt;/span&gt;S_IWOTH:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;raise&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;PermissionError&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Token file must not be world-writable&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;stat.S_IWOTH&lt;/code&gt; (&lt;code&gt;0o002&lt;/code&gt;)만 검사하면 됩니다. 이는 &amp;ldquo;기타 사용자에게 쓰기 권한이 있는가&amp;quot;만 확인합니다.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;권한&lt;/th&gt;
					&lt;th&gt;8진수&lt;/th&gt;
					&lt;th&gt;초기 코드&lt;/th&gt;
					&lt;th&gt;수정 후&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;600&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;0o600&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;허용&lt;/td&gt;
					&lt;td&gt;허용&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;640&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;0o640&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;거부&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;허용&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;644&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;0o644&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;거부&lt;/td&gt;
					&lt;td&gt;허용&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;646&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;0o646&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;거부&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;거부&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;666&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;0o666&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;거부&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;거부&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="hmac-sha256-시그니처-검증"&gt;HMAC-SHA256 시그니처 검증
&lt;/h2&gt;&lt;p&gt;GitHub Webhook은 요청 본문을 Webhook 시크릿으로 HMAC-SHA256 해싱한 시그니처를 &lt;code&gt;X-Hub-Signature-256&lt;/code&gt; 헤더로 전송합니다. 이를 검증하여 요청이 실제 GitHub에서 왔는지 확인합니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;verify_webhook_signature&lt;/span&gt;(payload: bytes, signature: str) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; bool:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;GitHub 웹훅 시그니처 검증&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; signature:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; logger&lt;span style="color:#f92672"&gt;.&lt;/span&gt;warning(&lt;span style="color:#e6db74"&gt;&amp;#34;Missing webhook signature&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; WEBHOOK_SECRET:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; logger&lt;span style="color:#f92672"&gt;.&lt;/span&gt;warning(&lt;span style="color:#e6db74"&gt;&amp;#34;WEBHOOK_SECRET not configured - skipping validation&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;True&lt;/span&gt; &lt;span style="color:#75715e"&gt;# 개발 모드 허용&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hash_algorithm, github_signature &lt;span style="color:#f92672"&gt;=&lt;/span&gt; signature&lt;span style="color:#f92672"&gt;.&lt;/span&gt;split(&lt;span style="color:#e6db74"&gt;&amp;#39;=&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; hash_algorithm &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;sha256&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mac &lt;span style="color:#f92672"&gt;=&lt;/span&gt; hmac&lt;span style="color:#f92672"&gt;.&lt;/span&gt;new(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; WEBHOOK_SECRET&lt;span style="color:#f92672"&gt;.&lt;/span&gt;encode(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; msg&lt;span style="color:#f92672"&gt;=&lt;/span&gt;payload,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; digestmod&lt;span style="color:#f92672"&gt;=&lt;/span&gt;hashlib&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sha256
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; expected_signature &lt;span style="color:#f92672"&gt;=&lt;/span&gt; mac&lt;span style="color:#f92672"&gt;.&lt;/span&gt;hexdigest()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 타이밍 공격 방지&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; hmac&lt;span style="color:#f92672"&gt;.&lt;/span&gt;compare_digest(expected_signature, github_signature):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exception&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; e:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; logger&lt;span style="color:#f92672"&gt;.&lt;/span&gt;error(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Signature verification error: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;e&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;주요 포인트:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;hmac.compare_digest()&lt;/code&gt;&lt;/strong&gt;: 일반 &lt;code&gt;==&lt;/code&gt; 비교 대신 상수 시간 비교를 사용하여 타이밍 공격을 방지합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;raw bytes 사용&lt;/strong&gt;: &lt;code&gt;request.data&lt;/code&gt; (원본 바이트)를 사용합니다. &lt;code&gt;request.json&lt;/code&gt;으로 파싱 후 재직렬화하면 원본과 달라질 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;개발 모드&lt;/strong&gt;: 시크릿이 설정되지 않으면 검증을 건너뜁니다. 프로덕션에서는 반드시 시크릿을 설정해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="nginx-헤더-포워딩"&gt;nginx 헤더 포워딩
&lt;/h3&gt;&lt;p&gt;시그니처 검증이 정상 작동하려면 nginx가 &lt;code&gt;X-Hub-Signature-256&lt;/code&gt; 헤더를 포워딩해야 합니다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;location&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/webhook&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_pass&lt;/span&gt; &lt;span style="color:#e6db74"&gt;http://localhost:8081&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;Host&lt;/span&gt; $host;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;X-Real-IP&lt;/span&gt; $remote_addr;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;X-Forwarded-For&lt;/span&gt; $proxy_add_x_forwarded_for;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;X-Hub-Signature-256&lt;/span&gt; $http_x_hub_signature_256;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;X-Hub-Signature-256&lt;/code&gt;을 명시적으로 포워딩하지 않으면, 기본 &lt;code&gt;proxy_pass&lt;/code&gt;만으로는 커스텀 헤더가 전달되지 않을 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="요청-검증-marshmallow-스키마"&gt;요청 검증 (marshmallow 스키마)
&lt;/h2&gt;&lt;p&gt;Webhook 페이로드의 구조를 marshmallow 스키마로 검증합니다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;WebhookSchema&lt;/span&gt;(Schema):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; action &lt;span style="color:#f92672"&gt;=&lt;/span&gt; fields&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Str(required&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;, validate&lt;span style="color:#f92672"&gt;=&lt;/span&gt;validate&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Equal(&lt;span style="color:#e6db74"&gt;&amp;#39;created&amp;#39;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; comment &lt;span style="color:#f92672"&gt;=&lt;/span&gt; fields&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Dict(required&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; discussion &lt;span style="color:#f92672"&gt;=&lt;/span&gt; fields&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Dict(required&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; repository &lt;span style="color:#f92672"&gt;=&lt;/span&gt; fields&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Dict(required&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sender &lt;span style="color:#f92672"&gt;=&lt;/span&gt; fields&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Dict(required&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;action = 'created'&lt;/code&gt;만 허용&lt;/strong&gt;: 댓글 수정(&lt;code&gt;edited&lt;/code&gt;)이나 삭제(&lt;code&gt;deleted&lt;/code&gt;) 이벤트는 거부합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;필수 필드 검증&lt;/strong&gt;: &lt;code&gt;comment&lt;/code&gt;, &lt;code&gt;discussion&lt;/code&gt;, &lt;code&gt;repository&lt;/code&gt;가 없으면 400 에러를 반환합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ValidationError → 감사 로그&lt;/strong&gt;: 잘못된 요청은 감사 로그에 기록됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="입력-sanitization"&gt;입력 Sanitization
&lt;/h2&gt;&lt;p&gt;사용자 댓글은 외부 입력이므로 반드시 처리합니다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sanitize_comment&lt;/span&gt;(body: str) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; str:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;사용자 입력 sanitization&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; body &lt;span style="color:#f92672"&gt;=&lt;/span&gt; re&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sub(&lt;span style="color:#e6db74"&gt;r&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;&amp;lt;[^&amp;gt;]+&amp;gt;&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&lt;/span&gt;, body) &lt;span style="color:#75715e"&gt;# HTML 태그 제거&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; body &lt;span style="color:#f92672"&gt;=&lt;/span&gt; html&lt;span style="color:#f92672"&gt;.&lt;/span&gt;escape(body) &lt;span style="color:#75715e"&gt;# 특수 문자 이스케이프&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; body &lt;span style="color:#f92672"&gt;=&lt;/span&gt; body[:&lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt;] &lt;span style="color:#75715e"&gt;# 길이 제한&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; body
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;이 sanitization이 적용되는 곳:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;댓글 본문 (&lt;code&gt;comment_body&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;토론 제목과 본문 (&lt;code&gt;discussion_title&lt;/code&gt;, &lt;code&gt;discussion_body&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;원작성자 이름 (&lt;code&gt;original_author&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;AI 응답 게시 시 인용 부분&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="사용자명-마스킹"&gt;사용자명 마스킹
&lt;/h3&gt;&lt;p&gt;로그에 사용자 이름 전체를 기록하지 않습니다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;mask_username&lt;/span&gt;(username: str) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; str:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;사용자명 마스킹&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; username &lt;span style="color:#f92672"&gt;or&lt;/span&gt; len(username) &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;***&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;username[:&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;***&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;로그에는 &lt;code&gt;yar***&lt;/code&gt;처럼 마스킹된 이름만 표시됩니다. 개인정보 보호와 디버깅 편의성 사이의 균형을 맞춥니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="속도-제한"&gt;속도 제한
&lt;/h2&gt;&lt;p&gt;Flask-Limiter로 엔드포인트별 속도 제한을 적용합니다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;limiter &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Limiter(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; app&lt;span style="color:#f92672"&gt;=&lt;/span&gt;app,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; key_func&lt;span style="color:#f92672"&gt;=&lt;/span&gt;get_remote_address,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; default_limits&lt;span style="color:#f92672"&gt;=&lt;/span&gt;[&lt;span style="color:#e6db74"&gt;&amp;#34;10 per minute&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; storage_uri&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;memory://&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@app.route&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;/webhook&amp;#39;&lt;/span&gt;, methods&lt;span style="color:#f92672"&gt;=&lt;/span&gt;[&lt;span style="color:#e6db74"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@limiter.limit&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;10 per minute&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;github_webhook&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;분당 10회 제한&lt;/strong&gt;: 정상적인 Webhook 호출 빈도를 고려한 수치&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;get_remote_address&lt;/code&gt;&lt;/strong&gt;: 클라이언트 IP 기준으로 제한&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;memory://&lt;/code&gt;&lt;/strong&gt;: 인메모리 저장소 (단일 프로세스에 적합)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="systemd-보안-디렉티브"&gt;systemd 보안 디렉티브
&lt;/h2&gt;&lt;p&gt;3부에서 systemd 배포를 상세히 다루지만, 보안 관련 디렉티브는 여기서 설명합니다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[Service]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 보안 강화&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;NoNewPrivileges&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;true # 권한 상승 방지&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;PrivateTmp&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;true # 독립된 /tmp 제공&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ProtectSystem&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;strict # 파일시스템 읽기 전용&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ProtectHome&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;false # 홈 디렉토리 접근 허용 (Claude Code 설정)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ReadWritePaths&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/var/www/auto-comment-worker /var/log/auto-comment-worker&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 리소스 제한&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;MemoryMax&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;512M # 메모리 제한&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;CPUQuota&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;50% # CPU 사용량 제한&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;TasksMax&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;100 # 프로세스 수 제한&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;디렉티브&lt;/th&gt;
					&lt;th&gt;효과&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;NoNewPrivileges=true&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;setuid&lt;/code&gt;, &lt;code&gt;setgid&lt;/code&gt; 등으로 권한 상승 불가&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;PrivateTmp=true&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;독립된 &lt;code&gt;/tmp&lt;/code&gt; 네임스페이스, 다른 프로세스와 격리&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;ProtectSystem=strict&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;전체 파일시스템을 읽기 전용으로 마운트&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;ReadWritePaths&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;명시적으로 쓰기를 허용할 경로만 지정&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;MemoryMax=512M&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;OOM 상황에서 시스템 전체를 보호&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="protectsystemstrict과-readonlypaths의-충돌"&gt;&lt;code&gt;ProtectSystem=strict&lt;/code&gt;과 &lt;code&gt;ReadOnlyPaths&lt;/code&gt;의 충돌
&lt;/h3&gt;&lt;p&gt;초기에 &lt;code&gt;ReadOnlyPaths=/etc/auto-comment-worker&lt;/code&gt;를 추가했다가 토큰 파일을 읽지 못하는 문제가 발생했습니다. &lt;code&gt;ProtectSystem=strict&lt;/code&gt;가 이미 전체 파일시스템을 읽기 전용으로 설정하므로, 별도의 &lt;code&gt;ReadOnlyPaths&lt;/code&gt;는 불필요합니다. 오히려 일부 환경에서 충돌을 일으킬 수 있어 제거했습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="마무리"&gt;마무리
&lt;/h2&gt;&lt;p&gt;이번 2부에서는 파일 기반 인증 관리, 파일 권한 검증 삽질기, HMAC-SHA256 시그니처 검증, 입력 sanitization, 속도 제한, systemd 보안 디렉티브를 다뤘습니다.&lt;/p&gt;
&lt;p&gt;보안에서 가장 중요한 교훈: &lt;strong&gt;&amp;ldquo;너무 엄격한 검증은 너무 느슨한 검증만큼 해롭다.&amp;rdquo;&lt;/strong&gt; 파일 권한을 &lt;code&gt;600&lt;/code&gt;만 허용하는 초기 코드는 보안상 안전했지만, 실제 운영 환경에서 &lt;code&gt;640&lt;/code&gt; 권한의 파일을 거부하여 서비스가 시작되지 않았습니다. 실제 위협(world-writable)에만 집중하는 것이 올바른 접근입니다.&lt;/p&gt;
&lt;p&gt;다음 3부에서는 &lt;strong&gt;배포와 트러블슈팅&lt;/strong&gt; — systemd 서비스 구성, nginx 리버스 프록시, 실제 겪은 오류들과 해결 과정을 다룹니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;이 글은 AgentForge 블로그 자동 댓글 시스템 시리즈의 2부입니다.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>[ZeroClaw] 2026 상반기 발전방향 회의록</title><link>https://blog.agentthread.dev/ko/post/2026-02-28-001-zeroclaw-2026-h1-roadmap-meeting/</link><pubDate>Sat, 28 Feb 2026 09:55:15 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/2026-02-28-001-zeroclaw-2026-h1-roadmap-meeting/</guid><description>&lt;h1 id="zeroclaw-2026-상반기-발전방향-팀-회의록"&gt;ZeroClaw 2026 상반기 발전방향 팀 회의록
&lt;/h1&gt;&lt;p&gt;&lt;strong&gt;회의 일시&lt;/strong&gt;: 2026-02-28
&lt;strong&gt;참석자&lt;/strong&gt;: 아키텍트, 보안 리드, 프로덕트 오너, DevOps 리드
&lt;strong&gt;회의 장소&lt;/strong&gt;: ZeroClaw 개발팀 회의실
&lt;strong&gt;작성자&lt;/strong&gt;: ZeroClaw Team&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="1-회의-개요"&gt;1. 회의 개요
&lt;/h2&gt;&lt;p&gt;본 회의는 ZeroClaw 프로젝트의 2026년 상반기 발전방향을 논의하고, 각 영역별 로드맵과 우선순위를 수립하기 위해 개최되었다.&lt;/p&gt;
&lt;h3 id="11-회의-안건"&gt;1.1 회의 안건
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;멀티 에이전트 아키텍처 발전 방향&lt;/li&gt;
&lt;li&gt;보안 강화 및 컴플라이언스 대응&lt;/li&gt;
&lt;li&gt;제품 경쟁력 강화 및 사용자 경험 개선&lt;/li&gt;
&lt;li&gt;DevOps 및 인프라 고도화&lt;/li&gt;
&lt;li&gt;종합 로드맵 및 우선순위 합의&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="2-아키텍처-발전방향-논의"&gt;2. 아키텍처 발전방향 논의
&lt;/h2&gt;&lt;h3 id="21-아키텍트-발언"&gt;2.1 아키텍트 발언
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;&amp;ldquo;현재 ZeroClaw는 Phase 1(인프로세스 위임)을 완료하고 Phase 2(파일 기반 멀티 에이전트) 구현 중에 있습니다. 2026년 상반기에는 다음과 같은 기술적 목표를 달성해야 합니다.&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h4 id="211-멀티-에이전트-오케스트레이션-고도화"&gt;2.1.1 멀티 에이전트 오케스트레이션 고도화
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DAG 기반 워크플로우 실행 엔진 구현&lt;/li&gt;
&lt;li&gt;순차/병렬/조건/반복 실행 패턴 지원&lt;/li&gt;
&lt;li&gt;에이전트 간 작업 분배 알고리즘 개선&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 높음 (P1)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 제안하는 워크플로우 구조
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Workflow&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; id: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; nodes: Vec&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;WorkflowNode&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; edges: Vec&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;WorkflowEdge&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; start_node: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; end_nodes: Vec&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; error_handling: &lt;span style="color:#a6e22e"&gt;ErrorHandlingPolicy&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="212-ipc-통신-프로토콜-개선"&gt;2.1.2 IPC 통신 프로토콜 개선
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;현재 stdout 기반 통신을 구조화된 IPC로 전환&lt;/li&gt;
&lt;li&gt;Unix Socket, gRPC, WebSocket 다중 전송 계층 지원&lt;/li&gt;
&lt;li&gt;메시지 직렬화 최적화 (JSON → MessagePack/CBOR)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 중간 (P2)&lt;/p&gt;
&lt;h4 id="213-확장성-및-모듈화"&gt;2.1.3 확장성 및 모듈화
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;플러그인 아키텍처 도입 검토&lt;/li&gt;
&lt;li&gt;Wasm 실행 모드 구현 (샌드박싱 강화)&lt;/li&gt;
&lt;li&gt;외부 에이전트 등록 API 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 낮음 (P3)&lt;/p&gt;
&lt;h4 id="214-성능-최적화"&gt;2.1.4 성능 최적화
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Zero-copy 메시지 전달&lt;/li&gt;
&lt;li&gt;연결 풀링 및 캐싱 전략&lt;/li&gt;
&lt;li&gt;비동기 I/O 최적화&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 중간 (P2)&lt;/p&gt;
&lt;h3 id="22-토의-내용"&gt;2.2 토의 내용
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;보안 리드:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;IPC 프로토콜 개선 시 보안 채널 적용이 선행되어야 합니다. 에이전트 간 통신에 대한 암호화와 인증이 필요합니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;프로덕트 오너:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;사용자 관점에서는 에이전트 정의가 더 직관적이어야 합니다. YAML 스키마를 단순화하는 것도 고려해봅시다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;DevOps 리드:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;성능 최적화 작업은 관찰성 향상과 병행되어야 합니다. 메트릭 수집이 먼저 확보되어야 최적화 효과를 측정할 수 있습니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="3-보안-발전방향-논의"&gt;3. 보안 발전방향 논의
&lt;/h2&gt;&lt;h3 id="31-보안-리드-발언"&gt;3.1 보안 리드 발언
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;&amp;ldquo;ZeroClaw는 이미 강력한 보안 기반을 갖추고 있으나, OWASP Top 10 관점에서 보완이 필요한 영역이 있습니다. 특히 샌드박스 격리와 감사 로깅 강화가 시급합니다.&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h4 id="311-샌드박스-강화"&gt;3.1.1 샌드박스 강화
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;현재 문제점:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Docker 샌드박스: &lt;code&gt;--read-only&lt;/code&gt;, &lt;code&gt;--cap-drop&lt;/code&gt;, seccomp 미적용&lt;/li&gt;
&lt;li&gt;Firejail: 네트워크 격리 미적용 (&lt;code&gt;--net=none&lt;/code&gt; 누락)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Docker 강화 프로필
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker_cmd.args([
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;run&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;--rm&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;--read-only&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;--security-opt&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;no-new-privileges&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;--cap-drop&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;ALL&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;--network&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;none&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;--memory&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;256m&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;--pids-limit&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;64&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 높음 (P1)&lt;/p&gt;
&lt;h4 id="312-감사-로깅-강화"&gt;3.1.2 감사 로깅 강화
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;로그 서명 (Log Signing) 도입으로 변조 방지&lt;/li&gt;
&lt;li&gt;실시간 보안 알림 웹훅 연동&lt;/li&gt;
&lt;li&gt;Prometheus 메트릭 노출&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 중간 (P2)&lt;/p&gt;
&lt;h4 id="313-ssrf-방어"&gt;3.1.3 SSRF 방어
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;외부 요청 시 HTTPS 강제&lt;/li&gt;
&lt;li&gt;사설 IP 대역 차단&lt;/li&gt;
&lt;li&gt;도메인 허용목록 검증 강화&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 중간 (P2)&lt;/p&gt;
&lt;h4 id="314-toctou-취약점-수정"&gt;3.1.4 TOCTOU 취약점 수정
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;경로 검증과 정규화를 원자적으로 수행&lt;/li&gt;
&lt;li&gt;심볼릭 링크 레이스 컨디션 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 높음 (P1)&lt;/p&gt;
&lt;h3 id="32-토의-내용"&gt;3.2 토의 내용
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;아키텍트:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;샌드박스 강화는 Wasm 실행 모드와 연계하여 진행하면 더 강력한 격리를 제공할 수 있습니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;DevOps 리드:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;Prometheus 메트릭은 Grafana 대시보드와 연동하여 운영 가시성을 크게 개선할 수 있습니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;프로덕트 오너:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;보안 강화로 인한 사용자 경험 저하가 없도록 기본값은 안전하게, 고급 사용자는 유연하게 설정할 수 있어야 합니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="4-제품-발전방향-논의"&gt;4. 제품 발전방향 논의
&lt;/h2&gt;&lt;h3 id="41-프로덕트-오너-발언"&gt;4.1 프로덕트 오너 발언
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;&amp;ldquo;ZeroClaw의 핵심 가치는 &amp;lsquo;고성능&amp;rsquo;, &amp;lsquo;확장성&amp;rsquo;, &amp;lsquo;보안&amp;rsquo;입니다. 2026년 상반기에는 이를 더욱 강화하면서 사용자 경험을 개선해야 합니다.&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h4 id="411-사용자-경험-개선"&gt;4.1.1 사용자 경험 개선
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;CLI 개선:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;zeroclaw agent list&lt;/code&gt; 출력 개선 (테이블 포맷)&lt;/li&gt;
&lt;li&gt;대화형 에이전트 실행 모드&lt;/li&gt;
&lt;li&gt;진행 상황 시각화&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;설정 단순화:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;에이전트 정의 YAML 스키마 단순화&lt;/li&gt;
&lt;li&gt;sensible defaults 제공&lt;/li&gt;
&lt;li&gt;설정 검증 및 오류 메시지 개선&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 높음 (P1)&lt;/p&gt;
&lt;h4 id="412-문서화-및-온보딩-강화"&gt;4.1.2 문서화 및 온보딩 강화
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Quickstart 가이드 개선&lt;/li&gt;
&lt;li&gt;API 참조 문서 자동 생성&lt;/li&gt;
&lt;li&gt;예제 시나리오 확장&lt;/li&gt;
&lt;li&gt;다국어 문서 (한국어, 일본어, 중국어)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 중간 (P2)&lt;/p&gt;
&lt;h4 id="413-커뮤니티-성장-전략"&gt;4.1.3 커뮤니티 성장 전략
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CONTRIBUTING.md 개선&lt;/li&gt;
&lt;li&gt;Good first issue 라벨링&lt;/li&gt;
&lt;li&gt;정기 릴리즈 노트 발행&lt;/li&gt;
&lt;li&gt;Discord/Slack 커뮤니티 채널&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 중간 (P2)&lt;/p&gt;
&lt;h4 id="414-차별화-포인트-강화"&gt;4.1.4 차별화 포인트 강화
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하드웨어 페리퍼럴 지원 강화 (STM32, RPi)&lt;/li&gt;
&lt;li&gt;특화 도메인 에이전트 템플릿&lt;/li&gt;
&lt;li&gt;성능 벤치마크 공개&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 낮음 (P3)&lt;/p&gt;
&lt;h3 id="42-토의-내용"&gt;4.2 토의 내용
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;아키텍트:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;CLI 개선은 ergonomics 측면에서 중요합니다. structopt 대신 clap 4.x로 업그레이드하는 것도 검토해봅시다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;보안 리드:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;다국어 문서 작성 시 보안 관련 내용은 정확한 번역이 중요합니다. 용어집을 먼저 작성하는 것을 권장합니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;DevOps 리드:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;커뮤니티 채널은 GitHub Discussions부터 시작하는 것이 좋습니다. 별도 인프라 없이 시작할 수 있습니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="5-devops-발전방향-논의"&gt;5. DevOps 발전방향 논의
&lt;/h2&gt;&lt;h3 id="51-devops-리드-발언"&gt;5.1 DevOps 리드 발언
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;&amp;ldquo;안정적인 배포와 운영을 위해 CI/CD 파이프라인과 모니터링 시스템을 고도화해야 합니다.&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h4 id="511-cicd-파이프라인-고도화"&gt;5.1.1 CI/CD 파이프라인 고도화
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;현재 상태:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub Actions 기반 CI&lt;/li&gt;
&lt;li&gt;cargo fmt, clippy, test 자동화&lt;/li&gt;
&lt;li&gt;Docker 빌드&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cargo audit 통합 (취약점 스캔)&lt;/li&gt;
&lt;li&gt;크로스 플랫폼 빌드 (Linux, macOS, Windows)&lt;/li&gt;
&lt;li&gt;릴리즈 자동화 (cargo-release)&lt;/li&gt;
&lt;li&gt;배포 전 스모크 테스트&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 높음 (P1)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 제안하는 CI 워크플로우&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Security audit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;cargo audit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Cross-platform build&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;strategy&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;matrix&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;os&lt;/span&gt;: [&lt;span style="color:#ae81ff"&gt;ubuntu-latest, macos-latest, windows-latest]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;runs-on&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;${{ matrix.os }}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="512-모니터링-및-관찰성"&gt;5.1.2 모니터링 및 관찰성
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prometheus 메트릭 엔드포인트&lt;/li&gt;
&lt;li&gt;Grafana 대시보드 템플릿&lt;/li&gt;
&lt;li&gt;분산 추적 (OpenTelemetry)&lt;/li&gt;
&lt;li&gt;로그 집계 (ELK/Loki)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 중간 (P2)&lt;/p&gt;
&lt;h4 id="513-배포-자동화-및-롤백"&gt;5.1.3 배포 자동화 및 롤백
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Blue-Green 배포 지원&lt;/li&gt;
&lt;li&gt;자동 롤백 임계값 설정&lt;/li&gt;
&lt;li&gt;Canary 릴리즈 옵션&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 중간 (P2)&lt;/p&gt;
&lt;h4 id="514-개발-환경-개선"&gt;5.1.4 개발 환경 개선
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;제안 사항:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;devcontainer 구성&lt;/li&gt;
&lt;li&gt;git hooks 자동 설정&lt;/li&gt;
&lt;li&gt;개발용 설정 프로파일&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;우선순위:&lt;/strong&gt; 낮음 (P3)&lt;/p&gt;
&lt;h3 id="52-토의-내용"&gt;5.2 토의 내용
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;아키텍트:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;OpenTelemetry 통합은 멀티 에이전트 추적에 필수적입니다. 에이전트 간 호출 체인을 시각화할 수 있어야 합니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;보안 리드:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;cargo audit은 CI에 반드시 포함되어야 합니다. 또한 의존성 라이선스 검사도 추가하는 것이 좋습니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;프로덕트 오너:&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;devcontainer는 신규 기여자 온보딩에 큰 도움이 될 것입니다. 우선순위를 높여도 좋습니다.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="6-종합-로드맵-및-우선순위"&gt;6. 종합 로드맵 및 우선순위
&lt;/h2&gt;&lt;h3 id="61-2026년-상반기-로드맵"&gt;6.1 2026년 상반기 로드맵
&lt;/h3&gt;&lt;h4 id="q1-3월-4월"&gt;Q1 (3월-4월)
&lt;/h4&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;항목&lt;/th&gt;
					&lt;th&gt;담당&lt;/th&gt;
					&lt;th&gt;우선순위&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Docker/Firejail 샌드박스 강화&lt;/td&gt;
					&lt;td&gt;보안&lt;/td&gt;
					&lt;td&gt;P1&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;TOCTOU 취약점 수정&lt;/td&gt;
					&lt;td&gt;보안&lt;/td&gt;
					&lt;td&gt;P1&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;CI/CD 보안 스캔 통합&lt;/td&gt;
					&lt;td&gt;DevOps&lt;/td&gt;
					&lt;td&gt;P1&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;CLI 개선 및 UX 향상&lt;/td&gt;
					&lt;td&gt;프로덕트&lt;/td&gt;
					&lt;td&gt;P1&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;DAG 기반 워크플로우 엔진&lt;/td&gt;
					&lt;td&gt;아키텍트&lt;/td&gt;
					&lt;td&gt;P1&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id="q2-5월-6월"&gt;Q2 (5월-6월)
&lt;/h4&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;항목&lt;/th&gt;
					&lt;th&gt;담당&lt;/th&gt;
					&lt;th&gt;우선순위&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;IPC 통신 프로토콜 개선&lt;/td&gt;
					&lt;td&gt;아키텍트&lt;/td&gt;
					&lt;td&gt;P2&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;감사 로깅 강화&lt;/td&gt;
					&lt;td&gt;보안&lt;/td&gt;
					&lt;td&gt;P2&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;SSRF 방어 구현&lt;/td&gt;
					&lt;td&gt;보안&lt;/td&gt;
					&lt;td&gt;P2&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Prometheus 메트릭 노출&lt;/td&gt;
					&lt;td&gt;DevOps&lt;/td&gt;
					&lt;td&gt;P2&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;문서화 및 온보딩 개선&lt;/td&gt;
					&lt;td&gt;프로덕트&lt;/td&gt;
					&lt;td&gt;P2&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;커뮤니티 인프라 구축&lt;/td&gt;
					&lt;td&gt;프로덕트&lt;/td&gt;
					&lt;td&gt;P2&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="62-합의된-원칙"&gt;6.2 합의된 원칙
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;보안 우선&lt;/strong&gt;: 모든 새로운 기능은 보안 리뷰를 거쳐야 함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;점진적 배포&lt;/strong&gt;: 큰 변경은 단계적으로 롤아웃&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;롤백 가능&lt;/strong&gt;: 모든 변경은 쉽게 롤백 가능해야 함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;문서 동기화&lt;/strong&gt;: 기능 변경 시 문서 업데이트 필수&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;테스트 커버리지&lt;/strong&gt;: 새로운 코드는 테스트 필수&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="63-리스크-및-대응"&gt;6.3 리스크 및 대응
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;리스크&lt;/th&gt;
					&lt;th&gt;확률&lt;/th&gt;
					&lt;th&gt;영향&lt;/th&gt;
					&lt;th&gt;대응 방안&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;샌드박스 호환성 문제&lt;/td&gt;
					&lt;td&gt;중간&lt;/td&gt;
					&lt;td&gt;높음&lt;/td&gt;
					&lt;td&gt;폴백 메커니즘 구현&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;성능 회귀&lt;/td&gt;
					&lt;td&gt;낮음&lt;/td&gt;
					&lt;td&gt;중간&lt;/td&gt;
					&lt;td&gt;벤치마크 자동화&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;의존성 취약점&lt;/td&gt;
					&lt;td&gt;중간&lt;/td&gt;
					&lt;td&gt;높음&lt;/td&gt;
					&lt;td&gt;cargo audit 자동화&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;문서 부실&lt;/td&gt;
					&lt;td&gt;높음&lt;/td&gt;
					&lt;td&gt;중간&lt;/td&gt;
					&lt;td&gt;문서 PR 필수화&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="7-결론-및-다음-단계"&gt;7. 결론 및 다음 단계
&lt;/h2&gt;&lt;h3 id="71-회의-결론"&gt;7.1 회의 결론
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;아키텍처&lt;/strong&gt;: 멀티 에이전트 오케스트레이션 고도화를 최우선으로 진행&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;보안&lt;/strong&gt;: 샌드박스 강화와 TOCTOU 수정을 즉시 착수&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;제품&lt;/strong&gt;: CLI UX 개선으로 사용자 경험 향상&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DevOps&lt;/strong&gt;: CI/CD 보안 강화와 모니터링 기반 구축&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="72-액션-아이템"&gt;7.2 액션 아이템
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;#&lt;/th&gt;
					&lt;th&gt;항목&lt;/th&gt;
					&lt;th&gt;담당자&lt;/th&gt;
					&lt;th&gt;기한&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;1&lt;/td&gt;
					&lt;td&gt;Docker 샌드박스 보안 프로필 적용&lt;/td&gt;
					&lt;td&gt;보안 리드&lt;/td&gt;
					&lt;td&gt;2026-03-07&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;TOCTOU 경로 검증 수정&lt;/td&gt;
					&lt;td&gt;보안 리드&lt;/td&gt;
					&lt;td&gt;2026-03-10&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;3&lt;/td&gt;
					&lt;td&gt;CI cargo audit 통합&lt;/td&gt;
					&lt;td&gt;DevOps 리드&lt;/td&gt;
					&lt;td&gt;2026-03-05&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4&lt;/td&gt;
					&lt;td&gt;CLI agent list 개선&lt;/td&gt;
					&lt;td&gt;프로덕트 오너&lt;/td&gt;
					&lt;td&gt;2026-03-12&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;5&lt;/td&gt;
					&lt;td&gt;워크플로우 엔진 설계 문서&lt;/td&gt;
					&lt;td&gt;아키텍트&lt;/td&gt;
					&lt;td&gt;2026-03-15&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="73-다음-회의-일정"&gt;7.3 다음 회의 일정
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;일시&lt;/strong&gt;: 2026-03-14 10:00&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;안건&lt;/strong&gt;: Q1 진행 상황 리뷰 및 Q2 상세 계획&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="부록-참고-문서"&gt;부록: 참고 문서
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docs/project/multi-agent-architecture-design.md&lt;/code&gt; - 멀티 에이전트 아키텍처 설계&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docs/project/multi-agent-communication-protocols.md&lt;/code&gt; - 통신 프로토콜 설계&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docs/security/security-improvements.md&lt;/code&gt; - 보안 개선 보고서&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; - 엔지니어링 프로토콜&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;회의록 작성일&lt;/strong&gt;: 2026-02-28
&lt;strong&gt;승인자&lt;/strong&gt;: ZeroClaw Team Lead
&lt;strong&gt;배포&lt;/strong&gt;: ZeroClaw 개발팀 전체&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;영어 버전:&lt;/strong&gt; &lt;a class="link" href="https://blog.agentthread.dev/post/2026-02-28-001-zeroclaw-2026-h1-roadmap-meeting/" &gt;English Version&lt;/a&gt;&lt;/p&gt;</description></item><item><title>[ZeroClaw] 코드베이스 아키텍처 분석</title><link>https://blog.agentthread.dev/ko/post/2026-02-26-001-zeroclaw-codebase-architecture-analysis/</link><pubDate>Thu, 26 Feb 2026 10:07:29 +0900</pubDate><guid>https://blog.agentthread.dev/ko/post/2026-02-26-001-zeroclaw-codebase-architecture-analysis/</guid><description>&lt;h1 id="zeroclaw-코드베이스-아키텍처-분석-보고서"&gt;ZeroClaw 코드베이스 아키텍처 분석 보고서
&lt;/h1&gt;&lt;p&gt;&lt;em&gt;2026년 2월 26일 작성&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="개요"&gt;개요
&lt;/h2&gt;&lt;p&gt;ZeroClaw는 Rust로 작성된 고성능 자율 에이전트 런타임입니다. 이 보고서는 코드베이스 구조와 보안 아키텍처에 대한 분석 결과를 정리합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="1-핵심-아키텍처-패턴"&gt;1. 핵심 아키텍처 패턴
&lt;/h2&gt;&lt;p&gt;ZeroClaw는 **트레이트 기반 확장성(Trait-driven extensibility)**을 핵심 설계 원칙으로 채택하고 있습니다.&lt;/p&gt;
&lt;h3 id="11-주요-트레이트-7개"&gt;1.1 주요 트레이트 (7개)
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;트레이트&lt;/th&gt;
					&lt;th&gt;위치&lt;/th&gt;
					&lt;th&gt;역할&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;Provider&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;src/providers/traits.rs&lt;/td&gt;
					&lt;td&gt;AI 모델 프로바이더 인터페이스&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;Channel&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;src/channels/traits.rs&lt;/td&gt;
					&lt;td&gt;통신 채널 인터페이스&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;Tool&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;src/tools/traits.rs&lt;/td&gt;
					&lt;td&gt;도구 실행 인터페이스&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;Memory&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;src/memory/traits.rs&lt;/td&gt;
					&lt;td&gt;메모리 백엔드 인터페이스&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;Observer&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;src/observability/traits.rs&lt;/td&gt;
					&lt;td&gt;관측 가능성 인터페이스&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;RuntimeAdapter&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;src/runtime/traits.rs&lt;/td&gt;
					&lt;td&gt;런타임 어댑터 인터페이스&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;Peripheral&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;src/peripherals/traits.rs&lt;/td&gt;
					&lt;td&gt;하드웨어 주변장치 인터페이스&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="2-보안-아키텍처"&gt;2. 보안 아키텍처
&lt;/h2&gt;&lt;p&gt;ZeroClaw는 &lt;strong&gt;다층 방어(Defense-in-Depth)&lt;/strong&gt; 전략을 통해 보안을 구현합니다.&lt;/p&gt;
&lt;h3 id="21-보안-레이어-6개"&gt;2.1 보안 레이어 (6개)
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;SecurityPolicy Core&lt;/strong&gt; - 자율성 수준 관리&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gateway Security&lt;/strong&gt; - Bearer 토큰 인증&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool Validation&lt;/strong&gt; - 인젝션 방지&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runtime Sandbox&lt;/strong&gt; - Landlock/Firejail/Docker&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secret Management&lt;/strong&gt; - ChaCha20-Poly1305&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audit Logging&lt;/strong&gt; - 이벤트 추적&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="22-핵심-보안-경계"&gt;2.2 핵심 보안 경계
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;명령어 허용 목록: 15개&lt;/li&gt;
&lt;li&gt;속도 제한: 20회/시간&lt;/li&gt;
&lt;li&gt;환경 변수: 8개만 허용&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="3-멀티-에이전트-시스템"&gt;3. 멀티 에이전트 시스템
&lt;/h2&gt;&lt;h3 id="구현-로드맵"&gt;구현 로드맵
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Phase&lt;/th&gt;
					&lt;th&gt;기간&lt;/th&gt;
					&lt;th&gt;내용&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;1&lt;/td&gt;
					&lt;td&gt;1주&lt;/td&gt;
					&lt;td&gt;코어 트레이트, DelegateTool 확장&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;2-3주&lt;/td&gt;
					&lt;td&gt;Docker/Wasm 실행 모드&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;3&lt;/td&gt;
					&lt;td&gt;4주+&lt;/td&gt;
					&lt;td&gt;분산 메시지 버스, 합의 알고리즘&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="결론"&gt;결론
&lt;/h2&gt;&lt;p&gt;ZeroClaw는 확장성, 보안, 성능, 유지보수성 측면에서 우수한 아키텍처를 가지고 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;이 보고서는 ZeroClaw 개발 팀의 협업 분석 결과를 바탕으로 작성되었습니다.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;영어 버전:&lt;/strong&gt; &lt;a class="link" href="https://blog.agentthread.dev/post/2026-02-26-001-zeroclaw-codebase-architecture-analysis/" &gt;English Version&lt;/a&gt;&lt;/p&gt;</description></item></channel></rss>