「무엇이든 리버스 엔지니어링」이라 주장하는 MCP 서버 REA를 직접 만든 Electron 앱으로 테스트해봤습니다
요약
GitHub Trending에서 인기 있는 코딩 에이전트 CLI/MCP 서버인 REA를 직접 만든 Electron 앱으로 테스트했습니다. 그 결과, REA는 앱의 구조적 사실(IPC 배선, 공개 API 등)은 매우 정확하게 재구축하지만, 숨겨진 기능에 대한 '설명' 자체는 자동적으로 생성하지 못함을 확인했습니다.
핵심 포인트
- REA는 앱의 구조적 사실을 수치 수준으로 완벽히 파악함.
- 숨겨진 로직이나 의도에 대한 설명은 직접 해석해야 함.
- 광고 문구는 '사실 수집'을 의미하며, '설명문 작성'이 아님.
- 정적 JavaScript/Electron 분석만으로 충분한 검증이 가능했음.
요약
GitHub Trending에서 급상승했던 morluto/rea (MIT 라이선스, npm: rea-agents, 검증 시점 최신 버전 5.0.0)는 코딩 에이전트가 네이티브 바이너리, Electron/JS 앱, .NET 어셈블리 등을 분석하여 "기능의 동작을 조사"하기 위한 CLI/MCP 서버입니다.
필자는 이 도구를 **직접 작성한 최소한의 Electron 앱(할인 로직 + 숨겨진 디버그 기능 포함)**을 대상으로 실제로 구동시키며, "어디까지 정확하게 기능을 설명할 수 있는지"를 정답을 미리 알고 있는 상태로 검증했습니다.
결론부터 말하자면, 앱의 구조(IPC 배선・공개 API・윈도우 설정) 재구축은 수치 수준으로 완벽하게 정확했지만, "왜 그것이 숨겨진 기능인지"라는 한 문장의 설명까지는 자동적으로 나오지 않았습니다. 따라서 반환된 Evidence(구조화된 사실의 집합)를 직접 읽고 연결해야 했습니다. 광고 문구인 "Reverse Engineer Anything"은 "도구가 설명문을 작성해 준다"는 의미가 아니라, "설명에 필요한 정확한 사실을 모아준다"는 의미로 이해하는 것이 실태에 가깝습니다.
출처
- 리포지토리: https://github.com/morluto/rea (MIT License)
- npm 패키지: https://www.npmjs.com/package/rea-agents (검증 시점 dist-tag
latest는5.0.0) - GitHub Trending TypeScript (2026-10-07 기준으로 오늘 +4,666 star, 14,340 star)
일본어 기사 건수는 2026-10-08 기준으로 확인한 Zenn/Qiita/note 모두 0건이었습니다.
왜 이 검증 범위를 선택했는가
REA는 "네이티브 바이너리(Mach-O/ELF/PE)・Electron/JS 앱・.NET 어셈블리・Android APK"까지 폭넓게 대응한다고 주장하지만, 네이티브 바이너리 분석에는 별도로 Hopper/Ghidra/IDA 중 하나가 필요합니다. 이번에는 다음과 같은 이유로 정적 JavaScript/Electron 분석으로만 범위를 좁혔습니다.
- 무료 OSS인 Ghidra (JDK21+)는 검증 환경에 준비할 수 있었지만, 네이티브 바이너리 분석까지 포함하면 반나절로는 끝나지 않습니다.
- REA 자체 README에는 "정적 JS 분석은 MCP 등록・Hopper・Ghidra 불필요, 앱을 실행하지 않고도 작동한다"고 명시되어 있어, 이 경로만으로도 "에이전트가 어디까지 정확하게 기능을 설명할 수 있는지"라는 검증의 축은 충분히 검증 가능합니다.
- 제3자의 저작물을 무단 분석할 위험을 피하기 위해, 검증 대상은 필자가 직접 작성한 최소한의 앱으로 한정했습니다.
또한, rea setup (MCP 서버로서 에이전트에 등록하는 명령어)는 이 세션의 전역 에이전트 설정 파일을 덮어쓰는 작업이므로 실행하지 않았습니다. 대신, REA 자체 문서에 "MCP 등록 불필요한 공식적인 폴백"으로 명시된 rea CLI를 직접 호출했습니다.
검증 대상: 할인 로직 + 숨겨진 디버그 기능이 있는 자작 Electron 앱
experiments/day-010/sample-app/에 일부러 짧은 변수명(_q, _h, _z)으로 작성한 작은 Electron 앱을 준비했습니다.
// lib/pricing.js (실제 파일 그대로)
function _h(a) {
let s = 0;
...
필자 자신이 작성했기 때문에 정답을 알고 있습니다. 쿠폰 코드의 문자 코드 합계를 7로 나눈 나머지 m에 따라, "VIP-으로 시작하고 m===0이면 50% 할인", "VIP-으로 시작하고 m!==0이면 15% 할인", "VIP-으로 시작하지 않고 m===3이면 일괄 $10 할인", "그 외는 할인 없음"이라는 4분기 로직이 있습니다. 게다가 main.js에는 ipcMain.handle('coupon:debug', ...)와 같이, process.env.COUPON_DEBUG === '1337'일 때만 내부 상태를 반환하는 숨겨진 디버그 채널도 심었습니다. UI 어디에도 설명이 없는 것을 가정한 테스트 케이스입니다.
실제로 Node로 실행하여 정답을 확인했습니다.
$ node -e "const {quote,_hash}=require('./lib/pricing.js'); ..."
SAVE10 100 -> hash=1 price=100
VIP-GOLD 200 -> hash=4 price=170
...
analyze-javascript-application
: 구조 재구축은 완전히 정확했다
$ npx -y rea-agents@latest analyze-javascript-application ./sample-app --format json \
> rea-output/analyze-javascript-application.json
단 한 번의 실행으로 성공(종료 코드 0). 반환된 것은 약 1.3MB/37,802줄의 JSON이었으며, 파일 단위의 AST 레벨 사실(함수・변수・파라미터・정확한 소스 위치・confidence)이 담겨 있었다.
앱 구조를 나타내는 집계값은 모두 정답과 일치했다.
| 항목 | REA 값 | 정답 |
|---|---|---|
| BrowserWindow 수 | 1 | 1 |
| ... | ||
더 나아가, 숨겨진 디버그 기능의 트리거가 되는 e.COUPON_DEBUG | ||
이라는 속성 읽기 역시 confidence: "exact"로, | ||
| 정확한 소스 라인 번호와 함께 첫 번째 분석 결과 안에 이미 기록되어 있었다. |
trace_application_feature
: IPC 채널 이름으로부터의 추적은 성공했지만, '의미'는 연결되지 않았다
REA에 포함된 skill 문서가 권장하는 trace_application_feature를 사용하여,
"coupon:debug 채널이 어떻게 사용되고 있는지"를 추적해 보았다.
우선, 입력 JSON의 형식이 CLI의 --help / --schema / --llms-full 어느 곳에도 적혀 있지 않았다. 빈 객체를 전달하여 유효성 검사 오류 메시지를 읽는 절차를 두 번 반복함으로써,
{"application": <분석 결과 전체>, "seed": {"kind": "string", "value": "coupon:debug", "match": "exact"}}
라는 형태를 특정할 수 있었다. 문서에서 알 수 없었던 부분을, 도구 자체의 실패 시 응답이 알려준 셈이다.
이 형태로 실행하자, coupon:debug이라는 문자열 리터럴로부터 ipc-channel과 ipc-handler 두 건에 정확히 히트했으며, 반환된 28개 노드의 그래프에는 main.js / preload.js / lib/pricing.js / renderer/renderer.js의 모듈 관계, couponApi라는 context-bridge API, BrowserWindow, 양쪽 IPC 채널이 모두 올바르게 포함되어 있었다.
{
"summary": {
"matched_seeds": 2, "traced_nodes": 28, "traced_edges": 43,
...
}
다만 `
⚠️ [IMG:N] 형식 토큰은 이미지 placeholder 입니다. 번역하지 말고 원래 위치에 그대로 유지하세요.
{ "coverage": { "status": "no-match" },
"limitations": ["No graph entity matched the literal seed; this is not evidence that the feature is absent." ] }
trace_application_feature
의 kind: "string"는 AST상의 문자열 리터럴 값(IPC 채널명 등)을 가리키며, 변수명이나 프로퍼티명 그 자체(식별자)는 가져오지 않습니다. 네이티브 바이너리 분석용으로 문자열/프로시저명을 교차 검색할 수 있는 search
커맨드도 있지만, 이는 디렉토리(JS/Electron 앱)에는 사용할 수 없다는 점을 실제로 에러 메시지로 확인했습니다("This target is a directory..."). 즉, JS 분석에서는 알려지지 않은 식별자를 단서로 교차 검색할 방법이 현재로는 없습니다.
"읽기 쉬운 소스 복원"은 별도의 외부 툴 전제였다
recover-javascript-sources
(읽기 쉬운 JS 모듈을 복원하는 기능)도 시도했지만, 이쪽은 실패했습니다.
{
"code": "capability_unavailable",
"details": {
...
README의 최상위 설명에서는 "정적 JS 분석은 Hopper/Ghidra 불필요"라고 강조하지만, "읽기 쉬운 소스로의 변환"에는 별도의 외부 툴(Wakaru)이 필요하며, 이는 REA가 자동 설치해주지 않습니다. 'Evidence를 얻는' 부분과 '읽기 쉬운 소스로 변환하는' 부분은 실제로 작동시켜 보니 서로 다른 의존성을 가진다는 점을 알게 되었습니다.
추가: 두 번의 실패는 모두 실제 해결 가능했다
아티클 공개 전 리뷰에서 위 두 가지 실패에 대해 "정말로 해결할 수 없는지"를 직접 시도해 보았습니다. 결론적으로, 둘 다 REA 자체의 결함이 아니라 문서에 쓰여 있지 않은 운용상의 전제였습니다.
node-id로 시드 해결하기
식별자의 "no-match"는 trace_application_feature
의 seed.kind에는, 문서화되지는 않았지만 `
이는 단일 번들 파일(webpack 등으로 하나로 합쳐진 minified JS)을 입력으로 받는 설계였으며, 여러 파일 구성의 sample-app 디렉토리를 통째로 넘기는 것은 애초에 대상 외의 사용법이었다. 대상을 sample-app/renderer/renderer.js라는 단일 파일로 좁히자, exit_code: 0으로 정상 종료되었고, 복원된 모듈이 실제로 출력되었다.
이 수정 사항은 이후 누구나 재현할 수 있는 형태로 정리되었다. experiments/day-010/package.json에 [email protected]을 고정(pin)하고, npm install만으로 동일한 환경이 재현되도록 해두었다.
요약
- 앱의 구조(IPC 배선・공개 API・창 설정)를 수치적 수준에서 완벽하게 정확하게 재구축했다.
- '숨겨진 디버그 채널이 존재한다'는 사실 자체는 첫 번째 분석만으로도 Evidence 안에 이미 존재하고 있었다.
- 하지만 '왜 그것이 숨겨진 기능인지(어떤 환경 변수의 어떤 값으로 게이트되어 있는지)'에 대한 한 문장의 설명은 자동으로 나오지 않았고, Evidence 그래프를 직접 읽고 연결해야 했다.
- 이번과 같은 경도의 난독화(짧은 변수명 정도)라면, 사람이
lib/pricing.js를 직접 읽어도 10분도 안 되어 정답에 도달할 수 있다. REA의 가치는 '그 읽는 작업을 생략해 주는 것'이 아니라, '정확한 단편적 사실(파일 위치・신뢰도(confidence)・코드 흐름)을 모아주어 오독이나 누락을 줄여주는 데' 있다는 것이 실제로 직접 해본 후의 결론이었다. - 처음 발견된 두 가지 실패(식별자 검색의 "no-match", Wakaru 미도입으로 인한 소스 복원 실패)는 둘 다 REA 자체의 결함이 아니라, 문서에 쓰여 있지 않은 운영상의 전제가 원인이었으며, 실제로 양쪽 모두 해결할 수 있었다.
검증에 사용된 코드 및 실행 로그 전문은 본 기사의 GitHub 리포지토리에 올려두었다.
논의

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기