메뉴
HN
Hacker News • 4일 전

'충분히 좋은' AI 시대의 보안 감사

IMP
7/10
핵심 요약

보안 업체가 Miden zkVM 감사를 준비하며 AI 에이전트를 활용해 LSP 서버, 디컴파일러, 정적 분석 엔진, Lean 형식 모델을 6개월간 직접 구축한 사례를 소개한다. 이렇게 만든 도구들이 실제 보안 취약점(악성 프로버가 Falcon 서명을 위조해 자금을 탈취할 수 있는 미검증 입력 문제)을 발견했고, 95개의 기계 검증 정확성 증명을 확보했다. 코드 리뷰 외에도 감사 준비 단계부터 AI가 감사 품질과 깊이를 크게 높일 수 있음을 보여준다.

번역된 본문

보안 업체들은 에이전트 하네스를 코드베이스에 투입해 수십 개의 버그를 발견했다는 블로그 게시물을 다수 발표해 왔습니다(우리도 그중 하나입니다). 하지만 이런 글들은 에이전트 기반 코드 리뷰에 초점을 맞추는 경향이 있으며, 이는 우리가 보안 리뷰에서 AI를 활용하는 방식의 한 측면일 뿐입니다. 우리는 다른 관점을 제시하고자 합니다. 코드 리뷰가 시작되기 전부터, 에이전트는 이제 리뷰의 품질과 깊이를 높이는 맞춤 도구와 형식 모델을 구축할 수 있게 해줍니다.

우리는 최근 Miden VM을 리뷰했습니다. 이는 자체 커스텀 어셈블리 언어를 사용하며 개발자 도구가 거의 없는 새로운 영지식(zero-knowledge) VM입니다. 준비 과정에서 우리는 6개월 동안 에이전트를 활용해 LSP 서버, 디컴파일러, 정적 분석 엔진, 그리고 VM 실행기의 Lean 모델을 처음부터 구축했습니다. 이 도구들은 실제 보안 문제를 발견했습니다. 예를 들어, 검증되지 않은 프로버(prover) 제공 입력으로 인해 악성 프로버가 Falcon 서명을 위조하고 Miden 계정 보유자의 자금을 탈취할 수 있는 문제가 있었습니다. 또한 Lean 작업을 통해 Miden 코어 라이브러리의 상당 부분을 다루는 95개의 기계 검증 정확성 증명을 확보했습니다.

Miden zkVM 감사 2025년 말, Miden 팀이 출시 전에 자신들의 영지식 VM의 일부를 리뷰해 달라고 요청했습니다. 리뷰 범위 중 일부는 Miden 코어 라이브러리였는데, 여기에는 Miden 어셈블리(MASM)라는 커스텀 어셈블리 언어로 작성된 소규모 암호학 프리미티브들이 포함되어 있습니다. 이는 우리에게 정말 흥미로운 일이었습니다. 우리가 한 번도 본 적 없는 저수준 커스텀 어셈블리 언어로 복잡한 암호 코드를 작성하는 고신뢰성(high-assurance) 프로젝트였기 때문입니다. 동시에 독특한 과제들도 있었습니다.

우선, Miden VM은 스택 머신 아키텍처를 구현합니다. 즉, 각 명령어는 스택에서 읽은 값에 대해 동작하고, 그 결과는 다시 스택의 최상단에 기록됩니다. 개념적으로는 단순하지만, 이로 인해 MASM으로 작성된 코드는 리뷰하기 어렵습니다. 명령어의 입력과 출력이 스택에서 읽히며 항상 암시적이기 때문입니다. 또한 Miden VM은 완전히 새로운 아키텍처이기 때문에 IDE 지원, LSP(Language Server Protocol) 서버, 린터 같은 개발자 도구가 거의 존재하지 않았습니다.

구현이 아직 완성되지 않은 상태였기에 리뷰까지 6개월의 시간이 있다는 것을 알고 있었고, 우리는 스스로에게 물었습니다. "코드베이스에서 가능한 한 많은 버그를 찾아내려면 우리의 시간과 토큰을 무엇에 쓸 수 있을까?"

도구를 전부 만들자! MASM에는 개발자 도구가 없었기 때문에, 프로젝트 시작 시점에 어떤 도구가 있었으면 좋겠는지 자문하는 것부터 시작했습니다. 우리는 보통 VS Code로 코드를 리뷰하며, 구문 강조와 코드 내비게이션은 가독성과 코드베이스 전체의 데이터 흐름 추적에 필수적입니다. 이를 위해 LSP 서버와 이에 대응하는 VS Code 확장이 필요했고, 며칠 만에 Claude가 우리가 원하는 기능 대부분을 갖춘 작동하는 프로토타입을 만들었습니다. 구문 강조, 정의로 이동(goto definition), 코드 참조 찾기, 프로시저 독스트링을 호버로 표시하는 기능 등이었습니다. 이러한 기본 기능을 갖춘 후, 인라인 명령어 문서 표시와 개별 명령어의 스택 효과(stack effects) 표시 같은 언어 특화 기능을 추가하기로 했습니다.

LSP 서버를 구축한 후에는 수동 리뷰와 에이전트 기반 리뷰를 지원하기 위해 고수준 의미 정보를 제공하는 다른 방법들을 생각하기 시작했습니다. 리뷰어가 보고 있는 프로시저의 고수준 제어 흐름과 데이터 흐름을 빠르게 이해할 수 있도록, VS Code UI 안에서 MASM 프로시저의 충실한 디컴파일 결과를 제공할 수 있는지 확인해보는 것이 흥미로울 것 같았습니다.

MASM의 경우 이는 처음 보다 더 어려운 문제입니다. 스택 머신 리프팅과 디컴파일은 잘 연구된 문제이지만, 손으로 작성된 MASM을 디컴파일하는 것은 여러 이유로 여전히 어렵습니다. 코어 라이브러리의 대부분의 프로시저는... (이하 원문 생략)

원문 보기
원문 보기 (영어)
Page content Security firms have published numerous blog posts describing how they pointed their agent harness at a codebase and found dozens of bugs ( we’re one of them ). However, these posts tend to focus on agentic code review, which is just one aspect of how we use AI in our security reviews. We want to give a different perspective: before code review even starts, agents now allow us to build custom tooling and formal models that improve the quality and depth of our reviews. We recently reviewed the Miden VM, a new zero-knowledge VM with its own custom assembly language and almost no developer tooling. To prepare, we spent six months having our agents build an LSP server , a decompiler , a static analysis engine , and a Lean model of the VM executor from scratch. These tools found real security issues, like an unvalidated prover-supplied input that would let a malicious prover forge Falcon signatures and steal funds from Miden account holders. Additionally, the Lean work produced 95 machine-checked correctness proofs, covering a large component of the Miden core library. Auditing the Miden zkVM In late 2025, the Miden team came to us to have parts of their zero-knowledge VM reviewed before launch. Part of the review was scoped to cover the Miden core library, which contains a small set of cryptographic primitives written in a custom assembly language called Miden assembly (MASM). This made us genuinely excited, as it was right up our alley: a high-assurance project writing complex cryptographic code in a low-level custom assembly language that we had never seen before. At the same time, it also presented some unique challenges. To start, the Miden VM implements a stack-machine architecture . This means that each instruction operates on values read from the stack, and the result of the instruction is then written back to the top of the stack. While conceptually simple, this makes code written in MASM challenging to review, since instruction inputs and outputs are read from the stack and are always implicit. Additionally, since the Miden VM is a completely new architecture, very little existed in terms of developer tooling like IDE support, Language Server Protocol (LSP) servers, and linters. We knew that we had six months to prepare for the review, since the implementation was not yet feature complete, so we asked ourselves: “What could we spend our time and tokens on to make sure that the review would root out as many bugs as possible in the codebase?” Building all the tools! Since MASM lacked developer tooling, we started out by asking ourselves what kind of tools we would like to have available when the project started. We typically use VS Code to review code, and syntax highlighting and code navigation are essential for readability and being able to follow data flow throughout a codebase. We needed an LSP server and a corresponding VS Code extension for this, and within a few days we had Claude build a working prototype that provided most of the functionality we wanted: features like syntax highlighting, goto definition, finding code references, and displaying procedure docstrings on hover. With these fundamental features in place, we also decided to add more language-specific features like displaying inline instruction documentation, and stack effects for individual instructions. Having built the LSP server, we started thinking about other ways to provide high-level semantic information to support manual and agent-driven review. We thought it would be interesting to see if we could provide faithful decompilation for MASM procedures inside the VS Code UI, to help the reviewer quickly understand the high-level control flow and data flow of the procedures they were looking at. For MASM, this is a harder problem than it first appears. Stack machine lifting and decompilation is a well-studied problem, but decompiling hand-written MASM is still difficult for a number of reasons. Most procedures in the core library do not have declared signatures, which means that the number of inputs and outputs must be inferred from context. MASM procedures do not conform to a well-defined calling convention, and the net stack effect of such calls is generally impossible to determine statically. This means that all analysis failures propagate up the call chain. While-loops do not need to be stack neutral, which means that the while-loop condition may occupy a different stack slot in each iteration. This also makes it impossible to map instruction inputs to stack slots for subsequent instructions. Different branches in conditional statements may have different stack effects, which similarly makes stack tracking and signature inference challenging. This meant that we could not expect to be able to decompile all MASM procedures if we also wanted the decompiled output to be correct. We therefore focused on decompiling a well-defined subset of MASM correctly. During the development of the decompiler, we alternated between using Claude for planning and development and Codex for code review. Whenever we had implemented a new feature, we had agents decompile a randomized set of procedures from the core library and compare the result to the original MASM to look for regressions. Any issues found were added as regression tests to be fixed by the model. The decompiler represented the single largest effort of the tooling development for this project, with over 100 AI-generated commits over multiple months. The main benefit of this work turned out to be the decompiler’s internal analysis frameworks and intermediate representation, which we could reuse for static analysis, rather than the full decompilation pipeline. With the decompiler in place, we had access to an intermediate representation of each procedure, with instruction inputs and outputs populated as expressions. This allowed us to bring all the standard static analysis machinery (like data flow analysis) to bear on the problem of finding bugs in the MASM code. We used this to build a number of analysis passes over the intermediate representation, answering questions like the following: Are prover-supplied advice values 1 like remainders and modular inverses properly validated? Are type constraints (e.g. an input being a 32-bit integer or a boolean) enforced? Are local variables initialized across all execution paths? One way to explore these issues is abstract interpretation. The idea behind this technique is simple. Instead of running the program with real numbers, the analysis tracks the types of values that could be on the stack at each step, like “a 32-bit integer” or “unknown.” It walks through the code again and again until no new information is found. Since it always keeps track of every possible value (with a little extra room), it can never miss a real case. So if a check passes in the analysis, it is guaranteed to hold in every actual run of the program. We used Claude and Codex to build a general abstract interpretation engine, and then implemented a number of concrete analysis passes on top of it, having the agents switch between development and code review as described above. We also decided to have the agents design and build command-line interfaces for both the decompiler and a new MASM linter to make the new tools available to agent-driven code-review workflows as well. Finding all the bugs! During the actual review, these analyses identified over 400 unique locations 2 where type validation could be improved (all of them reachable from the public API of the library) as well as one high-severity finding. The high-severity issue was due to an underconstrained advice value in the mod_12289 procedure, which reduces a 64-bit value modulo 12289 , with a quotient and remainder provided as advice values by the prover. The quotient is checked to ensure that it is a valid 64-bit value (represented as two 32-bit limbs), but the remainder is never validated before it is passed to the 3