Echo사는 AI 보안 에이전트를 활용하여 NanoClaw 컨테이너 이미지에서 약 1,400개의 보안 취약점(CVE)을 성공적으로 제거한 과정을 공유했습니다. 주요 라이브러리 업그레이드, 직접 패치 및 백포팅(Backporting)을 거쳐 자체적으로 유지보수하는 보안 강화 OS인 'Echo OS'를 기반으로 구동됨으로써 근본적인 보안 위협을 해결했습니다. 이는 AI 에이전트 및 오픈소스 기반 애플리케이션의 컨테이너 보안 및 취약점 관리 실무에 중요한 참고자료가 됩니다.
번역된 본문
지난주 우리는 오픈소스 프로젝트의 비전과 보안을 확장하기 위해 Echo와 NanoClaw의 파트너십을 발표했습니다. 이 글에서는 Echo의 에이전트 기반 보안 강화(agentic hardening) 과정이 구체적으로 어떻게 작동하는지 그 내부를 공개하고자 합니다.
우리는 어떻게 CVE를 탐지할까요?
문제를 해결하기 전에 먼저 이미지에 실제로 무엇이 포함되어 있는지 완전하고 신뢰할 수 있는 전체 그림이 필요합니다. 우리는 Trivy, Grype, Wiz를 포함한 여러 독립적인 취약점 스캐너를 사용하여 상위(NanoClaw) 컨테이너 이미지를 스캔하고 분석합니다. 다음은 Grype를 사용하여 오픈소스 NanoClaw 이미지를 스캔한 원시 결과를 심각도 순으로 정렬한 것입니다. 그리고 이것이 Grype 및 Trivy 모두에서 비교 가능한 다른 에이전트 런타임(Hermes 및 OpenClaw)과 어떻게 비교되는지 보여줍니다 (이 비교에는 우리가 곧 자세히 살펴볼 NanoClaw Echo 이미지도 추가했습니다).
이제 본격적인 수정 및 CVE 감소 과정으로 넘어가 보겠습니다.
1단계: 안전하게 업그레이드할 수 있는 것부터 시작하기
이미지의 모든 라이브러리는 각각 해결해야 할 독립적인 문제입니다. 따라서 우리가 가장 먼저 할 일은 탐지된 결과를 '안전하게 업그레이드 가능(safe to bump)'한 것과 '실제 작업이 필요(needs real work)'한 것으로 분류하는 것입니다. 쉬운 승리는 NanoClaw를 손상시키지 않고 업그레이드할 수 있다는 것을 알고 있는 라이브러리입니다. Chromium이 대표적인 예입니다. Chromium은 하위 호환성이 뛰어나기로 잘 알려져 있으므로, 해당 업데이트를 신뢰하고 자신 있게 버전을 올릴 수 있습니다. 이렇게 처리하고 나면 남는 것은 수정할 수 없는 CVE와 큰 버전 점프가 필요한 CVE들입니다. Chromium 관련 CVE를 제외하고 나면, 여전히 약 600개의 취약점이 남아있는 것을 확인할 수 있습니다. 다음 단계는 무엇일까요?
2단계: 심층적인 조사가 필요한 업그레이드
일부 업그레이드는 주요 버전(Major version) 점프를 필요로 하며, 이는 초기 설정 그대로 작동하지 않을 가능성이 높습니다. 이러한 경우에는 우리가 직접 패치를 적용하고, 애플리케이션을 손상시키지 않으면서 패치가 실제로 유효한지 검증해야 합니다. 구체적인 예는 다음과 같습니다: Hono의 node-server
서류상으로는 이것이 큰 버전 점프처럼 보였습니다. 하지만 소스 코드를 깊이 파고들어 보니, 현재 설치된 버전(1.19.14)과 매우 가까운 버전 내에서 이미 수정 사항이 제공되고 있다는 것을 발견했습니다 (비록 스캐너의 취약점 데이터베이스는 아직 이 사실을 인지하지 못했더라도). 오픈소스 커뮤니티에 대한 기여의 일환으로, 우리는 이를 해당 프로젝트의 보안 권고문(advisory)에 추가했습니다 (이 과정은 Echo에서 매일 수행하는 작업입니다). 이제 다음 단계인 '수정 불가(Won't fix)' 항목으로 넘어갑니다.
3단계: 패치 및 백포팅(Backporting)
그다음에는 벽에 부딪히게 됩니다. 즉, '수정 불가'로 표시되거나 배포판 유지보수자가 새로운 주요 버전에서만 수정하도록 남겨둔 나머지 항목들입니다. 앞서 언급했듯 이러한 새 버전으로의 이동은 애플리케이션을 손상시킬 가능성이 큽니다. 이러한 경우에 선택하는 수정 전략은 백포팅입니다. 이는 새로운 버전의 패키지에서 패치를 가져와 애플리케이션에 필요한 이전 버전에 적용하는 것을 의미합니다. 우리의 경우, 최신 상위 버전에서 수정 사항을 찾은 다음 NanoClaw의 소스 코드에서 직접 작업을 시작합니다. 여기서는 다음 세 가지 주요 과제 사이에서 균형을 잡아야 합니다:
올바른 수정 사항 찾기: 버그가 있는 위치를 파악하고, 수정 커밋을 추적하며, 수정 사항이 진정으로 안전하고 완전한지 확인해야 합니다. 모든 수정 소스가 안전한 것은 아니므로 추가적인 조사가 필요합니다.
애플리케이션을 손상시키지 않고 적용하기: 패치는 기존 애플리케이션과 완벽하게 호환되어야 합니다.
검증하기: 호환성, 기능성 및 CVE가 실제로 해결되었는지 확인해야 합니다.
애플리케이션 종속성 외에도, 모든 것의 기반이 되는 운영체제(OS)가 있습니다. NanoClaw의 Dockerfile은 Debian 12를 기반으로 구축됩니다. Debian 12 기본 이미지는 OS 수준의 취약점을 길게 끌고 옵니다. 바로 이 지점에서 'Echo OS'가 중요한 역할을 합니다. Echo가 유지 관리하는 이 리눅스 배포판은 Ubuntu, Debian, RHEL, Amazon Linux 등 일반적인 상위 배포판과 호환됩니다. 이 모든 부분은 소스에서 빌드되므로 당사의 AI 패치 에이전트가 지속적으로 패치를 적용할 수 있습니다. 이는 수천 개의 패치된 OS 패키지를 제공하며, 지금까지 110만 개 이상의 CVE를 제거했습니다.
Back Featured How we eliminated 1,400 CVEs in NanoClaw's container images Eylam Milner Aug 04, 2026 | 15 Minutes Featured Agentic security CVE remediation Company news Last week we announced Echo's partnership with NanoClaw, designed to extend the vision and security of the open source project. In this post, we want to pull back the curtain and show you exactly how Echo's agentic hardening process works. How do we detect CVEs? Before we can fix anything, we need a complete, trustworthy picture of what's actually in the image. We scan and analyze the upstream NanoClaw container using several independent vulnerability scanners, including Trivy, Grype, and Wiz. Here's the raw result of scanning the open source NanoClaw image with Grype, sorted by severity: And here's how that stacks up against comparable agent runtimes (Hermes and OpenClaw) across both Grype and Trivy (we also added the NanoClaw Echo image to this comparison - which we will dive into soon): Now, let’s move to the fixing and CVE reduction. Step 1: Start with what we can safely bump Every library in the image is its own problem to solve. So the first thing we do is separate the findings into "safe to bump" and "needs real work." The easy wins are the libraries we know we can upgrade without breaking NanoClaw. Chromium is a great example. It's well known for backward compatibility, so we can trust their updates and bump with confidence. What we are left with are the CVEs that can’t be fixed, and the ones with major jumps. Once we strip out the Chromium-related CVEs, we're still left with roughly 600 vulnerabilities that need fixing. So what happens next? Step 2: The bumps that require real research Some upgrades require a major version jump, which will likely not work out of the box. In these cases, we have to patch it ourselves and verify the patch actually holds without breaking the app. See below, a concrete example: Hono's node-server On paper, this looked like a major jump. But when we dug into the source code, we found the fix was available within a much closer version to what was installed, version 1.19.14 (even if the scanners’ vulnerability databases weren't aware of yet). As part of our contribution to the open source community, we added it to their advisory (it’s a process we do on a daily basis in Echo). We move on to the next step - the “won’t fix” ones. Step 3: Patching and Backporting Then you hit the wall: the rest of the findings that are marked as won't fix or that the distro maintainers only fix in new majors, which as mentioned above are likely to break your app. For these, the fix strategy of choice is to backport, which means taking a patch from a newer version of the package and applying it to the older version that the app requires. In our case, that means we find the fix in the latest upstream version, and start working on NanoClaw’s source code directly. There are three main challenges we need to balance between: Finding the right fix - understanding where the bug is, tracing the fixing commit, and confirming the fix is genuinely safe and complete - not all fix sources are safe to use, so further research is needed. Applying it without breaking anything - the patch has to be compatible with the existing app cleanly. Validating - compatibility, functionality and that the CVE was truly resolved. On top of the application dependencies, there's the operating system underneath everything. NanoClaw's Dockerfile builds on Debian 12 Debian 12 base images bring a long tail of OS-level vulnerabilities with them. This is where Echo OS comes into play. It's the Linux distro that Echo maintains, and it's compatible with the common upstream distros - Ubuntu, Debian, RHEL, Amazon Linux, and more. Every part of it is built from source so it can be continuously patched by our AI patching agents. It offers thousands of patched OS packages and has eliminated more than 1.1 million CVEs across them. How Echo conducts backporting Let’s grab one of the latest backports we did as part of the NanoClaw project. We’ll focus on CVE-2025-59375 in expat. This process is conducted by Echo’s proprietary backporter agent . Why did we pick CVE-2025-59375? It's the hardest kind of backport there is - a security fix that isn't a bounds check but a new subsystem threaded through the middle of the library, carried from a newer upstream release down into the older version we ship, and one where the upstream maintainer explicitly warned distributors not to attempt a partial cherry-pick. How can CVE-2025-59375 be exploited? An attacker sends a small, entirely well-formed XML document and the parser allocates a wildly disproportionate amount of heap. Upstream's own figure: a ~250 KiB document caused roughly 800 MiB of allocation - an amplification factor of about 3,300. The result is memory exhaustion and process death, or an OOM kill that takes neighbours with it. Why is it hard to do a backport fix? expat already had protection against amplification attacks - the billion-laughs defence, which enforces a 100x limit once output passes 8 MiB. But that accounting measures parsed output bytes, direct plus entity expansion. It never looked at the actual heap. So you can stay comfortably inside the entity budget while the parser's internal structures - hash tables, string pools, DTD scaffolding - balloon. he fix had to introduce real allocation accounting, and that's invasive: • Every internal allocation now goes through `expat_malloc` / `expat_free` / `expat_realloc`, which prepend a `size_t` header to every block so that free and realloc know how much to subtract. That changes the pointer handed back to callers. Mix a raw allocation with a tracked free anywhere and you corrupt the heap. • Structures that used to carry the memory-function table now carry the parser handle instead, because the counter lives on the root parser. That cascades into signature changes across `dtdCreate`, `dtdReset`, `dtdDestroy`, `dtdCopy`, `hashTableInit`, `poolInit`, `copyString`. • `parserCreate` gains a parent-parser argument so external-entity child parsers charge the root parser's budget, with strict initialisation ordering - some fields must be set before any tracked allocation can happen. • Three categories of allocation must deliberately bypass tracking: the app-facing `XML_MemMalloc`/`XML_MemRealloc`, the main input buffer from `XML_GetBuffer`, and the content model handed to the element-declaration handler (applications free that one with plain `free()`). Get any of them wrong and you either corrupt memory or start rejecting legitimate documents. What did we do and what eventually worked? The upstream fix was written against a newer expat than the one we ship, so this is a backport from a higher version down to a lower one, and it is large in every direction. Upstream's change spans 17 files; ours ends up touching 9, across the parser core, the public headers, the CLI tool, the documentation and the test suite. The reference patch does not apply to our tree, and landing only the parts that happen to fit produces exactly the state upstream warns about - some allocations tracked and some not, which is worse than not patching at all. What worked was treating it as a rebase rather than an apply. Our agent runs the whole thing end to end: apply, build, run the full upstream test suite, diagnose each failure into a specific class, hand it to the fixer built for that class, and repeat until the build and the entire test suite pass. The classes are things like a hunk whose line numbers no longer match, surrounding context that has changed, a file that has moved or been split, code that no longer compiles against this version, and tests that fail after a clean compile - each with its own dedicated fixer. Three things had to be solved for this patch specifically: 1. A renamed build guard. The fix wraps the new tracker in