AI Agent 보안 가이드: 악성 JSON, 프롬프트 인젝션, Tool Calling, 구조화 데이터 공격 위험

2026년 9월 14일 기준: Agent가 도구를 호출하고 외부 JSON을 읽으면 공격면은 ‘틀린 한 문장’에서 ‘실제 호출’로 옮겨 간다. 악성 JSON, 간접 인젝션, 과도한 권한의 도구, 느슨한 Schema에 대한 방어 지도.

결론부터: 2026년 AI Agent 보안은 「모델이 틀린 말을 할까」가 아니라 「신뢰할 수 없는 구조화 데이터가 진짜 도구 호출이 될까」다. 주입된 챗봇의 최악은 오해를 부르는 답이다. 주입된 Agent의 최악은 메일 발송, 파일 수정, DB 조회, 결제 실행이다. JSON은 계약이자 페이로드다. Prompt Injection은 채팅창에만 있지 않다 — 티켓 필드, 웹 페이지, 메일 본문, API 응답에 있고, 거의 모두 JSON으로 들어온다.

2026년 9월 14일 기준. 본 사이트는 이미 Tool Calling이 JSON Schema에 의존하는 이유, Agent JSON 데이터 흐름, 계약으로서의 JSON Schema, MCP / Skills / Tools / Subagents를 다뤘다. 이 글은 악성 JSON, 간접 주입, 과권한 Tool Calling, 느슨한 Schema가 각각 무엇을 위험에 빠뜨리는지, Host가 무엇을 강제해야 하는지만 답한다. 방어 가이드다. 재현 가능한 공격 절차나 바로 쓸 수 있는 주입 문구는 제공하지 않는다.

한 표: 챗봇 vs Agent 공격면

사용자가 입력창에 치는 것은 Agent가 삼키는 입력의 일부일 뿐이다. 2026 기본 스택에서 모델은 도구 결과, MCP Resources, 페이지 발췌, 티켓 필드, 메일 본문도 읽는다 — 거의 항상 JSON이거나 JSON에 감싼 문자열이다. 업계 목록은 이를 Prompt Injection, Insecure Output Handling, Excessive Agency로 분류한다. 분류부터 외울 필요는 없다. 누가 말하고 누가 실행하는가를 먼저 가리면 된다.

차원챗봇도구를 호출하는 Agent
실패 형태틀리거나 유도된 답실제 부작용: 쓰기, 요청, 데이터 변경
신뢰할 수 없는 입력현재 사용자 메시지사용자 텍스트 + 외부 JSON + 도구 결과 + 페이지/메일 필드
실행자모델은 텍스트만 낸다Host / MCP Server가 arguments를 실행한다
계약프롬프트(소프트)JSON Schema + Host 검증(하드)
최소 수정프롬프트 조정, 거절 정책더 조인 Schema, 도구 화이트리스트, 독립 인가

한 문장으로: 모델은 속일 수 있다. Host는 따라서 실행하면 안 된다. 보안 경계는 tool_calls를 생성하는 것과 실제로 돌리는 것 사이에 있다. 검증·인가·감사는 그 자리에 둔다 — 「모델 출력을 신뢰할 수 있는 RPC로 취급」하면 안 된다.

악성 JSON: 여분 필드, 타입 혼동, 중첩 페이로드

악성 JSON은 문법적으로 멀쩡하면서도 위험한 경우가 많다. 엄격한 파서는 후행 쉼표나 주석을 거절한다. 느슨한 파서, 문자열 이어붙이기, 「일단 텍스트로 보고 JSON.parse」는 실패만 늦출 뿐이다. Agent 시스템에서 피해는 Host가 이미 그 값을 객체로 다루는 순간 시작된다.

먼저 막을 세 가지 형태(패턴만 — how-to가 아니다):

패턴어떻게 보이는가Host가 검증하지 않으면방어
여분 필드선언되지 않은 키가 붙은 업무 객체설정에 merge되거나 다음 도구로 그대로 전달additionalProperties: false; 모르는 키는 버린다
타입 혼동number/array여야 할 값이 string 또는 object로 온다인가 분기가 빗나가거나, 덩어리 전체가 인수가 된다type, enum, format을 고정
중첩 페이로드문자열 필드에 JSON이나 긴 브리프가 들어 있다안쪽 텍스트가 프롬프트에 들어가 지시로 읽힌다길이 제한; 내부 JSON을 다시 검증; 원문을 system 발화로 취급하지 말 것

거의 아무것도 묶지 않아 「동작하는」 도구 파라미터 계약, 그리고 조인 뒤의 대조. 방어 샘플이지 공격 재료가 아니다:

{
  "type": "object",
  "properties": {
    "payload": { "type": "object" }
  }
}

위 Schema는 계약이 거의 없다: 아무 키, 아무 중첩이나 통과한다. 운영에서는 필드를 화이트리스트하고 여분을 금지한다:

{
  "type": "object",
  "properties": {
    "order_id": { "type": "string", "pattern": "^A-[0-9]{4,8}$" },
    "amount": { "type": "number", "minimum": 0, "maximum": 100000 },
    "note": { "type": "string", "maxLength": 200 }
  },
  "required": ["order_id", "amount"],
  "additionalProperties": false
}

샘플을 검토할 때는 본 사이트의 JSON 검증기로 그 Schema를 통과하는지 보고, JSON Diff로 「모델이 낸 arguments」와 「당신이 허용하는 최소 객체」를 비교하라. 여분 키와 뒤집힌 타입이 첫 경보다.

또 하나 놓치기 쉬운 점: 신뢰할 수 없는 객체를 내부 설정이나 프로토타입 체인에 닿는 구조에 그대로 merge하지 마라. JavaScript Host에서 설정 객체의 모르는 키는 「필드 하나 더」보다 클 수 있다. 처방은 같다: Schema로 화이트리스트 객체를 투영한 뒤 그것만 아래로 넘긴다.

Prompt Injection: 구조화 데이터에 숨은 지시

직접 주입은 사용자가 채팅창에 말해 시스템 프롬프트를 덮으려는 것이다. 간접 주입이 2026 Agent의 실제 모습에 가깝다: 지시는 모델이 나중에 읽을 데이터에 숨는다 — 티켓 제목, 메일 본문, 페이지 문단, PDF 발췌, 다른 도구가 돌려준 JSON 문자열. 모델은 「개발자가 쓴 규칙」과 「데이터 안의 문장」을 본래 가르지 않는다.

구조화 데이터는 이를 더 조용하게 만든다. 고객 메모는 API에서 customer_note 문자열일 뿐이다. 컨텍스트에 들어가면 시스템 프롬프트와 같은 토큰 스트림을 나눈다. Host가 도구 결과를 다음 턴에 그대로 붙이면, 외부 사이트나 발신자가 Agent에게 프롬프트를 쓰는 셈이다.

방어는 「악의적 지시는 무시하라」를 한 문장 더 쓰는 것이 아니다. 프롬프트는 위험을 낮출 뿐 경계가 아니다. 더 단단한 통제:

  • 출처를 표시하라 — 외부 텍스트는 별도 역할이나 래퍼로 넣는다(예: tool / untrusted). system에 접어 넣지 않는다.
  • 보이는 면을 줄여라 — 붙여 넣지 말고 요약하고, JSON 문서 전체가 아니라 필요한 필드만 보낸다.
  • 고위험 동작에 문을 달아라 — 이체, 삭제, 외부 발송은 모델과 무관한 정책 엔진이나 사람이 필요하다.
  • 도구 결과는 데이터이지 지시가 아니다 — 반환값을 Schema로 검증한 뒤에만 프롬프트에 다시 넣는다.

Apple Intelligence가 개인 데이터를 보호하는 방식과 같은 직감이다: 새거나 남용되는 것은 대개 당신이 JSON에 넣은 필드다. 모델 안에서는 필드를 말로 읽을 수 있고, 도구 arguments 안에서는 동작으로 읽을 수 있다.

Tool Calling: 유효한 호출처럼 보이는 권한 남용

위험은 「모델이 JSON을 낼 수 있는가」가 아니다. Host가 모델을 이미 인가된 호출자로 보느냐다. 전형적인 confused deputy다: 모델은 제안만 한다. 권한은 사용자 세션, 테넌트, 서비스 계정에 속한다. 「export_orders를 호출하라」는 「이 사용자가 테이블을 내보내도 된다」와 같지 않다.

2026년의 과권한은 대개 이런 모양이다:

  • 거의 임의 문자열을 받는 단 하나의 run_command / http_request;
  • 최종 사용자보다 넓은 서비스 계정으로 도는 도구;
  • Host가 JSON 문법만 보고 「이 사용자가 이 자원에 손대도 되는가」는 보지 않음;
  • 사용자나 페이지의 JSON을 Schema를 건너뛰고 도구 arguments로 그대로 전달.

너무 열린 선언과, 실제로 검토할 수 있는 선언:

{
  "name": "fetch_url",
  "description": "Fetch any URL and return text.",
  "parameters": {
    "type": "object",
    "properties": {
      "url": { "type": "string" }
    },
    "required": ["url"]
  }
}
{
  "name": "fetch_public_doc",
  "description": "Fetch an allowlisted documentation URL.",
  "parameters": {
    "type": "object",
    "properties": {
      "doc_id": {
        "type": "string",
        "pattern": "^doc-[a-z0-9-]{1,32}$"
      }
    },
    "required": ["doc_id"],
    "additionalProperties": false
  }
}

두 번째가 「더 똑똑한」 것은 아니다. 열린 능력을 감사 가능한 식별자로 줄일 뿐이다. Host는 doc_id를 자기 화이트리스트에서 풀고, origin을 채우고, 타임아웃과 크기 상한을 건다. 모델은 임의 URL을 보지 않으니, 신뢰할 수 없는 출처로 가는 길이 하나 줄어든다.

실행 전에 세 가지를 물어라: 이 도구가 현재 세션 화이트리스트에 있는가? arguments가 모델과 무관한 Schema·인가 검사를 통과했는가? 실패하면 거절하는가, 아니면 오류 상세를 새로운 주입 가능 텍스트로 되먹이는가? 파라미터 오류와 검증이 파이프라인을 다룬다. 보안에서는 하나를 더한다: 실패하면 닫고, 모델이 새 arguments로 무한 재시도하게 두지 마라.

너무 느슨한 Schema는 취약점이다

Structured Output과 Tool Calling은 둘 다 JSON Schema로 불법 토큰을 막는다 — 2026 기본 계약이다. Structured Output이란 무엇인가를 보라. Schema가 보장하는 것은 모양이지 안전한 의미가 아니다.

느슨한 계약은 대개 이런 모습이다:

  • type: object인데 properties가 없거나 additionalProperties가 true;
  • enum이어야 할 동작 이름을 아무 string으로 씀;
  • 식별자 필드에 한 페이지짜리 브리프가 들어감;
  • 벤더 strict 모드가 부분집합만 막는데, Host는 「모델 쪽이 이미 다 막았다」고 믿음.

문을 두 개 쌓아라: 모델 쪽 Schema는 허튼소리를 줄이고, Host는 같은(또는 더 엄한) Schema로 다시 검증한 뒤 내부 타입으로 투영한다. 벤더 Structured Outputs는 당신의 인가를 대체하지 않는다. 벤더 간 필드 차이 자체가 위험이다 — Structured Output 비교를 보라. 계약을 느슨하게 하는 마이그레이션은 공격면을 넓힌다.

Schema를 보안 통제로 쓸 때는 enum, const, pattern, maxLength, minimum / maximum, required, additionalProperties: false를 우선하라. 자유 텍스트가 필요하면 분리하고 상한을 두고, 자유 텍스트를 도구 이름이나 URL에 매핑하지 마라.

MCP와 외부 도구: 신뢰 경계는 어디인가

MCP는 프로세스 간 발견과 호출을 푼다. 「이 Server가 선의인가」는 풀지 않는다. MCP란 무엇인가가 이미 JSON-RPC 선을 그었다: 한쪽에 모델 API, 다른 쪽에 Host ↔ Server. 보안에는 두 번째 선이 필요하다: 서드파티 MCP Server의 도구 설명, Resources 텍스트, 반환 JSON은 모두 신뢰할 수 없는 입력이다.

위험은 프로토콜 날짜가 아니다. 신뢰를 잘못된 계층에 두는 것이다:

  • tools/list의 description / inputSchema를 시스템 프롬프트에 그대로 붙임;
  • tools/call의 result를 검증 없이 다음 턴에 넣음;
  • 한 Host에 Server가 너무 많아 이름·능력이 겹치는데도 그대로 실행;
  • 「사용자가 이 Server를 허용했다」를 「반환하는 문장마다 지시」로 읽음.

Skills에도 같은 부류의 문제가 있다: Skill은 실행 설명서이지 인가 계층이 아니다. 신뢰할 수 없는 저장소의 SKILL.md를 로드하면 모델이 따를 워크플로가 하나 더 생긴다. 네 계층이 각각 무엇을 맡는지를 보라 — Skill로 Schema를 대체하지 말고, Subagent로 권한 격리를 대체하지 마라. Subagent는 도구 집합을 좁힐 수 있다. 어떤 비밀을 받을지는 부모가 정한다.

방어 체크리스트: 검증, 화이트리스트, 최소 권한

실행 경로를 바깥에서 안으로 조여라. 프롬프트부터 손대지 마라:

  1. 계약을 먼저 써라. 도구마다 JSON Schema 하나: required를 채우고, additionalProperties: false, 식별자는 pattern / enum.
  2. Host에서 다시 검증하라. 「모델이 이미 Schema를 따랐다」고 믿지 마라. ajv 또는 동등 라이브러리; 실패하면 거절.
  3. 투영하고, 통과시키지 마라. 화이트리스트 필드만 내부 DTO로 복사한 뒤 업무 API를 호출한다.
  4. 도구를 최소화하라. fetch_url보다 fetch_public_doc; 쓰기보다 읽기 전용.
  5. 모델이 아니라 사용자로 인가하라. 도구 구현 안에서 세션, 테넌트, 자원 ACL을 검사한다.
  6. 바깥 부작용에 문을 달아라. 발송, 이체, 삭제, 프로덕션 배포: 정책 엔진 또는 사람 확인.
  7. 외부 텍스트의 등급을 내려라. 도구 결과, 페이지, 메일은 system이 아니다. 필요하면 읽기 전용 Subagent가 부모에 요약만 돌려준다.
  8. arguments를 감사하라. 도구 이름, 검증된 파라미터, 누가 인가했는지, 거절됐는지를 남긴다.

프롬프트는 여전히 도움이 된다: 데이터는 지시가 아니라고 말하고, 금지 동작을 나열하라. 보조 계층이다. Schema나 ACL이 빠졌다면 프롬프트 열 문장으로 메울 수 없다.

로컬 JSON 도구로 계약을 검토하라

배포 전에 세 파일을 같이 보라: 도구 parameters / MCP inputSchema, 「정상」 arguments 객체 하나, 일부러 못생기게 만든 샘플 하나(여분 키, 잘못된 타입, 과한 길이의 문자열). 실제 비밀과 실제 사용자 데이터는 쓰지 마라.

  • JSON 검증기 — 샘플이 적법한 JSON인가, Schema를 만족하는가?
  • JSON Diff — 모델이 최소 객체 너머에 무엇을 더했는가?
  • 트리 뷰어 — 중첩이 필요 이상으로 깊은가, 문자열 필드가 다른 구조를 감추고 있는가?

데이터는 브라우저를 떠나지 않는다. 프로덕션에 올리기 직전 계약을 검토하거나, 실패한 호출의 arguments를 대조하기에 맞다. 필드 이름과 required가 안정되면 그때 Host나 MCP Server에 연결하라.

FAQ

JSON Schema가 Prompt Injection을 막을 수 있나?

혼자서는 못 막는다. Schema는 도구 arguments의 모양과 값 범위를 제한해, 임의 문자열이 임의 동작이 될 확률을 낮춘다. 간접 주입은 텍스트가 컨텍스트에 들어갈 때 일어난다. 출처 분리, 등급 하향, 고위험 문이 여전히 필요하다.

모델이 이미 Structured Output / strict mode인데 Host도 검증해야 하나?

해야 한다. 벤더 제약은 생성 단계에 걸리고, 벤더마다 Schema 부분집합이 다르다. 보안 결정은 실행 전에, 당신의 검증기와 인가로 내려야 한다.

사용자 JSON을 도구에 그대로 넘기면 무엇이 문제인가?

계약을 건너뛰는 것이다. 여분 필드, 타입 혼동, 중첩 텍스트가 부작용 있는 코드에 그대로 도착한다. 검증한 뒤 투영하고, 화이트리스트 필드만 넘겨라.

MCP Server 출력을 시스템 프롬프트로 써도 되나?

안 된다. tools/list 설명, Resources 텍스트, tools/call 결과는 신뢰할 수 없는 데이터다. 검증한 뒤 더 낮은 권한으로 대화에 다시 넣어라.

쓸 수 있는 최소 Agent 보안 베이스라인은 무엇인가?

엄격한 JSON Schema, Host 재검증, 도구 화이트리스트, 사용자 단위 인가, 바깥 부작용 문. 이 다섯이 없으면 Agent를 프로덕션 데이터에 연결하지 마라.

도구 관련 JSON이 위험한지 로컬에서 어떻게 보나?

Schema와 arguments 샘플을 JSON 도구함에 넣고 검증과 Diff를 돌려라. required, additionalProperties, 길이/열거 제한을 확인한 뒤에 Host나 MCP Server에 연결하라.

요약

Agent의 공격면은 구조화 데이터와 도구 실행 사이에 있다: 악성 JSON이 계약을 빠져나가고, Prompt Injection이 의도를 꺾고, Tool Calling이 의도를 부작용으로 바꾸며, 느슨한 Schema가 셋 모두의 문을 연다. 2026 기본 스택(Tools, MCP, Skills, Subagents)은 Agent를 더 쓸모 있게 만들고, 「답 한 줄을 속이는 것」보다 「호출 한 번을 속이는 것」의 값을 높인다.

안쪽에서 바깥으로 방어하라: JSON Schema를 단단한 계약으로 만들고, Host가 검증·투영·인가하며, 도구는 작게 두고, 외부 텍스트를 지시로 읽지 마라. 프롬프트는 보조이지 경계가 아니다. 배포 전에 로컬에서 샘플을 검증하라 — 모델은 바꿀 수 있다. 필드 이름, required, 「누가 실행할 수 있는가」는 바꾸면 안 된다.