LLM 트레이딩 에이전트 보안
자율 트레이딩 에이전트는 일반 LLM 앱보다 더 엄격한 위협 모델을 가집니다. 인젝션이나 잘못된 도구 경로는 직접 자산 손실로 이어질 수 있습니다.
사용 시기
- 트랜잭션에 서명하고 전송하는 AI 에이전트를 구축할 때
- 트레이딩 봇 또는 온체인 실행 도우미를 감사할 때
- 에이전트의 지갑 키 관리를 설계할 때
- LLM에 주문 배치, 스왑 또는 재무 작업 접근 권한을 부여할 때
작동 방식
방어 계층을 구성하세요. 단일 검사만으로는 충분하지 않습니다. 프롬프트 위생, 지출 정책, 시뮬레이션, 실행 한도, 지갑 격리를 독립적인 제어 체계로 취급하세요.
예제
프롬프트 인젝션을 금전적 공격으로 취급
import re
INJECTION_PATTERNS = [
r'ignore (previous|all) instructions',
r'new (task|directive|instruction)',
r'system prompt',
r'send .{0,50} to 0x[0-9a-fA-F]{40}',
r'transfer .{0,50} to',
r'approve .{0,50} for',
]
def sanitize_onchain_data(text: str) -> str:
for pattern in INJECTION_PATTERNS:
if re.search(pattern, text, re.IGNORECASE):
raise ValueError(f"Potential prompt injection: {text[:100]}")
return text
토큰 이름, 페어 레이블, 웹훅 또는 소셜 피드를 실행 가능한 프롬프트에 무분별하게 주입하지 마세요.
엄격한 지출 한도
from decimal import Decimal
MAX_SINGLE_TX_USD = Decimal("500")
MAX_DAILY_SPEND_USD = Decimal("2000")
class SpendLimitError(Exception):
pass
class SpendLimitGuard:
def check_and_record(self, usd_amount: Decimal) -> None:
if usd_amount > MAX_SINGLE_TX_USD:
raise SpendLimitError(f"Single tx ${usd_amount} exceeds max ${MAX_SINGLE_TX_USD}")
daily = self._get_24h_spend()
if daily + usd_amount > MAX_DAILY_SPEND_USD:
raise SpendLimitError(f"Daily limit: ${daily} + ${usd_amount} > ${MAX_DAILY_SPEND_USD}")
self._record_spend(usd_amount)
전송 전 시뮬레이션
class SlippageError(Exception):
pass
async def safe_execute(self, tx: dict, expected_min_out: int | None = None) -> str:
sim_result = await self.w3.eth.call(tx)
if expected_min_out is None:
raise ValueError("min_amount_out is required before send")
actual_out = decode_uint256(sim_result)
if actual_out < expected_min_out:
raise SlippageError(f"Simulation: {actual_out} < {expected_min_out}")
signed = self.account.sign_transaction(tx)
return await self.w3.eth.send_raw_transaction(signed.raw_transaction)
서킷 브레이커
class TradingCircuitBreaker:
MAX_CONSECUTIVE_LOSSES = 3
MAX_HOURLY_LOSS_PCT = 0.05
def check(self, portfolio_value: float) -> None:
if self.consecutive_losses >= self.MAX_CONSECUTIVE_LOSSES:
self.halt("Too many consecutive losses")
if self.hour_start_value <= 0:
self.halt("Invalid hour_start_value")
return
hourly_pnl = (portfolio_value - self.hour_start_value) / self.hour_start_value
if hourly_pnl < -self.MAX_HOURLY_LOSS_PCT:
self.halt(f"Hourly PnL {hourly_pnl:.1%} below threshold")
지갑 격리
import os
from eth_account import Account
private_key = os.environ.get("TRADING_WALLET_PRIVATE_KEY")
if not private_key:
raise EnvironmentError("TRADING_WALLET_PRIVATE_KEY not set")
account = Account.from_key(private_key)
필요한 세션 자금만 보유한 전용 핫월렛을 사용하세요. 절대 기본 재무 지갑을 에이전트가 가리키게 하지 마세요.
MEV 및 마감 시간 보호
import time
PRIVATE_RPC = "https://rpc.flashbots.net"
MAX_SLIPPAGE_BPS = {"stable": 10, "volatile": 50}
deadline = int(time.time()) + 60
배포 전 체크리스트
- 외부 데이터는 LLM 컨텍스트에 들어가기 전에 정제됨
- 지출 한도는 모델 출력과 독립적으로 적용됨
- 트랜잭션은 전송 전에 시뮬레이션됨
min_amount_out은 필수- 서킷 브레이커는 손실 또는 유효하지 않은 상태에서 정지됨
- 키는 환경 변수 또는 비밀 관리자에서 가져오며, 코드나 로그에 절대 노출되지 않음
- 필요에 따라 비공개 멤풀 또는 보호된 라우팅 사용
- 슬리피지 및 마감 시간은 전략별로 설정됨
- 모든 에이전트 결정은 성공 전송뿐만 아니라 감사 로그에 기록됨