본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 21. 17:07

x402 결제가 잘못될 수 있는 다섯 가지 방식 — 그리고 에이전트가 지불하기 전에 포착할 수 있는 방법들

요약

자율 에이전트가 HTTP 402 결제를 수행할 때 발생할 수 있는 5가지 주요 공격 벡터를 분석합니다. 구조적 결함으로 인한 결제 오류를 로컬 환경에서 사전에 포착하는 방법과 보안 전략을 다룹니다.

핵심 포인트

  • x402 프로토콜 기반 에이전트 결제의 5가지 주요 공격 방식 제시
  • 동적 주소 스왑, 악의적 과금, 프롬프트 주입 등 보안 위협 분석
  • 로컬 검사로 해결 가능한 구조적 문제와 외부 데이터가 필요한 문제 구분
  • 결제 전 트랜잭션을 스크리닝하는 Frisk 라이브러리 활용 방안

x402는 HTTP 402 Payment Required를 자율 에이전트(autonomous agent)가 실제로 실행할 수 있는 무언가로 변환합니다. 즉, 서버가 가격을 제시하면, 당신의 에이전트가 스테이블코인(stablecoin)으로 결제하고, 요청이 처리됩니다. 인간의 개입(human in the loop)은 없습니다. 그것이 핵심이자, 동시에 모든 문제입니다.

프로그램이 당신이 지켜보지 않는 사이에 돈을 움직일 수 있게 되는 순간, "탐색(discovery)"과 "결제(payment)"는 단일 단계로 붕축됩니다. 당신의 에이전트는 엔드포인트(endpoint)를 찾아내는 동시에 즉시 결제합니다. 만약 그 엔드포인트의 주소, 가격, 목적지 중 무엇이라도 잘못되었다면, 당신이 알아차렸을 때는 이미 돈이 사라진 후일 것입니다.

이것이 어떻게 악용되는지에 대한 작지만 실질적인 연구들이 존재합니다. _Five Attacks on x402_와 A402 (둘 다 arXiv에 게재), Halborn의 기술 보고서, 그리고 AgentLISA의 포지션 페이퍼(position paper)는 모두 몇 가지 공통된 공격 벡터(attack vectors)로 수렴합니다. 저는 오픈 소스 트랜잭션 전 사전 스크리닝 라이브러리인 Frisk를 구축하면서 이 내용들을 읽었으며, 이번 포스트에서 구체적인 것을 해보고자 합니다. 즉, 문서화된 공격들을 살펴보고, 각 공격에 대해 결제 전, 당신의 코드 내 로컬(locally) 환경에서 이를 포착할 수 있는지, 아니면 근본적으로 당신의 머신에 없는 데이터를 필요로 하는지 솔직하게 다루어 보고자 합니다.

이 "로컬 및 결정론적(local-and-deterministic) vs 평판 데이터 필요(needs-reputation-data)"라는 구분은 에이전트 결제 안전성을 생각하는 데 가장 유용한 방식임이 드러났습니다. 그럼 이를 도식화해 봅시다.

공격 표면 (The attack surface)

위의 논문들을 종합해 볼 때, 반복되는 벡터는 다음과 같습니다:

  1. 동적 payTo 스왑. x402 V2에서는 요청마다 목적지 주소가 변경될 수 있습니다. 판매자(또는 중간자)가 사용자에게 주소 A를 제시한 다음, 실제 결제 요구 사항에서 주소 B를 반환합니다. 사용자의 에이전트가 B로 지불하게 됩니다.
  2. 악의적인 402 / 과금. 엔드포인트가 터무니없는 가격을 제시하거나 호출 간에 점진적으로 상승하는 가격을 제시하고, 순진한 에이전트는 402가 말하는 모든 금액을 그냥 지불합니다.
  3. 안전하지 않은 전송(Insecure transport). 결제하려는 주소를 포함하여 견적 자체가 평문 HTTP를 통해 도착하며, 이 경로상의 누구나 이를 재작성할 수 있습니다.
  4. Sybil 유발 발견(Sybil-induced discovery). 공격자가 가짜의, 잘 검토된 것처럼 보이는 엔드포인트로 발견 표면을 범람시켜 사용자의 에이전트를 자신이 통제하는 지갑 쪽으로 유도합니다.
  5. 프롬프트 주입을 통한 결제(Prompt-injection-to-payment). 에이전트가 읽는 콘텐츠가 그것이 보내서는 안 되는 곳에 자금을 전송하도록 설득합니다.

여기서 아무도 입 밖에 내지 않는 부분이 있습니다: 이 중 일부는 순수한 로컬 논리만으로 확인할 수 있고, 일부는 그렇지 않습니다. 이들을 혼동하는 것이 '에이전트 결제 보안'이 실제보다 더 어렵게 들리는 이유입니다.

결제가 발생하기 전에 로컬에서 포착할 수 있는 것들

벡터 1, 2, 그리고 3은 구조적(structural) 문제입니다. 이를 포착하기 위해 평판 그래프나 위협 피드가 필요하지 않습니다. 에이전트가 서명하는 바로 직전에 요청에 대해 실행되는 몇 가지 결정론적 검사만 필요합니다. 네트워크 호출도, 서비스 의존성도, 제3자에 대한 신뢰도 필요 없습니다.

이것이 Frisk의 lite 모드가 정확히 처리하는 부분입니다. 이는 전적으로 사용자의 기기에서 실행되며, 런타임 종속성이 전혀 없고, 이유와 함께 allow, review, 또는 block이라는 판결을 반환합니다. 전체 작동 방식은 다음과 같습니다:

import { Client } from

**동적 `payTo` 스왑(swap) → 이를 포착하세요.** 당신은 지불하고자 	extit{의도한} 상대방(counterparty)을 알고 있습니다. 또한 엔드포인트가 실제로 반환한 `payTo` 값도 가지고 있습니다. 만약 두 값이 다르다면, 그것은 V2 스왑 공격(swap attack)이며, 단 한 줄의 비교로 확인할 수 있습니다:

if (request.observedPayTo &&
request.observedPayTo.toLowerCase() !== counterparty) {
// 견적(quote)과 결제 사이에 주소가 변경되었습니다 — 결제하지 마세요
...


이것은 단일 항목 중 가장 가치 있는 로컬 체크(local check)입니다. 왜냐하면 스왑은 코드를 검토하는 인간에게는 보이지 않기 때문입니다. 이는 오직 런타임(runtime)에, 요청(request)마다 발생합니다.

**과다 청구(Overcharging) → 정책(policy)으로 포착하세요.** 시장 데이터 없이는 임의의 엔드포인트에 대한 "적정" 가격을 알 수 없지만, 	extit{자신만의} 한계치는 확실히 알고 있습니다. 호출당 상한선(per-call ceiling)과 자산 허용 목록(asset allowlist)은 결정론적(deterministic)이며 오프라인에서 작동합니다:

if (policy.maxPerCall !== undefined && amount > policy.maxPerCall) { /* 검토 / }
if (policy.allowedAssets && !policy.allowedAssets.includes(asset)) { /
검토 */ }


이 방식이 2달러짜리 호출이 0.05달러여야 한다는 사실을 알려주지는 않습니다. 하지만 악의적인 `402` 응답이 말하는 대로 에이전트가 조용히 400달러를 지불하는 것은 	extit{반드시} 막아줄 것입니다. 과다 청구로 인한 대부분의 피해는 단순히 지출 한도(spending limit)가 없기 때문에 발생합니다.

**보안되지 않은 전송(Insecure transport) → 이를 포착하세요.** 결제 주소를 포함한 견적(quote)이 `http://`를 통해 전달되었다면, 도착했을 때 해당 주소는 신뢰할 수 없습니다. 해당 주소에 따른 동작을 거부하세요:

if (endpoint && !endpoint.toLowerCase().startsWith("https://")) { /* 등급 강등(downgrade) */ }


여기에 기본적인 위생 관리 사항을 추가하자면, 상대방(counterparty)이 제대로 형성된 주소인지 확인하는 것입니다. 잘못된 형식의 상대방 주소는 버그이거나 탐색(probe) 시도이며, 어느 쪽이든 지불해서는 안 됩니다. (Lite는 또한 로컬 시드 차단 목록(seed blocklist)을 실행합니다. 이는 알려진 악성 주소에 대한 오프라인 체크이며, 실시간으로 지속 업데이트되는 목록은 여기서 유일하게 호스팅된 서비스에 속하는 항목입니다.)

이는 단 하나의 토큰이 이동하기 전에 실행되는 몇 가지 결정론적 (deterministic) 체크이며, 모두 [하나의 파일](https://github.com/Jiangw2718i/frisk/blob/main/typescript/src/lite.ts)에서 읽을 수 있는 코드 내에서 이루어집니다. 신뢰해야 할 별도의 서비스도 없습니다. 이것은 모든 x402 에이전트가 갖추어야 할 최소한의 기준이며, 제가 이를 무료이자 MIT 라이선스로 공개한 이유는 이것이 저를 포함한 그 누구의 API 뒤에 숨겨져 있어서는 안 되기 때문입니다.

## 로컬에서 포착할 수 없는 것들 — 그리고 솔직한 고백

벡터 4와 5 — **시빌 탐지 (Sybil discovery)** 및 **프롬프트 인젝션을 통한 결제 (prompt-injection-to-payment)** — 는 성격이 다릅니다. 로컬에서 실행되는 함수는 특정 주소가 시빌 클러스터 (Sybil cluster)에 속해 있는지, 혹은 깨끗해 보이는 이력을 가진 엔드포인트가 일주일 동안 조용히 지갑을 털어왔는지 진정으로 알 방법이 없습니다. 이를 위해서는 많은 에이전트에 걸쳐 누가 누구에게 지불했는지를 나타내는 그래프이자, 시간이 흐르며 축적된 _평판 데이터 (reputation data)_가 필요합니다. 그 어떤 영리한 오프라인 코드도 이를 대체할 수는 없습니다.

그리고 위 논문들이 실제로는 대부분의 페이지를 할애하고 있는 세 번째 카테고리가 있는데, 이는 라이트(lite) 버전이든 호스팅된 버전이든 그 어떤 스크리닝 라이브러리도 해결할 수 있다고 주장해서는 안 되는 영역입니다. 바로 **프로토콜 및 결제 계층 공격 (protocol- and settlement-layer attacks)** 입니다. 결제 재전송 (Payment replay), 결제가 확정되기 전에 서버가 서비스를 먼저 제공하는 결제 경합 (settlement races) (이는 _Five Attacks_ 논문의 핵심인 "결제되었으나 거부됨" 및 "결제되지 않은 서비스" 결과에 해당합니다), 중개자 신뢰 문제, 그리고 엔드포인트를 겨냥한 경제적 DoS 공격 등이 이에 속합니다. 이러한 문제들은 에이전트가 서명하려는 요청(request)이 아니라, x402 명세(spec), 중개자(facilitator), 그리고 온체인 결제 경로(on-chain settlement path)에 존재합니다. Frisk는 _거래 상대방과 트랜잭션의 형태_를 스크리닝할 뿐이며, 그 밑단의 프로토콜을 수정할 수는 없고 수정해서도 안 됩니다. 따라서 이 포스트는 결제 전 체크(pre-payment check)가 실제로 다룰 수 있는 벡터들로 의도적으로 범위를 제한했습니다. 스크리닝 호출이 재전송이나 원자성(atomicity) 결함을 해결할 수 있다고 가장하는 것은, 에이전트 결제 보안이 과장되는 방식의 나머지 절반을 따르는 것과 같습니다.

따라서 `lite` 모드는 이 점을 솔직하게 밝힙니다. 이 모드는 항상 "낮음 (low)" 신뢰도를 보고하며, Sybil 공격을 탐지한다고 주장하지 않습니다. 로컬 체크(local check)가 평판 문제(reputation problem)를 포착할 수 있다고 가장하는 것은 잘못된 신뢰를 배포하는 방식이며, 이는 아예 체크를 하지 않는 것보다 더 나쁩니다.

이것이 오픈 소스 라이브러리와 호스팅 서비스(hosted service) 사이의 경계입니다. 저는 피칭(pitch)을 위해 이 경계를 흐리기보다는 차라리 명확하게 밝히고 싶습니다. Frisk의 호스팅 측은 평판 이력(reputation history)과 위협 인텔리전스(threat intelligence)가 존재하게 될 영역이며, 아직 초기 단계입니다. 제가 모든 x402 개발자에게 _지금 당장_ 설치하라고 기꺼이 말할 수 있는 부분은 위에 언급된 결정론적 하한선(deterministic floor)입니다. 만약 결제를 수행하는 에이전트를 배포하고 있다면, 거기서부터 시작하십시오. 다섯 가지 체크 항목은 비용이 들지 않으며, 여러분의 프로세스 내에서 실제로 차단 가능한 공격들을 막아줍니다.

## 핵심 요약 (Takeaways)

- 에이전트 결제 보안은 단 하나의 문제가 아닙니다. 세 가지 문제입니다: 결제 전 수행할 수 있는 구조적 체크(structural checks), 데이터로부터 확보해야 하는 평판(reputation), 그리고 어떠한 스크리닝 호출(screening call)보다 하위에 존재하는 프로토콜/결제(protocol/settlement)의 허점입니다. 이들을 각각 별도로 해결하십시오. 그리고 하나의 도구가 다른 문제들까지 해결할 수 있다고 가장하게 두지 마십시오.
- 결정론적 하한선 — payTo-swap 탐지, 지출 정책(spending policy), 전송 및 주소의 건전성(transport and address sanity) — 은 네트워크 호출이나 타인에 대한 신뢰 없이도 문서화된 다섯 가지 x402 공격 클래스 중 세 가지를 포착합니다. 이를 배포하십시오.
- 순수하게 로컬 로직만으로 Sybil/평판 공격을 탐지한다고 주장하는 것은 무엇이든 회의적으로 바라보십시오. 해당 카테고리에는 데이터가 필요하며, 경계에 대한 정직함이 이 게임의 핵심입니다.

Frisk는 MIT 라이선스이며, lite 엔진은 의존성이 없습니다: [`npm i frisk-screen`](https://www.npmjs.com/package/frisk-screen) / [`pip install frisk-screen`](https://pypi.org/project/frisk-screen/). 소스 코드, 위협 모델(threat-model) 노트, 그리고 (짧고 읽기 쉬운) 체크 로직은 [GitHub](https://github.com/Jiangw2718i/frisk)에서 확인할 수 있습니다. 만약 x402를 기반으로 구축하면서 제가 놓친 공격 벡터를 발견한다면 이슈(issue)를 생성해 주세요. 그것이 바로 이 프로젝트가 축적되어야 할 방식입니다.

평판 기반의 호스팅 티어(reputation-backed hosted tier)는 현재 얼리 액세스(early access) 단계입니다. 만약 해당 기능이 필요하시다면 [support@tryfrisk.dev](mailto:support@tryfrisk.dev)로 이메일을 보내주세요. 하지만 위에 설명된 결정론적 하한선(deterministic floor)은 무료이며, MIT 라이선스로 제공되므로 오늘 바로 배포하여 사용하실 수 있습니다.

_참조된 출처: "Five Attacks on x402" 및 "A402" (arXiv); Halborn, "x402 Explained: Security Risks and Controls"; AgentLISA x402 security position paper._

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0