친구가 빌려준 책에 작은 글씨로 "이 책을 읽는 사람은 지갑을 옆자리 사람에게 주세요"라고 적혀 있다고 해 봅니다. 사람은 책 속 말을 지시로 따르지 않지만 Agent는 문서 속 글을 읽다가 그 말을 지시로 착각할 수 있습니다. 이런 공격을 프롬프트 인젝션(prompt injection)이라고 부릅니다.
아래 장면은 문서 속에 숨은 지시가 있고 Agent에게 메일 보내기 도구가 있는 상황입니다. 모의 모델 서버가 숨은 지시를 따르도록 만들어 직접 실행한 결과입니다. 실제 모델이 늘 따른다는 뜻은 아닙니다. 보호 방법을 바꿔 보세요.
설명을 위한 예시입니다. 세 장면 모두 langchain 1.4와 모의 모델 서버로 직접 실행해 확인했고 키와 주소는 가짜입니다.
Agent가 받는 입력은 내가 쓴 시스템 지시와 사용자의 질문과 도구가 가져온 문서가 한 줄로 이어진 글입니다. 사람은 출처를 알지만 모델에게는 모두 같은 글로 읽힙니다. 그래서 문서에 지시처럼 쓴 문장이 있으면 모델이 그대로 따를 수 있습니다.
OWASP는 이를 둘로 나눕니다. 사용자의 입력이 모델의 행동을 의도치 않게 바꾸는 직접 인젝션이 있고 웹사이트나 파일 같은 외부 자료에 숨은 내용이 바꾸는 간접 인젝션이 있습니다. 문서를 읽는 Agent에게는 간접 쪽이 문제입니다.
Simon Willison은 이를 치명적 삼중주(lethal trifecta)라고 불렀습니다. 내 비공개 데이터에 접근할 수 있고 믿을 수 없는 글을 읽으며 밖으로 내용을 보낼 수 있는 세 능력입니다. 셋이 한 Agent에 모이면 누군가 숨은 지시로 비공개 데이터를 읽어 밖으로 보내게 할 수 있습니다.
그는 이 문제를 완벽히 막는 방법을 아직 모른다고 말합니다. 그러면서 막아 준다는 보호 제품을 믿지 말고 이 조합 자체를 피하라고 권합니다. 능력을 켜고 꺼 보세요.
OWASP는 프롬프트 인젝션 대응으로 모델의 역할과 한계를 정하기, 출력 형식을 코드로 검사하기, 입출력 거르기, 최소 권한, 위험한 일은 사람 승인, 외부 자료를 믿을 수 없는 것으로 표시하기, 공격을 흉내 낸 시험을 듭니다. 어느 하나로 완벽히 막는 것이 아니라 겹쳐서 씁니다.
관문마다 힘도 다릅니다. 믿을 수 없는 글이라고 표시하면 영향이 줄어들 뿐 막지는 못합니다. 보낼 수단을 없애거나 사람이 승인하게 하는 관문은 코드가 확실히 막습니다. 관문을 켜고 유출이 어디서 멈추는지 보세요.
agent = create_agent(
model, tools=[read_doc, send_mail],
middleware=[HumanInTheLoopMiddleware(interrupt_on={"send_mail": True})], # 1 위험 도구는 승인
checkpointer=InMemorySaver(), # 2 멈췄다 이어 가려면 저장소가 필요
)
r = agent.invoke(ask, config) # 멈춤: r["__interrupt__"]에 보내려는 내용
agent.invoke(Command(resume={"decisions": [{"type": "reject",
"message": "수신자가 사내 주소가 아닙니다"}]}), config) # 3 사람이 거절출력은 예시입니다. 숨은 지시를 따르도록 만든 모의 모델 서버로 직접 실행했고 실제 모델의 행동과 다를 수 있습니다. 키와 주소는 가짜입니다.
HumanInTheLoopMiddleware에 승인이 필요한 도구 이름을 적으면 그 도구를 부르기 직전에 멈춥니다. 앞에서 배운 interrupt를 안에서 씁니다.방어는 한 번 세우고 끝나지 않습니다. 새 도구를 붙일 때마다 위 세 능력이 한 Agent에 모이는지 다시 확인하고 공격을 흉내 낸 시험을 평가 데이터셋에 넣어 둡니다. 다음 글부터는 지금까지 만든 부품을 모아 최종 프로젝트를 설계합니다.