Install any skill in seconds. Free to start, no credit card required.
Get Started Free →日本語翻訳:このファイルは llm-trading-agent-security 用の日本語翻訳が必要です
.claude/skills/affaan-m-llm-trading-agent-security/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 42% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 114% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 32% | 0% |
| case-21 | ✗→✓ | ▲ Improved | 57% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 56% | 0% |
自主交易代理面临比普通 LLM 应用更严苛的威胁模型:一次注入或错误的工具路径可能直接导致资产损失。
构建多层防御体系。单一检查不足以保障安全。应将提示词卫生、支出策略、模拟执行、执行限制和钱包隔离视为独立控制措施。
pythonimport 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
切勿将代币名称、交易对标签、网络钩子或社交信息流盲目注入具备执行能力的提示词中。
pythonfrom 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)
pythonclass 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)
pythonclass 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")
pythonimport 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)
使用仅包含所需会话资金的专用热钱包。切勿将代理指向主资金钱包。
pythonimport time PRIVATE_RPC = "https://rpc.flashbots.net" MAX_SLIPPAGE_BPS = {"stable": 10, "volatile": 50} deadline = int(time.time()) + 60
min_amount_out 为强制要求| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 21,981 | 23,361 | +6% | 1 | 1 | 0% | 4,280 | 6,088 | +42% | 0 | 0 | — |
case-02 | pass→pass | 12,321 | 11,815 | -4% | 1 | 1 | 0% | 2,132 | 3,326 | +56% | 0 | 0 | — |
case-03 | pass→pass | 11,121 | 10,506 | -6% | 1 | 1 | 0% | 1,875 | 3,236 | +73% | 0 | 0 | — |
case-04 | fail→pass | 6,015 | 6,260 | +4% | 1 | 1 | 0% | 1,065 | 2,283 | +114% | 0 | 0 | — |
case-05 | pass→pass | 12,816 | 10,225 | -20% | 1 | 1 | 0% | 2,114 | 3,007 | +42% | 0 | 0 | — |
case-06 | fail→pass | 16,198 | 14,666 | -9% | 1 | 1 | 0% | 3,140 | 4,148 | +32% | 0 | 0 | — |
case-07 | pass→pass | 12,169 | 3,776 | -69% | 1 | 1 | 0% | 2,035 | 1,909 | -6% | 0 | 0 | — |
case-08 | pass→pass | 9,168 | 4,788 | -48% | 1 | 1 | 0% | 1,475 | 1,995 | +35% | 0 | 0 | — |
case-09 | pass→pass | 16,888 | 13,943 | -17% | 1 | 1 | 0% | 3,479 | 4,037 | +16% | 0 | 0 | — |
case-10 | pass→pass | 6,097 | 5,086 | -17% | 1 | 1 | 0% | 1,222 | 2,117 | +73% | 0 | 0 | — |
case-11 | pass→pass | 10,106 | 7,555 | -25% | 1 | 1 | 0% | 1,870 | 2,593 | +39% | 0 | 0 | — |
case-12 | pass→pass | 9,625 | 9,163 | -5% | 1 | 1 | 0% | 1,758 | 2,856 | +62% | 0 | 0 | — |
case-13 | pass→pass | 15,433 | 13,890 | -10% | 1 | 1 | 0% | 2,808 | 3,874 | +38% | 0 | 0 | — |
case-14 | pass→pass | 13,467 | 10,702 | -21% | 1 | 1 | 0% | 2,393 | 3,301 | +38% | 0 | 0 | — |
case-15 | pass→pass | 13,221 | 13,387 | +1% | 1 | 1 | 0% | 2,397 | 3,541 | +48% | 0 | 0 | — |
case-16 | pass→pass | 14,662 | 14,838 | +1% | 1 | 1 | 0% | 2,563 | 4,021 | +57% | 0 | 0 | — |
case-17 | pass→pass | 14,811 | 14,651 | -1% | 1 | 1 | 0% | 2,715 | 3,894 | +43% | 0 | 0 | — |
case-18 | pass→pass | 17,449 | 16,500 | -5% | 1 | 1 | 0% | 3,343 | 4,749 | +42% | 0 | 0 | — |
case-19 | pass→pass | 19,555 | 15,846 | -19% | 1 | 1 | 0% | 3,598 | 4,486 | +25% | 0 | 0 | — |
case-20 | pass→pass | 12,880 | 9,013 | -30% | 1 | 1 | 0% | 2,052 | 2,753 | +34% | 0 | 0 | — |
case-21 | fail→pass | 14,830 | 14,165 | -4% | 1 | 1 | 0% | 2,359 | 3,698 | +57% | 0 | 0 | — |
case-22 | pass→pass | 18,153 | 17,053 | -6% | 1 | 1 | 0% | 3,036 | 4,609 | +52% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +18 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.