CVE-2026-47391

CVE-2026-47391

Area
AI Agent / CI/CD
Credit
Public Attribution
Credit Note
foxirain — GHSA reporter (public attribution)
CVSS
9.8
CVSS Source
CNA / Vendor
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE
CWE-95 · CWE-306
Doc QA
Reviewed
Evidence Level
Dynamic Product Integration
Evidence Ref
772f151c2a4804bea59fbfc8d9b536816c60de48
Fixed
Fixed Version
>= 4.6.40
Impact
Code Execution
Key Finding
인증 없이 공개된 A2A endpoint가 실제 LLM tool 선택과 eval() 실행까지 이어져 원격 코드 실행이 가능했습니다.
Published
Jul 21, 2026
Released
Reported
Reviewed At
Sep 4, 2026
Role
Discovery
Root Cause
PoC
Dynamic Validation
Disclosure
Fix Review
Evidence Curation
Score Note
GitHub CNA · CVSS v3.1 9.8 (Critical)
Severity
Critical
Status
Public
Target
PraisonAI <= 4.6.39 · first-party A2A server example
Template Version
v2

01 Summary

🔎
PraisonAI의 공개 A2A 예제는 인증 없이 0.0.0.0에 JSON-RPC endpoint를 열고, LLM이 선택한 calculator tool이 입력을 eval()에 전달했습니다. 원격 요청으로 server process에 marker file을 만들었습니다.
구분
확인 내용
Component
PraisonAI first-party examples/python/a2a/a2a-server.py · calculator tool
Attack Input
unauthenticated HTTP JSON-RPC prompt
Preconditions
공식 예제 또는 같은 구조의 agent가 public interface에 노출되고 실제 LLM·tool 호출 활성
Sink
인증 없는 public bind와 calculate(expression)의 Python eval() 결합
Verified
HTTP → agent.chat() → Gemini tool selection → eval()로 marker file 생성
Impact
server process 권한의 remote code execution
Fix Principle
4.6.40에서 eval() calculator를 제한된 AST evaluator로 교체; public bind 시 인증 강제·loopback 기본값·위험 tool 제한; fix commit에 적용됐다고 주장하지 않음
목차 · 섹션으로 이동

Attack Chain

검증 결론: 문자열 유사 모델이 아니라 실제 Gemini tool-selection 경로를 거쳐 server process에 side effect가 생기는 end-to-end RCE를 확인했습니다.

Disclosure Timeline

Date
Event
Evidence
2026-05-10 15:17:23 UTC / 05-11 KST
gemini/gemini-2.5-flash-lite 실제 LLM marker 생성 성공
실제 모델 success log
2026-05-19
GHSA 공개 및 CVE 예약
GHSA metadata
2026-07-21
CVE 레코드 공개
CVE record
2026-07-23
CVE 레코드·CISA ADP 갱신
CVE record·CISA ADP
2026-08-23
공개 evidence 최초 감사
공개 evidence audit
전체 타임라인 원문
  • 2026-05-10 15:17:23 UTC / 05-11 KSTgemini/gemini-2.5-flash-lite 실제 LLM marker 생성 성공
  • 2026-05-19 — GHSA 공개 및 CVE 예약
  • 2026-07-21 — CVE 레코드 공개
  • 2026-07-23 — CVE 레코드·CISA ADP 갱신
  • 2026-08-23 — 공개 evidence 최초 감사

02 Vulnerability

취약점 개요

PraisonAI의 first-party A2A server 예제는 agent를 JSON-RPC로 호출할 수 있게 했습니다. 예제 server는 0.0.0.0에 bind하고 auth token 없이 /a2a를 노출했으며, 등록된 calculate(expression) tool은 Python eval()을 사용했습니다.
위험한 예제 코드만 지적하는 대신 unauthenticated HTTP request가 A2A dispatch, agent.chat(), 실제 LLM tool selection을 지나 eval에 도달하는지 확인했습니다. Gemini 모델을 사용한 검증에서 server process가 marker file을 만들었습니다.
외부 입력
인증 없는 /a2a의 JSON-RPC message/send
결정 계층
실제 LLM의 tool selection
위험한 sink
calculate(expression)의 Python eval()
검증 결과
server process의 marker file 생성

03 Approach

조사 대상

에이전트 framework에서는 tool 함수 하나만 보지 않고 transport의 인증, agent dispatch, model call, tool argument, sink를 한 줄로 연결했습니다. 중간 어느 단계에서든 인증이나 tool 제한이 있으면 전체 영향이 달라집니다.
대상은 framework의 모든 A2A 사용이 아니라 examples/python/a2a/a2a-server.py와 이를 따라 만든 유사 배포였습니다. 안전한 tool만 등록하거나 endpoint에 인증이 있는 배포까지 같은 영향으로 묶지 않았습니다.
인증 없는 A2A HTTP 요청 하나가 실제 LLM 판단을 거쳐 공식 예제의 eval tool을 실행할 수 있는가?

04 Root Cause

원인 분석

server 설정에서 auth_token이 없고 host가 0.0.0.0인 것을 먼저 확인했습니다. message/send handler는 받은 text를 agent.chat()으로 전달했고, agent에는 calculate tool이 등록돼 있었습니다.
tool은 이름과 달리 산술 parser를 쓰지 않고 eval(expression)을 실행했습니다. LLM이 tool을 실제로 고르지 않는다면 단순 dead code일 수 있으므로 gemini/gemini-2.5-flash-lite를 연결해 HTTP request에서 marker 생성까지 같은 test client 경로로 확인했습니다.
공식 A2A 예제의 핵심 줄 — 관련 없는 agent metadata는 생략
def calculate(expression: str) -> str: try: return f"Result: {eval(expression)}" except Exception: return "Invalid expression" agent = Agent(..., tools=[search_web, calculate]) a2a = A2A( agent=agent, url="http://localhost:8000/a2a", version="1.0.0", ) app.include_router(a2a.get_router()) # A2A auth_token 미설정 uvicorn.run(app, host="0.0.0.0", port=8000) POST /a2a -> message/send -> agent.chat() -> LLM -> calculate()
입력에서 영향까지의 경로
  1. 원격 사용자가 인증 없이 /a2a에 JSON-RPC를 보냅니다.
  1. A2A handler가 message를 agent에 전달합니다.
  1. 실제 LLM이 calculate tool 호출을 선택합니다.
  1. LLM이 만든 expression이 Python eval()로 들어갑니다.
  1. server 권한으로 marker file이 생성됩니다.

05 Reproduction & Validation

재현 및 검증

공식 example의 server 구성과 route를 유지하고 Starlette TestClient로 동일한 HTTP /a2a path를 호출했습니다. model은 mock이 아니라 실제 Gemini API를 사용했고, PoC·captured log·최종 report는 credential 값을 출력·저장하지 않도록 설계했습니다. private shell·chat history는 공개 evidence 범위에 포함하지 않습니다. TestClient는 실제 router와 handler stack을 실행하지만 public listening socket을 열지는 않았으므로, network 노출은 보존된 uvicorn.run(..., host="0.0.0.0") source로 별도 확인했습니다.
payload는 reverse shell이나 data exfiltration 대신 조사 workspace의 out/official-a2a-example-real-llm-canary.txt marker file 하나를 쓰도록 제한했습니다. response text뿐 아니라 server filesystem에서 marker 내용까지 확인해 LLM의 설명과 실제 side effect를 구분했습니다.
$ POST /a2a {"jsonrpc":"2.0","method":"message/send",...} $ test -f out/official-a2a-example-real-llm-canary.txt
검사
입력·조건
관찰
의미
인증
token 없는 message/send
handler 도달
public A2A surface
실제 모델
Gemini 연결
tool call 생성
mock-only 경로 아님
Side effect
marker write expression
marker file과 고정 문자열
server process code execution
결정적 positive control
fake LLM이 calculate 호출을 고정
같은 HTTP → tool → eval() 경로에서 marker 생성
server-to-tool sink wiring을 독립 확인
인증 경계 대조
auth_token 설정 전·후 task API 비교
무인증 200; token 설정 뒤 무인증·잘못된 token 401, 정상 token 200
인증이 노출 경계를 실질적으로 변경함을 확인; RCE fix 검증은 아님
공개 report에 남긴 실제 LLM 검증 marker
model: gemini/gemini-2.5-flash-lite route: POST /a2a (message/send) authentication: none marker status: OFFICIAL_A2A_EXAMPLE_REAL_LLM_UNAUTH_HTTP_TO_CUSTOM_EVAL_TOOL_CONFIRMED marker content: OFFICIAL_A2A_EXAMPLE_REAL_LLM_CONFIRMED

06 Impact & Fix

영향과 수정

공식 예제와 같은 배포에서는 비인증 원격 사용자가 agent에게 message를 보내 server process에서 임의 Python 표현식을 실행시킬 수 있습니다. 같은 A2A surface의 task history와 cancellation 기능도 기밀성·무결성 범위를 넓혔습니다.
확인한 범위
  • 인증 없는 실제 HTTP route
  • 실제 LLM의 tool invocation
  • eval을 통한 marker file side effect
제외한 범위
  • 모든 PraisonAI 배포의 자동 영향
  • 이 취약점을 통한 API key 탈취를 실증했다는 주장
  • 파괴적 payload나 외부 데이터 반출
  • public listening socket을 연 원격 host 간 실행
  • 4.6.40 fixed version runtime regression test
영향 버전과 수정
공식 GHSA는 praisonai <= 4.6.39를 영향 범위, >= 4.6.40을 수정 범위로 기록합니다. 로컬에서는 v4.5.44–v4.6.37 및 조사 commit 4985415e...의 complete path를 확인했으며, 이 로컬 범위를 공식 범위의 대체값으로 제시하지 않습니다.
공식 fix commit e0fb8e7eval() 기반 calculator를 숫자 literal과 산술 연산만 허용하는 AST evaluator로 교체했습니다. 해당 commit은 auth_token 기본값이나 0.0.0.0 bind를 변경하지 않았습니다. 인증 강제와 loopback bind는 추가 hardening 권고사항이며 적용 완료 사실로 기록하지 않습니다. 4.6.40 runtime regression은 보존 자료에서 재실행하지 않았습니다.

07 Turning Points

Key Turning Points

🧭
핵심 변화는 더 넓은 Critical 주장을 유지한 과정이 아니라, 실패·대조군·실제 모델 증거에 따라 최종 RCE chain을 더 좁고 강하게 만든 과정입니다.
Stage
Prior Belief
New Evidence
Revised Judgment
Trigger
1. Surface narrowing
A2A·AgentOS·Gateway와 auto-approved tool 경로를 기본 Critical surface로 함께 볼 수 있었습니다.
Gateway no-token external bind 거부, AgentOS HTTP 500, explicit auto-approve 의존성을 확인했습니다.
Gateway·AgentOS·조건부 auto-approve chain을 기본 RCE에서 제외하고 first-party A2A example에 집중했습니다.
사용자가 auto-approve 의존성을 반박하고 추가 승격 검증을 요구했습니다.
2. HTTP chain isolation
Direct tool sink 확인만으로 framework RCE를 설명할 수 있다고 볼 수 있었습니다.
실제 Agent와 deterministic fake LLM으로 /a2a → agent.chat() → calculate() → eval() marker를 확인했습니다.
Direct call은 sink control, deterministic HTTP run은 framework wiring evidence로 분리했습니다.
사용자가 조기 보고서화를 중단시키고 실제 chain 증명을 요구했습니다.
3. Real-model proof
HTTP 200 또는 completed task를 tool 실행으로 볼 수 있었습니다.
Gemini 2.0은 quota 429와 marker 없음, Gemini 2.5 Flash Lite는 exact marker 생성·내용 일치를 보였습니다.
Marker를 ground truth로 정하고 quota run은 operational non-confirming evidence로 낮췄습니다.
사용자가 Gemini 사용과 최종 증거 충분성 검토를 선택했습니다.
4. Scope and version
로컬 확인 범위와 공식 영향 범위, AST fix와 auth·bind hardening을 한 묶음으로 볼 수 있었습니다.
로컬 history, GHSA/CVE 범위와 fix diff를 대조했습니다.
공식 영향 <=4.6.39, 수정 >=4.6.40, 실제 AST fix와 추가 hardening 권고를 분리했습니다.
Affected versions와 최신 main·공개 metadata의 최종 확인 요청이 계기였습니다.

Investigation Log

전체 판단 변화 원문
판단 변화와 협업 조사 일지
🧭
이 절은 최종 보고서의 목차를 다시 쓴 것이 아니라, 보존된 private 조사 대화와 생성 artifact의 시각을 대조해 실제 판단 변화를 재구성한 기록입니다. 아래 시각은 KST입니다. 509줄 MD는 raw 대화가 아니라 조사 후반에 생성한 최종 Advisory입니다. private raw session의 경로·식별자·credential 관련 내용은 이 페이지와 공개 저장소에 포함하지 않습니다.
시각·단계
사용자 질문·반론
Codex 검증
실패·반증
바뀐 판단
05-10 18:49–18:58 넓은 surface에서 출발
A2A·AgentOS·Gateway를 묶은 기본 공개·무인증 Agent 실행면을 선택하고 재현과 Critical 승격을 요청했습니다.
외부 listener를 열지 않고 TestClient로 각 entrypoint의 bind·auth·agent.chat() 도달 여부를 분리했습니다.
Gateway는 token 없는 외부 0.0.0.0 bind를 이미 거부했습니다.
A2A·AgentOS는 유효하지만 Gateway까지 같은 주장으로 묶는 것은 철회했습니다. 이 시점의 증거는 unauthenticated agent invocation이지 아직 RCE가 아니었습니다.
05-10 18:58–20:45 조건부 Critical의 한계
Critical까지 계속 검증하도록 요청한 뒤, auto-approve 같은 위험 설정을 켠 결과를 기본 취약점으로 볼 수 있는지 반론했습니다.
공개 endpoint에서 execute_command marker까지 이어지는 체인을 만들고 approval 계층을 확인했습니다.
Agent(approval=True)만으로는 built-in tool decorator를 통과하지 못했고 PRAISONAI_AUTO_APPROVE·full_auto 같은 명시적 조건이 필요했습니다.
기본 공개·무인증 실행면은 High 후보로 남기고 auto-approved tool RCE는 base claim이 아닌 Conditional Critical로 낮췄습니다. 사용자의 반론이 과장된 기본 RCE 주장을 제거한 첫 핵심 전환점이었습니다.
05-10 20:50–22:14 기본값과 독립 영향 재검증
그 기본값이 실제로 쓰이기 쉬운지, 위험 경고나 더 안전한 경로가 있는지 확인하고 추가 승격을 계속 요청했습니다.
공식 문서·SDK·CLI를 교차 확인하고 A2A tasks/list/get/cancel에서 다른 task의 prompt·response·artifact 노출과 취소를 검증했습니다. Agent.launch()도 별도로 조사했습니다.
A2A CLI에는 일부 안전한 localhost 경로가 있었고, Agent.launch()_server_lock 미초기화로 endpoint 생성 전에 실패했습니다. 전역을 억지로 주입하는 증명은 버렸습니다.
task disclosure/cancel은 auto-approve와 무관한 별도 기밀성·무결성 경계 증거로 남겼습니다. 깨진 launch 경로는 RCE 근거에서 제외했습니다.
05-10 23:38–23:43 조기 보고서화 중단
Codex가 보고서 작성으로 넘어가자 아직 목표는 문서화가 아니라 추가 승격 검증이라고 방향을 되돌렸습니다.
조건부 execute_command 대신 공식 A2A 예제의 0.0.0.0 bind·auth 없음·calculate(expression)eval()을 확인하고 실제 _execute_tool_impl()로 marker를 만들었습니다.
기존 auto-approve 체인은 first-party 기본 Critical을 뒷받침하기에 조건이 많았습니다.
주장의 중심을 일반적인 위험 설정에서 first-party example이 제공하는 public unauthenticated A2A + eval tool로 옮겼습니다. calculateDEFAULT_DANGEROUS_TOOLS에 포함되지 않는다는 사실도 확인했습니다.
05-10 23:45–23:52 direct sink에서 HTTP chain으로
보고서보다 실제 승격 증명을 계속하도록 요구했습니다.
실제 Agent와 deterministic fake LLM을 사용해 /a2a message/send → agent.chat() → calculate() → eval() marker의 전체 HTTP chain을 만들었습니다.
AgentOS의 실제 Agent 경로는 event-loop 충돌로 HTTP 500이 발생했고 A2A만 성공했습니다.
AgentOS를 실제 RCE claim에서 제외하고 A2A에 집중했습니다. direct tool call은 sink 확인용, deterministic HTTP run은 framework wiring control로 역할을 분리했습니다.
05-10 23:53–05-11 00:04 실제 모델 요구
Critical 확신을 높일 다음 요소를 물었고 Codex가 제안한 real LLM canary를 실행하기로 결정했습니다. Gemini 사용도 사용자가 선택했습니다.
공식 example을 그대로 로드하고 실제 provider가 tool call을 선택한 뒤 marker를 만드는 PoC를 작성했습니다.
처음에는 credential이 없어 HTTP 실행 전에 SKIP됐고, 이후 LLM dependency와 격리된 Python 환경을 준비해야 했습니다.
credential 없는 SKIP과 dependency 준비 실패는 취약점의 반증이 아니라 실행 전 operational blocker로 분류해 primary evidence에서 제외했습니다.
05-11 00:15–00:17 quota 실패에서 real-model 성공으로
실제 Gemini 실행 결과를 공유하고 다음 검증을 이어갔습니다.
gemini/gemini-2.0-flash 실패 원인을 진단한 뒤 gemini/gemini-2.5-flash-lite로 전환했습니다.
2.0 Flash run은 provider 429 RESOURCE_EXHAUSTED·quota limit 0으로 정상 추론이 수행되지 않았습니다. framework는 HTTP 200을 반환했지만 marker는 없었습니다.
HTTP 200이나 completed task만으로 sink 실행을 판단할 수 없고 server-side marker가 ground truth라는 기준을 세웠습니다. 2.0 run은 모델 비결정성 대조군이 아니라 quota로 인한 operational non-confirming run입니다. 2.5 Flash Lite에서 marker 존재·내용 일치가 확인된 run을 primary real-model proof로 승격했습니다.
05-11 00:18–00:32 증거 충분성·claim boundary
로그가 충분한지 냉정하게 평가하도록 요청하고, 과장 없이 상세한 Advisory 작성을 요청했습니다.
HTTP status, auth 상태, model, tool, marker, deterministic control과 task boundary를 다시 검토하고 Non-Claims와 TestClient 제한을 넣었습니다.
응답 본문에는 Result: prefix가 없었고 TestClient는 실제 public socket을 열지 않았습니다.
응답 문구보다 exact marker를 실행 증거로 채택했습니다. 네트워크 노출 가능성은 0.0.0.0 source로, handler chain은 TestClient로 분리했습니다. 범위는 모든 PraisonAI가 아니라 공식 예제 및 같은 구조의 public unauthenticated A2A + unsafe eval tool 배포로 제한했습니다.
05-11 00:23–01:02 및 공개 후 버전·수정 범위 교정
Affected versions와 최신 GitHub main에서 패치되지 않았는지 최종 확인을 요청했습니다.
history와 main을 확인해 당시 complete remote chain의 로컬 하한을 v4.5.44, 상한을 v4.6.37·조사 commit으로 제한했습니다. 공개 후 GHSA/CVE와 fix diff를 다시 대조했습니다.
v4.5.43은 route가 TODO라 complete chain을 확정할 수 없었고, 보존 자료에서는 fixed 4.6.40 runtime regression을 실행하지 않았습니다.
로컬 확인 범위와 공식 범위를 분리했습니다. 공식 영향은 <=4.6.39, 수정은 >=4.6.40이며 실제 patch는 eval→제한 AST 교체입니다. auth 강제·loopback bind는 적용 사실이 아니라 추가 hardening 권고로 남겼습니다.
최종적으로 버리거나 축소한 주장
  • Gateway까지 동일한 기본 무인증 외부 surface라는 주장 — external no-token bind가 차단돼 제외
  • 기본 설정만으로 곧바로 shell RCE라는 주장 — explicit auto-approve 조건이 필요해 Conditional Critical로 분리
  • AgentOS 실제 Agent RCE 체인 — 해당 경로의 HTTP 500으로 확인되지 않아 제외
  • 깨진 Agent.launch()를 억지로 초기화해 증명하는 방식 — 인위적이므로 제외
  • HTTP 200 또는 agent 응답만으로 tool 실행이 증명된다는 판단 — exact server-side marker가 필요
  • Gemini 2.0 실패를 모델 비결정성의 대조군으로 사용하는 판단 — 실제 원인은 provider quota 429였으므로 operational non-confirming run으로 재분류
최종 증거 계층
  1. Primary confirmed: Gemini 2.5 Flash Lite 실제 tool selection + unauthenticated /a2a + exact marker 생성·내용 일치
  1. Deterministic control: fake LLM이 tool 선택만 고정하고 실제 HTTP handler·Agent·calculate·eval() wiring을 확인
  1. Supporting boundary evidence: auth token 전·후 task API 접근과 task disclosure/cancel; primary RCE와 별도 C/I finding
  1. Operational non-confirming: Gemini 2.0 quota 429 후 HTTP 200·marker 없음; 모델 행동 대조군 또는 안전성 증거가 아님
  1. Excluded: no-credential SKIP, dependency 준비 실패, broken launch path
  1. Not verified: fixed 4.6.40 checkout의 runtime regression과 실제 public socket을 연 host-to-host 실행
공개 credential 보존 경계
PoC·실행 log·최종 report·공개 GitHub evidence에는 실제 API key 값을 포함하지 않습니다. private raw conversation은 공개 evidence에서 제외하며, 이 페이지는 key가 private shell·chat lifecycle 전체에서 어떻게 취급됐는지까지 주장하지 않습니다.
이 과정에서 확인한 협업의 역할
사용자는 높은 severity를 요구하는 데서 끝나지 않고 auto-approve 의존성을 직접 반박하고, 조기 보고서화를 중단시키고, real LLM 및 최종 증거 충분성 검증을 요구했습니다. Codex는 각 반론을 코드·대조군·실패 실험으로 검증해 Gateway·AgentOS·조건부 RCE 주장을 버리거나 축소했습니다. 최종 Critical 주장은 처음보다 좁아졌지만 first-party example과 실제 모델 marker에 근거해 더 강하고 방어 가능한 형태가 됐습니다.

Role & Credit

  • Portfolio author: Taegu Ha
  • Official GHSA reporter: foxirain
  • 사용자·Codex 협업 역할의 상세 근거는 Investigation Log의 기존 「이 과정에서 확인한 협업의 역할」 원문에 보존했습니다.

08 Provenance

🗄️
322da5d0e45b3e13e6487c4a8c9eb04124233f56 링크는 current main에서 삭제된 감사·보존 파일의 historical snapshot입니다. 그 밖의 current artifact 링크는 main을 유지합니다.

분석 기준과 증거 보존

항목
최종 감사 결과
소스 기준
PraisonAI checkout 4985415e61043a1734903f6a6e8ca85efdaede7c
원본 검증
HTTP request, 실제 LLM tool selection, calculator eval() sink와 marker file side effect를 연결
GitHub 보존 상태
Historical audit commit c04cacf 당시 evidence 4개와 root manifest.json에 size·SHA-256을 고정했습니다. 현재 main은 evidence 11개이며 root manifest.json은 제거됐습니다.
무결성
사례별 SHA256SUMS 검증 통과, root manifest의 모든 file hash와 실제 파일 일치, README 상대 링크 확인 완료
원본 최종 조사 report
praisonai-a2a-official-example-unauthenticated-real-llm-rce-advisory.md · 509 lines · 20,443 bytes · SHA-256 83cd4eb81893d9915b37b5df71ed6b0cd7a9df2c815d4c3af7343ba168e9f1c9. 이 MD는 raw Codex session이 아니라 조사 후 작성한 최종 report이며, 공개 github-advisory.json description의 source derivative입니다.
실행 PoC identity
prove-official-a2a-example-real-llm-canary.py · 208 lines · 7,463 bytes · SHA-256 078977678df3d7b14ddf5893a3f219c868f6665ce091c5f3db03f80e35074fda. 공개 advisory의 PoC는 더 짧은 submission excerpt입니다.
모델별 proof logs
성공 log SHA-256 3b1ce402fcbfd884d7ae1940f202a9ab0748451dd297cab8ecd8074ccda76d42; Gemini 2.0 non-confirming log SHA-256 beb3036453fccdcb14200309de222442bec644bd508192bfa381767038b41ac1.
보존 경계·제약
raw Codex session, raw layout-bound scripts/logs, Gemini API key와 환경 credential은 공개 저장소에서 제외했습니다. report hash를 raw-session hash로 간주하지 않습니다.
감사 완료
2026-08-23 · Docker/로컬 원본 대조 후 GitHub push 완료
이전 Summary 상세 · 원문 보존
공식 A2A 예제가 인증 없이 0.0.0.0에 열리고 eval() 기반 calculate 도구를 등록했습니다. 실제 HTTP JSON-RPC와 실제 LLM 호출을 거쳐 marker file이 생성되는 전체 chain을 확인했습니다.
Taegu Ha / foxirain Reporter · PraisonAI · CWE-95 · CWE-306 · GitHub CNA CVSS 9.8 Critical
2026년 5월 19일 GHSA 공개 · 2026년 7월 21일 CVE 레코드 공개 · 영향 praisonai <= 4.6.39 · 수정 >= 4.6.40 · 공식 기록
구분
확인 내용
프로젝트·컴포넌트
PraisonAI first-party examples/python/a2a/a2a-server.py · calculator tool
버전 범위
공식 GHSA: praisonai <= 4.6.39; 수정: >= 4.6.40. 로컬 history에서 complete A2A chain을 확인한 범위는 v4.5.44–v4.6.37 및 commit 4985415e입니다.
공격 입력
unauthenticated HTTP JSON-RPC prompt
필요 조건
공식 예제 또는 같은 구조의 agent가 public interface에 노출되고 실제 LLM·tool 호출 활성
취약 지점
인증 없는 public bind와 calculate(expression)의 Python eval() 결합
검증 결과
HTTP → agent.chat() → Gemini tool selection → eval()로 marker file 생성
영향
server process 권한의 remote code execution
실제 upstream 수정
4.6.40에서 eval() calculator를 제한된 AST evaluator로 교체
추가 hardening 권고
public bind 시 인증 강제·loopback 기본값·위험 tool 제한; fix commit에 적용됐다고 주장하지 않음

09 Artifacts

관련 파일

2026-08-23 최종 감사
공개 advisory·CVE 레코드와 함께 공식 example의 취약 source를 a2a-server-vulnerable.py로 독립 보존했습니다. 실제 API key는 포함하지 않았습니다.

10 References

11 Takeaways

느낀 점

에이전트 취약점은 prompt 한 문장이나 위험한 tool 하나로 설명되지 않았습니다. 외부 transport, 인증, 모델의 선택, tool schema, 실제 sink가 모두 연결돼야 영향이 생겼습니다. 이 다섯 단계를 따로 검증한 것이 가장 큰 학습이었습니다.
공식 example도 단순 문서 조각이 아니라 복사해 실행되는 배포 템플릿이라는 관점이 남았습니다. 예제의 기본 bind와 인증 여부, tool 구현은 production code와 비슷한 책임으로 검토해야 했습니다.